mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
migrated to retrofit+jackson
This commit is contained in:
parent
9a5bb5aac8
commit
c356f9b426
13
.idea/deploymentTargetDropDown.xml
generated
13
.idea/deploymentTargetDropDown.xml
generated
@ -1,17 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="deploymentTargetDropDown">
|
<component name="deploymentTargetDropDown">
|
||||||
<runningDeviceTargetSelectedWithDropDown>
|
|
||||||
<Target>
|
|
||||||
<type value="RUNNING_DEVICE_TARGET" />
|
|
||||||
<deviceKey>
|
|
||||||
<Key>
|
|
||||||
<type value="VIRTUAL_DEVICE_PATH" />
|
|
||||||
<value value="$USER_HOME$/.android/avd/Pixel_5_API_29.avd" />
|
|
||||||
</Key>
|
|
||||||
</deviceKey>
|
|
||||||
</Target>
|
|
||||||
</runningDeviceTargetSelectedWithDropDown>
|
|
||||||
<targetSelectedWithDropDown>
|
<targetSelectedWithDropDown>
|
||||||
<Target>
|
<Target>
|
||||||
<type value="QUICK_BOOT_TARGET" />
|
<type value="QUICK_BOOT_TARGET" />
|
||||||
@ -23,7 +12,7 @@
|
|||||||
</deviceKey>
|
</deviceKey>
|
||||||
</Target>
|
</Target>
|
||||||
</targetSelectedWithDropDown>
|
</targetSelectedWithDropDown>
|
||||||
<timeTargetWasSelectedWithDropDown value="2021-12-17T12:12:37.849691Z" />
|
<timeTargetWasSelectedWithDropDown value="2021-12-17T12:50:01.440166Z" />
|
||||||
<runningDeviceTargetsSelectedWithDialog>
|
<runningDeviceTargetsSelectedWithDialog>
|
||||||
<Target>
|
<Target>
|
||||||
<type value="RUNNING_DEVICE_TARGET" />
|
<type value="RUNNING_DEVICE_TARGET" />
|
||||||
|
@ -8,7 +8,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'xyz.btcland.libretube'
|
applicationId 'xyz.btcland.libretube'
|
||||||
minSdk 19
|
minSdk 21
|
||||||
targetSdk 31
|
targetSdk 31
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName '1.0'
|
versionName '1.0'
|
||||||
@ -39,14 +39,16 @@ dependencies {
|
|||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0-rc01'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0-rc01'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0-rc01'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0-rc01'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
testImplementation 'junit:junit:'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.9'
|
|
||||||
implementation 'com.squareup.picasso:picasso:2.8'
|
implementation 'com.squareup.picasso:picasso:2.8'
|
||||||
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
|
implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
|
||||||
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
|
implementation 'com.squareup.retrofit2:converter-jackson:2.9.0'
|
||||||
|
|
||||||
}
|
}
|
@ -1,24 +1,27 @@
|
|||||||
package xyz.btcland.libretube
|
package xyz.btcland.libretube
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
|
||||||
// TODO: Rename parameter arguments, choose names that match
|
// TODO: Rename parameter arguments, choose names that match
|
||||||
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
||||||
private const val ARG_PARAM1 = "param1"
|
private const val ARG_PARAM1 = "param1"
|
||||||
private const val ARG_PARAM2 = "param2"
|
private const val ARG_PARAM2 = "param2"
|
||||||
|
const val TAG = "HomeFragment"
|
||||||
/**
|
/**
|
||||||
* A simple [Fragment] subclass.
|
* A simple [Fragment] subclass.
|
||||||
* Use the [Home.newInstance] factory method to
|
* Use the [Home.newInstance] factory method to
|
||||||
@ -79,10 +82,10 @@ class Home : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchJson(progressBar: ProgressBar, recyclerView: RecyclerView) {
|
private fun fetchJson(progressBar: ProgressBar, recyclerView: RecyclerView) {
|
||||||
val client = OkHttpClient()
|
//val client = OkHttpClient()
|
||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
val request = Request.Builder()
|
/* val request = Request.Builder()
|
||||||
.url("http://piped-api.alefvanoon.xyz/trending?region=US")
|
.url("http://piped-api.alefvanoon.xyz/trending?region=US")
|
||||||
.build()
|
.build()
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).enqueue(object : Callback {
|
||||||
@ -102,10 +105,24 @@ class Home : Fragment() {
|
|||||||
recyclerView.adapter = TrendingAdapter(trendingList)
|
recyclerView.adapter = TrendingAdapter(trendingList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})*/
|
||||||
|
lifecycleScope.launchWhenCreated {
|
||||||
|
val response = try {
|
||||||
|
RetrofitInstance.api.getTrending("US")
|
||||||
|
}catch(e: IOException) {
|
||||||
|
println(e)
|
||||||
|
Log.e(TAG, "IOException, you might not have internet connection")
|
||||||
|
return@launchWhenCreated
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
Log.e(TAG, "HttpException, unexpected response")
|
||||||
|
return@launchWhenCreated
|
||||||
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
progressBar.visibility = View.GONE
|
||||||
|
recyclerView.adapter = TrendingAdapter(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
run()
|
run()
|
||||||
|
|
||||||
|
15
app/src/main/java/xyz/btcland/libretube/PipedApi.kt
Normal file
15
app/src/main/java/xyz/btcland/libretube/PipedApi.kt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package xyz.btcland.libretube
|
||||||
|
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
import xyz.btcland.libretube.obj.StreamItem
|
||||||
|
import xyz.btcland.libretube.obj.Streams
|
||||||
|
|
||||||
|
interface PipedApi {
|
||||||
|
@GET("trending")
|
||||||
|
suspend fun getTrending(@Query("region") region: String): List<StreamItem>
|
||||||
|
|
||||||
|
@GET("streams/{videoId}")
|
||||||
|
suspend fun getStreams(@Path("videoId") videoId: String): Streams
|
||||||
|
}
|
@ -6,215 +6,19 @@ import android.content.DialogInterface
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
|
||||||
import com.google.android.exoplayer2.MediaItem
|
|
||||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource
|
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource
|
|
||||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import okhttp3.*
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class Player : Activity() {
|
class Player : Activity() {
|
||||||
|
|
||||||
private lateinit var exoPlayerView: StyledPlayerView
|
|
||||||
private lateinit var motionLayout: SingleViewTouchableMotionLayout
|
|
||||||
private lateinit var exoPlayer: ExoPlayer
|
|
||||||
private var videoId: String? =null
|
|
||||||
private var seekTo: Long? = 0
|
|
||||||
private var whichQuality: Int? = 0
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_player)
|
setContentView(R.layout.activity_player)
|
||||||
seekTo = intent.getStringExtra("seekTo")?.toLong()
|
|
||||||
whichQuality = intent.getStringExtra("quality")?.toInt()
|
|
||||||
videoId=intent.getStringExtra("videoId")
|
|
||||||
exoPlayerView = findViewById(R.id.fullscreen_player)
|
|
||||||
fetchJson(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun fetchJson(context: Context) {
|
|
||||||
val client = OkHttpClient()
|
|
||||||
|
|
||||||
fun run() {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("https://pipedapi.kavin.rocks/streams/$videoId")
|
|
||||||
.build()
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
response.use {
|
|
||||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
|
||||||
val body = response.body!!.string()
|
|
||||||
println(body)
|
|
||||||
val gson = GsonBuilder().create()
|
|
||||||
val videoInPlayer = gson.fromJson(body, VideoInPlayer::class.java)
|
|
||||||
var videosNameArray: Array<CharSequence> = arrayOf()
|
|
||||||
videosNameArray += "HLS"
|
|
||||||
for (vids in videoInPlayer.videoStreams){
|
|
||||||
val name = vids.quality +" "+ vids.format
|
|
||||||
videosNameArray += name
|
|
||||||
}
|
|
||||||
runOnUiThread {
|
|
||||||
exoPlayer = ExoPlayer.Builder(context)
|
|
||||||
.build()
|
|
||||||
var subtitle = mutableListOf<MediaItem.SubtitleConfiguration>()
|
|
||||||
if(videoInPlayer.subtitles.isNotEmpty()){
|
|
||||||
subtitle?.add(
|
|
||||||
MediaItem.SubtitleConfiguration.Builder(videoInPlayer.subtitles[0].url.toUri())
|
|
||||||
.setMimeType(videoInPlayer.subtitles[0].mimeType) // The correct MIME type (required).
|
|
||||||
.setLanguage(videoInPlayer.subtitles[0].code) // The subtitle language (optional).
|
|
||||||
.build())}
|
|
||||||
if(whichQuality==0){
|
|
||||||
val mediaItem: MediaItem = MediaItem.Builder()
|
|
||||||
.setUri(videoInPlayer.hls)
|
|
||||||
.setSubtitleConfigurations(subtitle)
|
|
||||||
.build()
|
|
||||||
exoPlayer.setMediaItem(mediaItem)
|
|
||||||
}else{
|
|
||||||
val dataSourceFactory: DataSource.Factory =
|
|
||||||
DefaultHttpDataSource.Factory()
|
|
||||||
val videoItem: MediaItem = MediaItem.Builder()
|
|
||||||
.setUri(videoInPlayer.videoStreams[whichQuality!!-1].url)
|
|
||||||
.setSubtitleConfigurations(subtitle)
|
|
||||||
.build()
|
|
||||||
val videoSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory)
|
|
||||||
.createMediaSource(videoItem)
|
|
||||||
var audioSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory)
|
|
||||||
.createMediaSource(
|
|
||||||
MediaItem.fromUri(
|
|
||||||
videoInPlayer.audioStreams[0].url
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (videoInPlayer.videoStreams[whichQuality!!-1].quality=="720p" || videoInPlayer.videoStreams[whichQuality!!-1].quality=="1080p" || videoInPlayer.videoStreams[whichQuality!!-1].quality=="480p" ){
|
|
||||||
audioSource = ProgressiveMediaSource.Factory(dataSourceFactory)
|
|
||||||
.createMediaSource(
|
|
||||||
MediaItem.fromUri(
|
|
||||||
videoInPlayer.audioStreams[getMostBitRate(
|
|
||||||
videoInPlayer.audioStreams
|
|
||||||
)].url
|
|
||||||
)
|
|
||||||
)
|
|
||||||
//println("fuckkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkitttttttttttttttttttttt")
|
|
||||||
}
|
|
||||||
val mergeSource: MediaSource = MergingMediaSource(videoSource,audioSource)
|
|
||||||
exoPlayer.setMediaSource(mergeSource)
|
|
||||||
}
|
|
||||||
findViewById<TextView>(R.id.quality_text).text=videosNameArray[whichQuality!!]
|
|
||||||
|
|
||||||
exoPlayerView.setShowSubtitleButton(true)
|
|
||||||
exoPlayerView.setShowNextButton(false)
|
|
||||||
exoPlayerView.setShowPreviousButton(false)
|
|
||||||
exoPlayerView.controllerShowTimeoutMs = 1500
|
|
||||||
exoPlayerView.controllerHideOnTouch = true
|
|
||||||
exoPlayerView.player = exoPlayer
|
|
||||||
|
|
||||||
///exoPlayer.getMediaItemAt(5)
|
|
||||||
exoPlayer.prepare()
|
|
||||||
exoPlayer.play()
|
|
||||||
exoPlayer.seekTo(seekTo!!)
|
|
||||||
findViewById<ImageButton>(R.id.quality_select).setOnClickListener{
|
|
||||||
val builder: AlertDialog.Builder? = let {
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
}
|
|
||||||
builder!!.setTitle(R.string.choose_quality_dialog)
|
|
||||||
.setItems(videosNameArray,
|
|
||||||
DialogInterface.OnClickListener { _, which ->
|
|
||||||
// The 'which' argument contains the index position
|
|
||||||
// of the selected item
|
|
||||||
//println(which)
|
|
||||||
if(videoInPlayer.subtitles.isNotEmpty()) {
|
|
||||||
var subtitle =
|
|
||||||
mutableListOf<MediaItem.SubtitleConfiguration>()
|
|
||||||
subtitle?.add(
|
|
||||||
MediaItem.SubtitleConfiguration.Builder(videoInPlayer.subtitles[0].url.toUri())
|
|
||||||
.setMimeType(videoInPlayer.subtitles[0].mimeType) // The correct MIME type (required).
|
|
||||||
.setLanguage(videoInPlayer.subtitles[0].code) // The subtitle language (optional).
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(which==0){
|
|
||||||
val mediaItem: MediaItem = MediaItem.Builder()
|
|
||||||
.setUri(videoInPlayer.hls)
|
|
||||||
.setSubtitleConfigurations(subtitle)
|
|
||||||
.build()
|
|
||||||
exoPlayer.setMediaItem(mediaItem)
|
|
||||||
}else{
|
|
||||||
val dataSourceFactory: DataSource.Factory =
|
|
||||||
DefaultHttpDataSource.Factory()
|
|
||||||
val videoItem: MediaItem = MediaItem.Builder()
|
|
||||||
.setUri(videoInPlayer.videoStreams[which-1].url)
|
|
||||||
.setSubtitleConfigurations(subtitle)
|
|
||||||
.build()
|
|
||||||
val videoSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory)
|
|
||||||
.createMediaSource(videoItem)
|
|
||||||
var audioSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory)
|
|
||||||
.createMediaSource(
|
|
||||||
MediaItem.fromUri(
|
|
||||||
videoInPlayer.audioStreams[0].url
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (videoInPlayer.videoStreams[which-1].quality=="720p" || videoInPlayer.videoStreams[which-1].quality=="1080p" || videoInPlayer.videoStreams[which-1].quality=="480p" ){
|
|
||||||
audioSource = ProgressiveMediaSource.Factory(dataSourceFactory)
|
|
||||||
.createMediaSource(
|
|
||||||
MediaItem.fromUri(
|
|
||||||
videoInPlayer.audioStreams[getMostBitRate(
|
|
||||||
videoInPlayer.audioStreams
|
|
||||||
)].url
|
|
||||||
)
|
|
||||||
)
|
|
||||||
//println("fuckkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkitttttttttttttttttttttt")
|
|
||||||
}
|
|
||||||
val mergeSource: MediaSource = MergingMediaSource(videoSource,audioSource)
|
|
||||||
exoPlayer.setMediaSource(mergeSource)
|
|
||||||
}
|
|
||||||
findViewById<TextView>(R.id.quality_text).text=videosNameArray[which]
|
|
||||||
})
|
|
||||||
val dialog: AlertDialog? = builder?.create()
|
|
||||||
dialog?.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
fun getMostBitRate(audios: List<Stream>):Int{
|
|
||||||
var bitrate =0
|
|
||||||
var index = 0
|
|
||||||
for ((i, audio) in audios.withIndex()){
|
|
||||||
val q = audio.quality.replace(" kbps","").toInt()
|
|
||||||
if (q>bitrate){
|
|
||||||
bitrate=q
|
|
||||||
index = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
myApp.seekTo=exoPlayer.currentPosition
|
|
||||||
exoPlayer.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,8 +18,7 @@ import com.google.android.exoplayer2.source.MediaSource
|
|||||||
import com.google.android.exoplayer2.ui.PlayerView
|
import com.google.android.exoplayer2.ui.PlayerView
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerControlView
|
import com.google.android.exoplayer2.ui.StyledPlayerControlView
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
import com.google.android.exoplayer2.ui.StyledPlayerView
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@ -47,9 +46,11 @@ import android.widget.TextView
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import com.google.android.exoplayer2.util.Util
|
import com.google.android.exoplayer2.util.Util
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.text.PrecomputedTextCompat
|
import androidx.core.text.PrecomputedTextCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
@ -59,6 +60,15 @@ import com.google.android.exoplayer2.util.RepeatModeUtil
|
|||||||
|
|
||||||
import com.google.android.exoplayer2.ui.TimeBar
|
import com.google.android.exoplayer2.ui.TimeBar
|
||||||
import com.google.android.exoplayer2.ui.TimeBar.OnScrubListener
|
import com.google.android.exoplayer2.ui.TimeBar.OnScrubListener
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import xyz.btcland.libretube.obj.PipedStream
|
||||||
|
|
||||||
|
|
||||||
// TODO: Rename parameter arguments, choose names that match
|
// TODO: Rename parameter arguments, choose names that match
|
||||||
@ -186,12 +196,13 @@ class PlayerFragment : Fragment() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//FullScreen button trigger
|
||||||
view.findViewById<ImageButton>(R.id.fullscreen).setOnClickListener{
|
view.findViewById<ImageButton>(R.id.fullscreen).setOnClickListener{
|
||||||
//remember to hide everything when new shit added
|
//remember to hide everything when new shit added
|
||||||
if (!isFullScreen){
|
if (!isFullScreen){
|
||||||
view.findViewById<ScrollView>(R.id.scrollView2).visibility = View.GONE
|
/*view.findViewById<ScrollView>(R.id.scrollView2).visibility = View.GONE
|
||||||
view.findViewById<LinearLayout>(R.id.linLayout).visibility = View.GONE
|
view.findViewById<LinearLayout>(R.id.linLayout).visibility = View.GONE
|
||||||
view.findViewById<TextView>(R.id.textTest).visibility = View.GONE
|
view.findViewById<TextView>(R.id.textTest).visibility = View.GONE*/
|
||||||
//view.findViewById<ConstraintLayout>(R.id.main_container).visibility = View.GONE
|
//view.findViewById<ConstraintLayout>(R.id.main_container).visibility = View.GONE
|
||||||
with(motionLayout) {
|
with(motionLayout) {
|
||||||
getConstraintSet(R.id.start).constrainHeight(R.id.player, -1)
|
getConstraintSet(R.id.start).constrainHeight(R.id.player, -1)
|
||||||
@ -200,9 +211,9 @@ class PlayerFragment : Fragment() {
|
|||||||
isFullScreen=true
|
isFullScreen=true
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
view.findViewById<ScrollView>(R.id.scrollView2).visibility = View.VISIBLE
|
/*view.findViewById<ScrollView>(R.id.scrollView2).visibility = View.VISIBLE
|
||||||
view.findViewById<LinearLayout>(R.id.linLayout).visibility = View.VISIBLE
|
view.findViewById<LinearLayout>(R.id.linLayout).visibility = View.VISIBLE
|
||||||
view.findViewById<TextView>(R.id.textTest).visibility = View.VISIBLE
|
view.findViewById<TextView>(R.id.textTest).visibility = View.VISIBLE*/
|
||||||
//view.findViewById<ConstraintLayout>(R.id.main_container).visibility = View.VISIBLE
|
//view.findViewById<ConstraintLayout>(R.id.main_container).visibility = View.VISIBLE
|
||||||
with(motionLayout) {
|
with(motionLayout) {
|
||||||
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
|
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
|
||||||
@ -245,12 +256,19 @@ class PlayerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchJson(view: View) {
|
private fun fetchJson(view: View) {
|
||||||
val client = OkHttpClient()
|
//val client = OkHttpClient()
|
||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
val request = Request.Builder()
|
/* val request = Request.Builder()
|
||||||
.url("http://piped-api.alefvanoon.xyz/streams/$videoId")
|
.url("http://piped-api.alefvanoon.xyz/streams/$videoId")
|
||||||
.build()
|
.build()
|
||||||
|
*//* val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl("http://piped-api.alefvanoon.xyz/")
|
||||||
|
.addConverterFactory(JacksonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val videoInPlayer2 = retrofit.create(vidVid::class.java).vidIn(videoId)*//*
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).enqueue(object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@ -264,8 +282,8 @@ class PlayerFragment : Fragment() {
|
|||||||
val videoInPlayer = gson.fromJson(body, VideoInPlayer::class.java)
|
val videoInPlayer = gson.fromJson(body, VideoInPlayer::class.java)
|
||||||
var videosNameArray: Array<CharSequence> = arrayOf()
|
var videosNameArray: Array<CharSequence> = arrayOf()
|
||||||
videosNameArray += "HLS"
|
videosNameArray += "HLS"
|
||||||
for (vids in videoInPlayer.videoStreams){
|
for (vid in videoInPlayer.videoStreams){
|
||||||
val name = vids.quality +" "+ vids.format
|
val name = vid.quality +" "+ vid.format
|
||||||
videosNameArray += name
|
videosNameArray += name
|
||||||
}
|
}
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@ -295,15 +313,13 @@ class PlayerFragment : Fragment() {
|
|||||||
view.findViewById<TextView>(R.id.title_textView).text = videoInPlayer.title
|
view.findViewById<TextView>(R.id.title_textView).text = videoInPlayer.title
|
||||||
|
|
||||||
view.findViewById<ImageButton>(R.id.quality_select).setOnClickListener{
|
view.findViewById<ImageButton>(R.id.quality_select).setOnClickListener{
|
||||||
|
//Dialog for quality selection
|
||||||
val builder: AlertDialog.Builder? = activity?.let {
|
val builder: AlertDialog.Builder? = activity?.let {
|
||||||
AlertDialog.Builder(it)
|
AlertDialog.Builder(it)
|
||||||
}
|
}
|
||||||
builder!!.setTitle(R.string.choose_quality_dialog)
|
builder!!.setTitle(R.string.choose_quality_dialog)
|
||||||
.setItems(videosNameArray,
|
.setItems(videosNameArray,
|
||||||
DialogInterface.OnClickListener { _, which ->
|
DialogInterface.OnClickListener { _, which ->
|
||||||
// The 'which' argument contains the index position
|
|
||||||
// of the selected item
|
|
||||||
//println(which)
|
|
||||||
whichQuality = which
|
whichQuality = which
|
||||||
if(videoInPlayer.subtitles.isNotEmpty()) {
|
if(videoInPlayer.subtitles.isNotEmpty()) {
|
||||||
var subtitle =
|
var subtitle =
|
||||||
@ -345,6 +361,7 @@ class PlayerFragment : Fragment() {
|
|||||||
val dialog: AlertDialog? = builder?.create()
|
val dialog: AlertDialog? = builder?.create()
|
||||||
dialog?.show()
|
dialog?.show()
|
||||||
}
|
}
|
||||||
|
//Listener for play and pause icon change
|
||||||
exoPlayer!!.addListener(object : com.google.android.exoplayer2.Player.Listener {
|
exoPlayer!!.addListener(object : com.google.android.exoplayer2.Player.Listener {
|
||||||
override fun onPlayerStateChanged(playWhenReady: Boolean,playbackState: Int) {
|
override fun onPlayerStateChanged(playWhenReady: Boolean,playbackState: Int) {
|
||||||
if (playWhenReady && playbackState == Player.STATE_READY) {
|
if (playWhenReady && playbackState == Player.STATE_READY) {
|
||||||
@ -366,22 +383,134 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})*/
|
||||||
|
|
||||||
|
lifecycleScope.launchWhenCreated {
|
||||||
|
val response = try {
|
||||||
|
RetrofitInstance.api.getStreams(videoId!!)
|
||||||
|
}catch(e: IOException) {
|
||||||
|
println(e)
|
||||||
|
Log.e(TAG, "IOException, you might not have internet connection")
|
||||||
|
return@launchWhenCreated
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
Log.e(TAG, "HttpException, unexpected response")
|
||||||
|
return@launchWhenCreated
|
||||||
|
}
|
||||||
|
var videosNameArray: Array<CharSequence> = arrayOf()
|
||||||
|
videosNameArray += "HLS"
|
||||||
|
for (vid in response.videoStreams!!){
|
||||||
|
val name = vid.quality +" "+ vid.format
|
||||||
|
videosNameArray += name
|
||||||
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
var subtitle = mutableListOf<SubtitleConfiguration>()
|
||||||
|
if(response.subtitles!!.isNotEmpty()){
|
||||||
|
subtitle?.add(SubtitleConfiguration.Builder(response.subtitles!![0].url!!.toUri())
|
||||||
|
.setMimeType(response.subtitles!![0].mimeType!!) // The correct MIME type (required).
|
||||||
|
.setLanguage(response.subtitles!![0].code) // The subtitle language (optional).
|
||||||
|
.build())}
|
||||||
|
val mediaItem: MediaItem = MediaItem.Builder()
|
||||||
|
.setUri(response.hls)
|
||||||
|
.setSubtitleConfigurations(subtitle)
|
||||||
|
.build()
|
||||||
|
exoPlayer = ExoPlayer.Builder(view.context)
|
||||||
|
.build()
|
||||||
|
exoPlayerView.setShowSubtitleButton(true)
|
||||||
|
exoPlayerView.setShowNextButton(false)
|
||||||
|
exoPlayerView.setShowPreviousButton(false)
|
||||||
|
//exoPlayerView.controllerShowTimeoutMs = 1500
|
||||||
|
exoPlayerView.controllerHideOnTouch = true
|
||||||
|
exoPlayerView.player = exoPlayer
|
||||||
|
exoPlayer.setMediaItem(mediaItem)
|
||||||
|
///exoPlayer.getMediaItemAt(5)
|
||||||
|
exoPlayer.prepare()
|
||||||
|
exoPlayer.play()
|
||||||
|
|
||||||
|
view.findViewById<TextView>(R.id.title_textView).text = response.title
|
||||||
|
|
||||||
|
view.findViewById<ImageButton>(R.id.quality_select).setOnClickListener{
|
||||||
|
//Dialog for quality selection
|
||||||
|
val builder: AlertDialog.Builder? = activity?.let {
|
||||||
|
AlertDialog.Builder(it)
|
||||||
|
}
|
||||||
|
builder!!.setTitle(R.string.choose_quality_dialog)
|
||||||
|
.setItems(videosNameArray,
|
||||||
|
DialogInterface.OnClickListener { _, which ->
|
||||||
|
whichQuality = which
|
||||||
|
if(response.subtitles!!.isNotEmpty()) {
|
||||||
|
var subtitle =
|
||||||
|
mutableListOf<SubtitleConfiguration>()
|
||||||
|
subtitle?.add(
|
||||||
|
SubtitleConfiguration.Builder(response.subtitles!![0].url!!.toUri())
|
||||||
|
.setMimeType(response.subtitles!![0].mimeType!!) // The correct MIME type (required).
|
||||||
|
.setLanguage(response.subtitles!![0].code) // The subtitle language (optional).
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if(which==0){
|
||||||
|
val mediaItem: MediaItem = MediaItem.Builder()
|
||||||
|
.setUri(response.hls)
|
||||||
|
.setSubtitleConfigurations(subtitle)
|
||||||
|
.build()
|
||||||
|
exoPlayer.setMediaItem(mediaItem)
|
||||||
|
}else{
|
||||||
|
val dataSourceFactory: DataSource.Factory =
|
||||||
|
DefaultHttpDataSource.Factory()
|
||||||
|
val videoItem: MediaItem = MediaItem.Builder()
|
||||||
|
.setUri(response.videoStreams[which-1].url)
|
||||||
|
.setSubtitleConfigurations(subtitle)
|
||||||
|
.build()
|
||||||
|
val videoSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory)
|
||||||
|
.createMediaSource(videoItem)
|
||||||
|
var audioSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory)
|
||||||
|
.createMediaSource(fromUri(response.audioStreams!![0].url!!))
|
||||||
|
if (response.videoStreams[which-1].quality=="720p" || response.videoStreams[which-1].quality=="1080p" || response.videoStreams[which-1].quality=="480p" ){
|
||||||
|
audioSource = ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
|
.createMediaSource(fromUri(response.audioStreams!![getMostBitRate(response.audioStreams)].url!!))
|
||||||
|
}
|
||||||
|
val mergeSource: MediaSource = MergingMediaSource(videoSource,audioSource)
|
||||||
|
exoPlayer.setMediaSource(mergeSource)
|
||||||
|
}
|
||||||
|
view.findViewById<TextView>(R.id.quality_text).text=videosNameArray[which]
|
||||||
})
|
})
|
||||||
|
val dialog: AlertDialog? = builder?.create()
|
||||||
|
dialog?.show()
|
||||||
|
}
|
||||||
|
//Listener for play and pause icon change
|
||||||
|
exoPlayer!!.addListener(object : com.google.android.exoplayer2.Player.Listener {
|
||||||
|
override fun onPlayerStateChanged(playWhenReady: Boolean,playbackState: Int) {
|
||||||
|
if (playWhenReady && playbackState == Player.STATE_READY) {
|
||||||
|
// media actually playing
|
||||||
|
view.findViewById<ImageView>(R.id.play_imageView).setImageResource(R.drawable.ic_pause)
|
||||||
|
} else if (playWhenReady) {
|
||||||
|
// might be idle (plays after prepare()),
|
||||||
|
// buffering (plays when data available)
|
||||||
|
// or ended (plays when seek away from end)
|
||||||
|
view.findViewById<ImageView>(R.id.play_imageView).setImageResource(R.drawable.ic_play)
|
||||||
|
} else {
|
||||||
|
// player paused in any state
|
||||||
|
view.findViewById<ImageView>(R.id.play_imageView).setImageResource(R.drawable.ic_play)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
run()
|
run()
|
||||||
|
|
||||||
}
|
}
|
||||||
fun Fragment?.runOnUiThread(action: () -> Unit) {
|
private fun Fragment?.runOnUiThread(action: () -> Unit) {
|
||||||
this ?: return
|
this ?: return
|
||||||
if (!isAdded) return // Fragment not attached to an Activity
|
if (!isAdded) return // Fragment not attached to an Activity
|
||||||
activity?.runOnUiThread(action)
|
activity?.runOnUiThread(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMostBitRate(audios: List<Stream>):Int{
|
fun getMostBitRate(audios: List<PipedStream>):Int{
|
||||||
var bitrate =0
|
var bitrate =0
|
||||||
var index = 0
|
var index = 0
|
||||||
for ((i, audio) in audios.withIndex()){
|
for ((i, audio) in audios.withIndex()){
|
||||||
val q = audio.quality.replace(" kbps","").toInt()
|
val q = audio.quality!!.replace(" kbps","").toInt()
|
||||||
if (q>bitrate){
|
if (q>bitrate){
|
||||||
bitrate=q
|
bitrate=q
|
||||||
index = i
|
index = i
|
||||||
|
14
app/src/main/java/xyz/btcland/libretube/RetrofitInstance.kt
Normal file
14
app/src/main/java/xyz/btcland/libretube/RetrofitInstance.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package xyz.btcland.libretube
|
||||||
|
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||||
|
|
||||||
|
object RetrofitInstance {
|
||||||
|
val api: PipedApi by lazy {
|
||||||
|
Retrofit.Builder()
|
||||||
|
.baseUrl("https://piped-api.alefvanoon.xyz/")
|
||||||
|
.addConverterFactory(JacksonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
.create(PipedApi::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -10,10 +10,11 @@ import android.widget.TextView
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
|
import xyz.btcland.libretube.obj.StreamItem
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
|
|
||||||
class TrendingAdapter(private val videoFeed: List<Video>): RecyclerView.Adapter<CustomViewHolder>() {
|
class TrendingAdapter(private val videoFeed: List<StreamItem>): RecyclerView.Adapter<CustomViewHolder>() {
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return videoFeed.size
|
return videoFeed.size
|
||||||
}
|
}
|
||||||
@ -40,7 +41,7 @@ class TrendingAdapter(private val videoFeed: List<Video>): RecyclerView.Adapter<
|
|||||||
//intent.putExtra("videoId",trending.url.replace("/watch?v=",""))
|
//intent.putExtra("videoId",trending.url.replace("/watch?v=",""))
|
||||||
//holder.v.context.startActivity(intent)
|
//holder.v.context.startActivity(intent)
|
||||||
var bundle = Bundle()
|
var bundle = Bundle()
|
||||||
bundle.putString("videoId",trending.url.replace("/watch?v=",""))
|
bundle.putString("videoId",trending.url!!.replace("/watch?v=",""))
|
||||||
var frag = PlayerFragment()
|
var frag = PlayerFragment()
|
||||||
frag.arguments = bundle
|
frag.arguments = bundle
|
||||||
val activity = holder.v.context as AppCompatActivity
|
val activity = holder.v.context as AppCompatActivity
|
||||||
@ -57,9 +58,9 @@ class CustomViewHolder(val v: View): RecyclerView.ViewHolder(v){
|
|||||||
init {
|
init {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun videoViews(views: Int): String{
|
fun videoViews(views: Long?): String{
|
||||||
when {
|
when {
|
||||||
views<1000 -> {
|
views!!<1000 -> {
|
||||||
return views.toString()
|
return views.toString()
|
||||||
}
|
}
|
||||||
views in 1000..999999 -> {
|
views in 1000..999999 -> {
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
package xyz.btcland.libretube
|
|
||||||
|
|
||||||
data class Video(
|
|
||||||
val url: String,
|
|
||||||
val title: String,
|
|
||||||
val thumbnail: String,
|
|
||||||
val uploaderName: String,
|
|
||||||
val uploaderUrl:String,
|
|
||||||
val uploaderAvatar:String,
|
|
||||||
val uploadedDate: String,
|
|
||||||
val duration: Int,
|
|
||||||
val views: Int,
|
|
||||||
val uploaderVerified: Boolean
|
|
||||||
)
|
|
@ -1,49 +0,0 @@
|
|||||||
package xyz.btcland.libretube
|
|
||||||
|
|
||||||
data class VideoInPlayer(
|
|
||||||
val title: String,
|
|
||||||
val description: String,
|
|
||||||
val uploadDate: String,
|
|
||||||
val uploader: String,
|
|
||||||
val uploaderUrl: String,
|
|
||||||
val uploaderAvatar: String,
|
|
||||||
val thumbnailUrl: String,
|
|
||||||
val hls: String,
|
|
||||||
val uploaderVerified: Boolean,
|
|
||||||
val duration: Int,
|
|
||||||
val views: Int,
|
|
||||||
val likes: Int,
|
|
||||||
val dislikes: Int,
|
|
||||||
val relatedStreams: List<Video>,
|
|
||||||
val livestream: Boolean,
|
|
||||||
val proxyUrl: String,
|
|
||||||
val audioStreams: List<Stream>,
|
|
||||||
val videoStreams: List<Stream>,
|
|
||||||
val subtitles: List<Subtitle>
|
|
||||||
|
|
||||||
)
|
|
||||||
data class Stream(
|
|
||||||
val url:String,
|
|
||||||
val format:String,
|
|
||||||
val quality:String,
|
|
||||||
val mimeType:String,
|
|
||||||
val codec:String,
|
|
||||||
val videoOnly: Boolean,
|
|
||||||
val bitrate:Int,
|
|
||||||
val initStart:Int,
|
|
||||||
val initEnd:Int,
|
|
||||||
val indexStart:Int,
|
|
||||||
val indexEnd:Int,
|
|
||||||
val width:Int,
|
|
||||||
val height:Int,
|
|
||||||
val fps:Int
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Subtitle(
|
|
||||||
val url:String,
|
|
||||||
val mimeType:String,
|
|
||||||
val name:String,
|
|
||||||
val code:String,
|
|
||||||
val autoGenerated:Boolean
|
|
||||||
)
|
|
23
app/src/main/java/xyz/btcland/libretube/obj/Channel.java
Normal file
23
app/src/main/java/xyz/btcland/libretube/obj/Channel.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package xyz.btcland.libretube.obj;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Channel {
|
||||||
|
public String id, name, avatarUrl, bannerUrl, description, nextpage;
|
||||||
|
public long subscriberCount;
|
||||||
|
public boolean verified;
|
||||||
|
public List<StreamItem> relatedStreams;
|
||||||
|
|
||||||
|
public Channel(String id, String name, String avatarUrl, String bannerUrl, String description, long subscriberCount,
|
||||||
|
boolean verified, String nextpage, List<StreamItem> relatedStreams) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.avatarUrl = avatarUrl;
|
||||||
|
this.bannerUrl = bannerUrl;
|
||||||
|
this.description = description;
|
||||||
|
this.subscriberCount = subscriberCount;
|
||||||
|
this.verified = verified;
|
||||||
|
this.nextpage = nextpage;
|
||||||
|
this.relatedStreams = relatedStreams;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package xyz.btcland.libretube.obj
|
||||||
|
|
||||||
|
data class ChapterSegment(
|
||||||
|
var title: String?,
|
||||||
|
var image: String?,
|
||||||
|
var start: Int?
|
||||||
|
){
|
||||||
|
constructor(): this("","",-1)
|
||||||
|
}
|
22
app/src/main/java/xyz/btcland/libretube/obj/Comment.java
Normal file
22
app/src/main/java/xyz/btcland/libretube/obj/Comment.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package xyz.btcland.libretube.obj;
|
||||||
|
|
||||||
|
public class Comment {
|
||||||
|
public String author, thumbnail, commentId, commentText, commentedTime, commentorUrl, repliesPage;
|
||||||
|
public int likeCount;
|
||||||
|
public boolean hearted, pinned, verified;
|
||||||
|
|
||||||
|
public Comment(String author, String thumbnail, String commentId, String commentText, String commentedTime,
|
||||||
|
String commentorUrl, String repliesPage, int likeCount, boolean hearted, boolean pinned, boolean verified) {
|
||||||
|
this.author = author;
|
||||||
|
this.thumbnail = thumbnail;
|
||||||
|
this.commentId = commentId;
|
||||||
|
this.commentText = commentText;
|
||||||
|
this.commentedTime = commentedTime;
|
||||||
|
this.commentorUrl = commentorUrl;
|
||||||
|
this.repliesPage = repliesPage;
|
||||||
|
this.likeCount = likeCount;
|
||||||
|
this.hearted = hearted;
|
||||||
|
this.pinned = pinned;
|
||||||
|
this.verified = verified;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package xyz.btcland.libretube.obj;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CommentsPage {
|
||||||
|
|
||||||
|
public List<Comment> comments;
|
||||||
|
public String nextpage;
|
||||||
|
public boolean disabled;
|
||||||
|
|
||||||
|
public CommentsPage(List<Comment> comments, String nextpage, boolean disabled) {
|
||||||
|
this.comments = comments;
|
||||||
|
this.nextpage = nextpage;
|
||||||
|
this.disabled = disabled;
|
||||||
|
}
|
||||||
|
}
|
23
app/src/main/java/xyz/btcland/libretube/obj/FeedItem.java
Normal file
23
app/src/main/java/xyz/btcland/libretube/obj/FeedItem.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package xyz.btcland.libretube.obj;
|
||||||
|
|
||||||
|
public class FeedItem {
|
||||||
|
public String url, title, thumbnail, uploaderUrl, uploaderName, uploaderAvatar;
|
||||||
|
|
||||||
|
public long views, duration, uploaded;
|
||||||
|
|
||||||
|
public boolean uploaderVerified;
|
||||||
|
|
||||||
|
public FeedItem(String url, String title, String thumbnail, String uploaderUrl, String uploaderName,
|
||||||
|
String uploaderAvatar, long views, long duration, long uploaded, boolean uploaderVerified) {
|
||||||
|
this.url = url;
|
||||||
|
this.title = title;
|
||||||
|
this.thumbnail = thumbnail;
|
||||||
|
this.uploaderUrl = uploaderUrl;
|
||||||
|
this.uploaderName = uploaderName;
|
||||||
|
this.uploaderAvatar = uploaderAvatar;
|
||||||
|
this.views = views;
|
||||||
|
this.duration = duration;
|
||||||
|
this.uploaded = uploaded;
|
||||||
|
this.uploaderVerified = uploaderVerified;
|
||||||
|
}
|
||||||
|
}
|
20
app/src/main/java/xyz/btcland/libretube/obj/PipedStream.kt
Normal file
20
app/src/main/java/xyz/btcland/libretube/obj/PipedStream.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package xyz.btcland.libretube.obj
|
||||||
|
|
||||||
|
data class PipedStream(
|
||||||
|
var url: String?,
|
||||||
|
var format: String?,
|
||||||
|
var quality: String?,
|
||||||
|
var mimeType: String?,
|
||||||
|
var codec: String?,
|
||||||
|
var videoOnly: Boolean?,
|
||||||
|
var bitrate: Int?,
|
||||||
|
var initStart: Int?,
|
||||||
|
var initEnd: Int?,
|
||||||
|
var indexStart: Int?,
|
||||||
|
var indexEnd: Int?,
|
||||||
|
var width: Int?,
|
||||||
|
var height: Int?,
|
||||||
|
var fps: Int?
|
||||||
|
){
|
||||||
|
constructor(): this("","","","","",null,-1,-1,-1,-1,-1,-1,-1,-1)
|
||||||
|
}
|
23
app/src/main/java/xyz/btcland/libretube/obj/Playlist.java
Normal file
23
app/src/main/java/xyz/btcland/libretube/obj/Playlist.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package xyz.btcland.libretube.obj;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Playlist {
|
||||||
|
|
||||||
|
public String name, thumbnailUrl, bannerUrl, nextpage, uploader, uploaderUrl, uploaderAvatar;
|
||||||
|
public int videos;
|
||||||
|
public List<StreamItem> relatedStreams;
|
||||||
|
|
||||||
|
public Playlist(String name, String thumbnailUrl, String bannerUrl, String nextpage, String uploader,
|
||||||
|
String uploaderUrl, String uploaderAvatar, int videos, List<StreamItem> relatedStreams) {
|
||||||
|
this.name = name;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.bannerUrl = bannerUrl;
|
||||||
|
this.nextpage = nextpage;
|
||||||
|
this.videos = videos;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.uploaderUrl = uploaderUrl;
|
||||||
|
this.uploaderAvatar = uploaderAvatar;
|
||||||
|
this.relatedStreams = relatedStreams;
|
||||||
|
}
|
||||||
|
}
|
16
app/src/main/java/xyz/btcland/libretube/obj/StreamItem.kt
Normal file
16
app/src/main/java/xyz/btcland/libretube/obj/StreamItem.kt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package xyz.btcland.libretube.obj
|
||||||
|
|
||||||
|
data class StreamItem(
|
||||||
|
var url: String?,
|
||||||
|
var title: String?,
|
||||||
|
var thumbnail: String?,
|
||||||
|
var uploaderName: String?,
|
||||||
|
var uploaderUrl: String?,
|
||||||
|
var uploaderAvatar: String?,
|
||||||
|
var uploadedDate: String?,
|
||||||
|
var duration: Long?,
|
||||||
|
var views: Long?,
|
||||||
|
var uploaderVerified: Boolean?
|
||||||
|
){
|
||||||
|
constructor() : this("","","","","","","",0,0,null)
|
||||||
|
}
|
31
app/src/main/java/xyz/btcland/libretube/obj/Streams.kt
Normal file
31
app/src/main/java/xyz/btcland/libretube/obj/Streams.kt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package xyz.btcland.libretube.obj
|
||||||
|
|
||||||
|
import xyz.btcland.libretube.obj.Subtitle
|
||||||
|
|
||||||
|
data class Streams(
|
||||||
|
val title: String?,
|
||||||
|
val description: String?,
|
||||||
|
val uploadDate: String?,
|
||||||
|
val uploader: String?,
|
||||||
|
val uploaderUrl: String?,
|
||||||
|
val uploaderAvatar: String?,
|
||||||
|
val thumbnailUrl: String?,
|
||||||
|
val hls: String?,
|
||||||
|
val dash: String?,
|
||||||
|
val lbryId: String?,
|
||||||
|
val uploaderVerified: Boolean?,
|
||||||
|
val duration: Int?,
|
||||||
|
val views: Int?,
|
||||||
|
val likes: Int?,
|
||||||
|
val dislikes: Int?,
|
||||||
|
val audioStreams: List<PipedStream>?,
|
||||||
|
val videoStreams: List<PipedStream>?,
|
||||||
|
val relatedStreams: List<StreamItem>?,
|
||||||
|
val subtitles: List<Subtitle>?,
|
||||||
|
val livestream: Boolean?,
|
||||||
|
val proxyUrl: String?,
|
||||||
|
val chapters: List<ChapterSegment>?
|
||||||
|
){
|
||||||
|
constructor(): this("","","","","","","","","","",null,-1,-1,-1,-1, emptyList(), emptyList(),
|
||||||
|
emptyList(), emptyList(), null,"", emptyList())
|
||||||
|
}
|
11
app/src/main/java/xyz/btcland/libretube/obj/Subtitle.kt
Normal file
11
app/src/main/java/xyz/btcland/libretube/obj/Subtitle.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package xyz.btcland.libretube.obj
|
||||||
|
|
||||||
|
data class Subtitle(
|
||||||
|
val url: String?,
|
||||||
|
val mimeType: String?,
|
||||||
|
val name: String?,
|
||||||
|
val code: String?,
|
||||||
|
val autoGenerated: Boolean?
|
||||||
|
){
|
||||||
|
constructor(): this("","","","",null)
|
||||||
|
}
|
108
app/src/main/res/layout-land/fragment_player.xml
Normal file
108
app/src/main/res/layout-land/fragment_player.xml
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xyz.btcland.libretube.SingleViewTouchableMotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layoutDescription="@xml/player_scene"
|
||||||
|
tools:context=".PlayerFragment"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:id="@+id/playerMotionLayout"
|
||||||
|
>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/main_container"
|
||||||
|
android:background="@color/white"
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textTest"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text=""
|
||||||
|
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/main_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="350dp"
|
||||||
|
android:background="#FFFFFF"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
<xyz.btcland.libretube.CustomExoPlayerView
|
||||||
|
android:id="@+id/player"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/main_container"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/main_container"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/main_container"
|
||||||
|
app:show_buffering="always"
|
||||||
|
android:background="@color/black"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/close_imageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:alpha="0"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/main_container"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/main_container"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/main_container"
|
||||||
|
android:src="@drawable/ic_close"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/play_imageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:alpha="0"
|
||||||
|
android:src="@drawable/ic_play"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/close_imageView"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/close_imageView"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/close_imageView"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_textView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:alpha="0"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/play_imageView"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/play_imageView"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/player"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/play_imageView"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</xyz.btcland.libretube.SingleViewTouchableMotionLayout>
|
Loading…
x
Reference in New Issue
Block a user