mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-01-06 01:20:29 +05:30
Merge pull request #354 from Bnyro/structure
Player Fragment Structure + BackBuffer Cache
This commit is contained in:
commit
962cb891a8
@ -1,8 +1,12 @@
|
|||||||
package com.github.libretube.dialogs
|
package com.github.libretube.dialogs
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -13,14 +17,18 @@ import android.widget.RadioButton
|
|||||||
import android.widget.RadioGroup
|
import android.widget.RadioGroup
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.github.libretube.DownloadService
|
import com.github.libretube.DownloadService
|
||||||
|
import com.github.libretube.MainActivity
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.obj.Streams
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
||||||
class DownloadDialog : DialogFragment() {
|
class DownloadDialog : DialogFragment() {
|
||||||
private val TAG = "DownloadDialog"
|
private val TAG = "DownloadDialog"
|
||||||
|
var streams: Streams = Streams()
|
||||||
var vidName = arrayListOf<String>()
|
var vidName = arrayListOf<String>()
|
||||||
var vidUrl = arrayListOf<String>()
|
var vidUrl = arrayListOf<String>()
|
||||||
var audioName = arrayListOf<String>()
|
var audioName = arrayListOf<String>()
|
||||||
@ -32,12 +40,62 @@ class DownloadDialog : DialogFragment() {
|
|||||||
private lateinit var videoId: String
|
private lateinit var videoId: String
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
return activity?.let {
|
return activity?.let {
|
||||||
vidName = arguments?.getStringArrayList("videoName") as ArrayList<String>
|
streams = arguments?.getParcelable("streams")!!
|
||||||
vidUrl = arguments?.getStringArrayList("videoUrl") as ArrayList<String>
|
videoId = arguments?.getString("video_id")!!
|
||||||
audioName = arguments?.getStringArrayList("audioName") as ArrayList<String>
|
|
||||||
audioUrl = arguments?.getStringArrayList("audioUrl") as ArrayList<String>
|
val mainActivity = activity as MainActivity
|
||||||
duration = arguments?.getInt("duration")!!
|
Log.e(TAG, "download button clicked!")
|
||||||
videoId = arguments?.getString("videoId")!!
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
Log.d("myz", "" + Build.VERSION.SDK_INT)
|
||||||
|
if (!Environment.isExternalStorageManager()) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
mainActivity,
|
||||||
|
arrayOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.MANAGE_EXTERNAL_STORAGE
|
||||||
|
),
|
||||||
|
1
|
||||||
|
) // permission request code is just an int
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED ||
|
||||||
|
ActivityCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
mainActivity,
|
||||||
|
arrayOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var vidName = arrayListOf<String>()
|
||||||
|
vidName.add("No video")
|
||||||
|
var vidUrl = arrayListOf<String>()
|
||||||
|
vidUrl.add("")
|
||||||
|
for (vid in streams?.videoStreams!!) {
|
||||||
|
val name = vid.quality + " " + vid.format
|
||||||
|
vidName.add(name)
|
||||||
|
vidUrl.add(vid.url!!)
|
||||||
|
}
|
||||||
|
var audioName = arrayListOf<String>()
|
||||||
|
audioName.add("No audio")
|
||||||
|
var audioUrl = arrayListOf<String>()
|
||||||
|
audioUrl.add("")
|
||||||
|
for (audio in streams?.audioStreams!!) {
|
||||||
|
val name = audio.quality + " " + audio.format
|
||||||
|
audioName.add(name)
|
||||||
|
audioUrl.add(audio.url!!)
|
||||||
|
}
|
||||||
|
|
||||||
val builder = MaterialAlertDialogBuilder(it)
|
val builder = MaterialAlertDialogBuilder(it)
|
||||||
// Get the layout inflater
|
// Get the layout inflater
|
||||||
val inflater = requireActivity().layoutInflater
|
val inflater = requireActivity().layoutInflater
|
||||||
@ -115,12 +173,4 @@ class DownloadDialog : DialogFragment() {
|
|||||||
builder.create()
|
builder.create()
|
||||||
} ?: throw IllegalStateException("Activity cannot be null")
|
} ?: throw IllegalStateException("Activity cannot be null")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
vidName.clear()
|
|
||||||
vidUrl.clear()
|
|
||||||
audioUrl.clear()
|
|
||||||
audioName.clear()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
package com.github.libretube.fragments
|
package com.github.libretube.fragments
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
@ -34,7 +31,6 @@ import android.widget.TextView
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
@ -63,6 +59,7 @@ import com.github.libretube.obj.Subscribe
|
|||||||
import com.github.libretube.util.CronetHelper
|
import com.github.libretube.util.CronetHelper
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.RetrofitInstance
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
|
import com.google.android.exoplayer2.DefaultLoadControl
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.MediaItem.SubtitleConfiguration
|
import com.google.android.exoplayer2.MediaItem.SubtitleConfiguration
|
||||||
@ -97,8 +94,6 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
private val TAG = "PlayerFragment"
|
private val TAG = "PlayerFragment"
|
||||||
private var videoId: String? = null
|
private var videoId: String? = null
|
||||||
private var param2: String? = null
|
|
||||||
private var lastProgress: Float = 0.toFloat()
|
|
||||||
private var sId: Int = 0
|
private var sId: Int = 0
|
||||||
private var eId: Int = 0
|
private var eId: Int = 0
|
||||||
private var paused = false
|
private var paused = false
|
||||||
@ -116,7 +111,6 @@ class PlayerFragment : Fragment() {
|
|||||||
private lateinit var exoPlayerView: StyledPlayerView
|
private lateinit var exoPlayerView: StyledPlayerView
|
||||||
private lateinit var motionLayout: MotionLayout
|
private lateinit var motionLayout: MotionLayout
|
||||||
private lateinit var exoPlayer: ExoPlayer
|
private lateinit var exoPlayer: ExoPlayer
|
||||||
private lateinit var mediaSource: MediaSource
|
|
||||||
private lateinit var segmentData: Segments
|
private lateinit var segmentData: Segments
|
||||||
|
|
||||||
private lateinit var relDownloadVideo: LinearLayout
|
private lateinit var relDownloadVideo: LinearLayout
|
||||||
@ -154,6 +148,7 @@ class PlayerFragment : Fragment() {
|
|||||||
val playerMotionLayout = view.findViewById<MotionLayout>(R.id.playerMotionLayout)
|
val playerMotionLayout = view.findViewById<MotionLayout>(R.id.playerMotionLayout)
|
||||||
motionLayout = playerMotionLayout
|
motionLayout = playerMotionLayout
|
||||||
exoPlayerView = view.findViewById(R.id.player)
|
exoPlayerView = view.findViewById(R.id.player)
|
||||||
|
|
||||||
view.findViewById<TextView>(R.id.player_description).text = videoId
|
view.findViewById<TextView>(R.id.player_description).text = videoId
|
||||||
playerMotionLayout.addTransitionListener(object : MotionLayout.TransitionListener {
|
playerMotionLayout.addTransitionListener(object : MotionLayout.TransitionListener {
|
||||||
override fun onTransitionStarted(
|
override fun onTransitionStarted(
|
||||||
@ -203,6 +198,7 @@ class PlayerFragment : Fragment() {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
playerMotionLayout.progress = 1.toFloat()
|
playerMotionLayout.progress = 1.toFloat()
|
||||||
playerMotionLayout.transitionToStart()
|
playerMotionLayout.transitionToStart()
|
||||||
fetchJson(view)
|
fetchJson(view)
|
||||||
@ -328,14 +324,6 @@ class PlayerFragment : Fragment() {
|
|||||||
GridLayoutManager(view.context, resources.getInteger(R.integer.grid_items))
|
GridLayoutManager(view.context, resources.getInteger(R.integer.grid_items))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
try {
|
|
||||||
// exoPlayer.pause() // breaks background play
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
try {
|
try {
|
||||||
@ -423,17 +411,20 @@ class PlayerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
createExoPlayer(view)
|
||||||
|
prepareExoPlayerView()
|
||||||
|
setResolutionAndSubtitles(view, response)
|
||||||
|
exoPlayer.prepare()
|
||||||
|
exoPlayer.play()
|
||||||
initializePlayerView(view, response)
|
initializePlayerView(view, response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
run()
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializePlayerView(view: View, response: Streams) {
|
private fun prepareExoPlayerView() {
|
||||||
isLoading = false
|
|
||||||
runOnUiThread {
|
|
||||||
createExoPlayer(view)
|
|
||||||
|
|
||||||
exoPlayerView.setShowSubtitleButton(true)
|
exoPlayerView.setShowSubtitleButton(true)
|
||||||
exoPlayerView.setShowNextButton(false)
|
exoPlayerView.setShowNextButton(false)
|
||||||
exoPlayerView.setShowPreviousButton(false)
|
exoPlayerView.setShowPreviousButton(false)
|
||||||
@ -441,7 +432,146 @@ class PlayerFragment : Fragment() {
|
|||||||
// exoPlayerView.controllerShowTimeoutMs = 1500
|
// exoPlayerView.controllerShowTimeoutMs = 1500
|
||||||
exoPlayerView.controllerHideOnTouch = true
|
exoPlayerView.controllerHideOnTouch = true
|
||||||
exoPlayerView.player = exoPlayer
|
exoPlayerView.player = exoPlayer
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializePlayerView(view: View, response: Streams) {
|
||||||
|
view.findViewById<TextView>(R.id.player_views_info).text =
|
||||||
|
response.views.formatShort() + " views • " + response.uploadDate
|
||||||
|
view.findViewById<TextView>(R.id.textLike).text = response.likes.formatShort()
|
||||||
|
val channelImage = view.findViewById<ImageView>(R.id.player_channelImage)
|
||||||
|
Picasso.get().load(response.uploaderAvatar).into(channelImage)
|
||||||
|
view.findViewById<TextView>(R.id.player_channelName).text = response.uploader
|
||||||
|
|
||||||
|
view.findViewById<TextView>(R.id.title_textView).text = response.title
|
||||||
|
view.findViewById<TextView>(R.id.player_title).text = response.title
|
||||||
|
view.findViewById<TextView>(R.id.player_description).text = response.description
|
||||||
|
|
||||||
|
// Listener for play and pause icon change
|
||||||
|
exoPlayer.addListener(object : com.google.android.exoplayer2.Player.Listener {
|
||||||
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
|
if (isPlaying && SponsorBlockSettings.sponsorBlockEnabled) {
|
||||||
|
exoPlayerView.postDelayed(
|
||||||
|
this@PlayerFragment::checkForSegments,
|
||||||
|
100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayerStateChanged(
|
||||||
|
playWhenReady: Boolean,
|
||||||
|
playbackState: Int
|
||||||
|
) {
|
||||||
|
|
||||||
|
exoPlayerView.keepScreenOn = !(
|
||||||
|
playbackState == Player.STATE_IDLE ||
|
||||||
|
playbackState == Player.STATE_ENDED ||
|
||||||
|
!playWhenReady
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// share button
|
||||||
|
view.findViewById<LinearLayout>(R.id.relPlayer_share).setOnClickListener {
|
||||||
|
showShareDialog(requireContext(), videoId!!)
|
||||||
|
}
|
||||||
|
// check if livestream
|
||||||
|
if (response.duration!! > 0) {
|
||||||
|
// download clicked
|
||||||
|
relDownloadVideo.setOnClickListener {
|
||||||
|
if (!IS_DOWNLOAD_RUNNING) {
|
||||||
|
val newFragment = DownloadDialog()
|
||||||
|
var bundle = Bundle()
|
||||||
|
bundle.putString("video_id", videoId)
|
||||||
|
bundle.putParcelable("streams", response)
|
||||||
|
newFragment.arguments = bundle
|
||||||
|
newFragment.show(childFragmentManager, "Download")
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, R.string.dlisinprogress, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hls != null) {
|
||||||
|
view.findViewById<LinearLayout>(R.id.relPlayer_vlc).setOnClickListener {
|
||||||
|
exoPlayer.pause()
|
||||||
|
try {
|
||||||
|
val vlcRequestCode = 42
|
||||||
|
val uri: Uri = Uri.parse(response.hls)
|
||||||
|
val vlcIntent = Intent(Intent.ACTION_VIEW)
|
||||||
|
vlcIntent.setPackage("org.videolan.vlc")
|
||||||
|
vlcIntent.setDataAndTypeAndNormalize(uri, "video/*")
|
||||||
|
vlcIntent.putExtra("title", response.title)
|
||||||
|
vlcIntent.putExtra("from_start", false)
|
||||||
|
vlcIntent.putExtra("position", exoPlayer.currentPosition)
|
||||||
|
startActivityForResult(vlcIntent, vlcRequestCode)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(context, R.string.vlcerror, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relatedRecView.adapter = TrendingAdapter(
|
||||||
|
response.relatedStreams!!,
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
|
val description = response.description!!
|
||||||
|
view.findViewById<TextView>(R.id.player_description).text =
|
||||||
|
// detect whether the description is html formatted
|
||||||
|
if (description.contains("<") && description.contains(">")) {
|
||||||
|
if (SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
Html.fromHtml(description, Html.FROM_HTML_MODE_COMPACT)
|
||||||
|
.trim()
|
||||||
|
} else {
|
||||||
|
Html.fromHtml(description).trim()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
description
|
||||||
|
}
|
||||||
|
|
||||||
|
view.findViewById<RelativeLayout>(R.id.player_channel).setOnClickListener {
|
||||||
|
|
||||||
|
val activity = view.context as MainActivity
|
||||||
|
val bundle = bundleOf("channel_id" to response.uploaderUrl)
|
||||||
|
activity.navController.navigate(R.id.channel, bundle)
|
||||||
|
activity.findViewById<MotionLayout>(R.id.mainMotionLayout).transitionToEnd()
|
||||||
|
view.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
|
||||||
|
}
|
||||||
|
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
|
||||||
|
if (sharedPref?.getString("token", "") != "") {
|
||||||
|
val channelId = response.uploaderUrl?.replace("/channel/", "")
|
||||||
|
val subButton = view.findViewById<MaterialButton>(R.id.player_subscribe)
|
||||||
|
isSubscribed(subButton, channelId!!)
|
||||||
|
view.findViewById<LinearLayout>(R.id.save).setOnClickListener {
|
||||||
|
val newFragment = AddtoPlaylistDialog()
|
||||||
|
var bundle = Bundle()
|
||||||
|
bundle.putString("videoId", videoId)
|
||||||
|
newFragment.arguments = bundle
|
||||||
|
newFragment.show(childFragmentManager, "AddToPlaylist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setResolutionAndSubtitles(view: View, response: Streams) {
|
||||||
var videosNameArray: Array<CharSequence> = arrayOf()
|
var videosNameArray: Array<CharSequence> = arrayOf()
|
||||||
videosNameArray += "HLS"
|
videosNameArray += "HLS"
|
||||||
for (vid in response.videoStreams!!) {
|
for (vid in response.videoStreams!!) {
|
||||||
@ -462,11 +592,9 @@ class PlayerFragment : Fragment() {
|
|||||||
val defres = sharedPreferences.getString("default_res", "")!!
|
val defres = sharedPreferences.getString("default_res", "")!!
|
||||||
when {
|
when {
|
||||||
defres != "" -> {
|
defres != "" -> {
|
||||||
var foundRes = false
|
|
||||||
run lit@{
|
run lit@{
|
||||||
response.videoStreams.forEachIndexed { index, pipedStream ->
|
response.videoStreams.forEachIndexed { index, pipedStream ->
|
||||||
if (pipedStream.quality!!.contains(defres)) {
|
if (pipedStream.quality!!.contains(defres)) {
|
||||||
foundRes = true
|
|
||||||
val dataSourceFactory: DataSource.Factory =
|
val dataSourceFactory: DataSource.Factory =
|
||||||
DefaultHttpDataSource.Factory()
|
DefaultHttpDataSource.Factory()
|
||||||
val videoItem: MediaItem = MediaItem.Builder()
|
val videoItem: MediaItem = MediaItem.Builder()
|
||||||
@ -555,14 +683,6 @@ class PlayerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /exoPlayer.getMediaItemAt(5)
|
|
||||||
exoPlayer.prepare()
|
|
||||||
exoPlayer.play()
|
|
||||||
|
|
||||||
view.findViewById<TextView>(R.id.title_textView).text = response.title
|
|
||||||
view.findViewById<TextView>(R.id.player_title).text = response.title
|
|
||||||
view.findViewById<TextView>(R.id.player_description).text = response.description
|
|
||||||
|
|
||||||
view.findViewById<ImageButton>(R.id.quality_select).setOnClickListener {
|
view.findViewById<ImageButton>(R.id.quality_select).setOnClickListener {
|
||||||
// Dialog for quality selection
|
// Dialog for quality selection
|
||||||
val builder: MaterialAlertDialogBuilder? = activity?.let {
|
val builder: MaterialAlertDialogBuilder? = activity?.let {
|
||||||
@ -635,190 +755,6 @@ class PlayerFragment : Fragment() {
|
|||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
// Listener for play and pause icon change
|
|
||||||
exoPlayer.addListener(object : com.google.android.exoplayer2.Player.Listener {
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
||||||
if (isPlaying && SponsorBlockSettings.sponsorBlockEnabled) {
|
|
||||||
exoPlayerView.postDelayed(
|
|
||||||
this@PlayerFragment::checkForSegments,
|
|
||||||
100
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPlayerStateChanged(
|
|
||||||
playWhenReady: Boolean,
|
|
||||||
playbackState: Int
|
|
||||||
) {
|
|
||||||
|
|
||||||
exoPlayerView.keepScreenOn = !(
|
|
||||||
playbackState == Player.STATE_IDLE ||
|
|
||||||
playbackState == Player.STATE_ENDED ||
|
|
||||||
!playWhenReady
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
relatedRecView.adapter = TrendingAdapter(
|
|
||||||
response.relatedStreams!!,
|
|
||||||
childFragmentManager
|
|
||||||
)
|
|
||||||
val description = response.description!!
|
|
||||||
view.findViewById<TextView>(R.id.player_description).text =
|
|
||||||
// detect whether the description is html formatted
|
|
||||||
if (description.contains("<") && description.contains(">")) {
|
|
||||||
if (SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
Html.fromHtml(description, Html.FROM_HTML_MODE_COMPACT)
|
|
||||||
.trim()
|
|
||||||
} else {
|
|
||||||
Html.fromHtml(description).trim()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
description
|
|
||||||
}
|
|
||||||
|
|
||||||
view.findViewById<TextView>(R.id.player_views_info).text =
|
|
||||||
response.views.formatShort() + " views • " + response.uploadDate
|
|
||||||
view.findViewById<TextView>(R.id.textLike).text = response.likes.formatShort()
|
|
||||||
val channelImage = view.findViewById<ImageView>(R.id.player_channelImage)
|
|
||||||
Picasso.get().load(response.uploaderAvatar).into(channelImage)
|
|
||||||
view.findViewById<TextView>(R.id.player_channelName).text = response.uploader
|
|
||||||
view.findViewById<RelativeLayout>(R.id.player_channel).setOnClickListener {
|
|
||||||
|
|
||||||
val activity = view.context as MainActivity
|
|
||||||
val bundle = bundleOf("channel_id" to response.uploaderUrl)
|
|
||||||
activity.navController.navigate(R.id.channel, bundle)
|
|
||||||
activity.findViewById<MotionLayout>(R.id.mainMotionLayout).transitionToEnd()
|
|
||||||
view.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
|
|
||||||
}
|
|
||||||
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
|
|
||||||
if (sharedPref?.getString("token", "") != "") {
|
|
||||||
val channelId = response.uploaderUrl?.replace("/channel/", "")
|
|
||||||
val subButton = view.findViewById<MaterialButton>(R.id.player_subscribe)
|
|
||||||
isSubscribed(subButton, channelId!!)
|
|
||||||
view.findViewById<LinearLayout>(R.id.save).setOnClickListener {
|
|
||||||
val newFragment = AddtoPlaylistDialog()
|
|
||||||
var bundle = Bundle()
|
|
||||||
bundle.putString("videoId", videoId)
|
|
||||||
newFragment.arguments = bundle
|
|
||||||
newFragment.show(childFragmentManager, "AddToPlaylist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// share button
|
|
||||||
view.findViewById<LinearLayout>(R.id.relPlayer_share).setOnClickListener {
|
|
||||||
showShareDialog(requireContext(), videoId!!)
|
|
||||||
}
|
|
||||||
// check if livestream
|
|
||||||
if (response.duration!! > 0) {
|
|
||||||
// download clicked
|
|
||||||
relDownloadVideo.setOnClickListener {
|
|
||||||
if (!IS_DOWNLOAD_RUNNING) {
|
|
||||||
val mainActivity = activity as MainActivity
|
|
||||||
Log.e(TAG, "download button clicked!")
|
|
||||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
Log.d("myz", "" + SDK_INT)
|
|
||||||
if (!Environment.isExternalStorageManager()) {
|
|
||||||
ActivityCompat.requestPermissions(
|
|
||||||
mainActivity,
|
|
||||||
arrayOf(
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.MANAGE_EXTERNAL_STORAGE
|
|
||||||
),
|
|
||||||
1
|
|
||||||
) // permission request code is just an int
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ActivityCompat.checkSelfPermission(
|
|
||||||
requireContext(),
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
|
||||||
) != PackageManager.PERMISSION_GRANTED ||
|
|
||||||
ActivityCompat.checkSelfPermission(
|
|
||||||
requireContext(),
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
|
||||||
ActivityCompat.requestPermissions(
|
|
||||||
mainActivity,
|
|
||||||
arrayOf(
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var vidName = arrayListOf<String>()
|
|
||||||
vidName.add("No video")
|
|
||||||
var vidUrl = arrayListOf<String>()
|
|
||||||
vidUrl.add("")
|
|
||||||
for (vid in response.videoStreams) {
|
|
||||||
val name = vid.quality + " " + vid.format
|
|
||||||
vidName.add(name)
|
|
||||||
vidUrl.add(vid.url!!)
|
|
||||||
}
|
|
||||||
var audioName = arrayListOf<String>()
|
|
||||||
audioName.add("No audio")
|
|
||||||
var audioUrl = arrayListOf<String>()
|
|
||||||
audioUrl.add("")
|
|
||||||
for (audio in response.audioStreams!!) {
|
|
||||||
val name = audio.quality + " " + audio.format
|
|
||||||
audioName.add(name)
|
|
||||||
audioUrl.add(audio.url!!)
|
|
||||||
}
|
|
||||||
val newFragment = DownloadDialog()
|
|
||||||
var bundle = Bundle()
|
|
||||||
bundle.putStringArrayList("videoName", vidName)
|
|
||||||
bundle.putStringArrayList("videoUrl", vidUrl)
|
|
||||||
bundle.putStringArrayList("audioName", audioName)
|
|
||||||
bundle.putStringArrayList("audioUrl", audioUrl)
|
|
||||||
bundle.putString("videoId", videoId)
|
|
||||||
bundle.putInt("duration", response.duration)
|
|
||||||
newFragment.arguments = bundle
|
|
||||||
newFragment.show(childFragmentManager, "Download")
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, R.string.dlisinprogress, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
if (response.hls != null) {
|
|
||||||
view.findViewById<LinearLayout>(R.id.relPlayer_vlc).setOnClickListener {
|
|
||||||
exoPlayer.pause()
|
|
||||||
try {
|
|
||||||
val vlcRequestCode = 42
|
|
||||||
val uri: Uri = Uri.parse(response.hls)
|
|
||||||
val vlcIntent = Intent(Intent.ACTION_VIEW)
|
|
||||||
vlcIntent.setPackage("org.videolan.vlc")
|
|
||||||
vlcIntent.setDataAndTypeAndNormalize(uri, "video/*")
|
|
||||||
vlcIntent.putExtra("title", response.title)
|
|
||||||
vlcIntent.putExtra("from_start", false)
|
|
||||||
vlcIntent.putExtra("position", exoPlayer.currentPosition)
|
|
||||||
startActivityForResult(vlcIntent, vlcRequestCode)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(context, R.string.vlcerror, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createExoPlayer(view: View) {
|
private fun createExoPlayer(view: View) {
|
||||||
@ -831,13 +767,21 @@ class PlayerFragment : Fragment() {
|
|||||||
cronetDataSourceFactory
|
cronetDataSourceFactory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// handles the audio focus
|
||||||
val audioAttributes = AudioAttributes.Builder()
|
val audioAttributes = AudioAttributes.Builder()
|
||||||
.setUsage(C.USAGE_MEDIA)
|
.setUsage(C.USAGE_MEDIA)
|
||||||
.setContentType(C.CONTENT_TYPE_MOVIE)
|
.setContentType(C.CONTENT_TYPE_MOVIE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
// handles the duration of media to retain in the buffer prior to the current playback position (for fast backward seeking)
|
||||||
|
val loadControl = DefaultLoadControl.Builder()
|
||||||
|
// cache the last three minutes
|
||||||
|
.setBackBuffer(1000 * 60 * 3, true)
|
||||||
|
.build()
|
||||||
|
|
||||||
exoPlayer = ExoPlayer.Builder(view.context)
|
exoPlayer = ExoPlayer.Builder(view.context)
|
||||||
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
|
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
|
||||||
|
.setLoadControl(loadControl)
|
||||||
.setSeekBackIncrementMs(5000)
|
.setSeekBackIncrementMs(5000)
|
||||||
.setSeekForwardIncrementMs(5000)
|
.setSeekForwardIncrementMs(5000)
|
||||||
.build()
|
.build()
|
||||||
@ -996,6 +940,7 @@ class PlayerFragment : Fragment() {
|
|||||||
commentsRecView.adapter = commentsAdapter
|
commentsRecView.adapter = commentsAdapter
|
||||||
nextPage = commentsResponse.nextpage
|
nextPage = commentsResponse.nextpage
|
||||||
commentsLoaded = true
|
commentsLoaded = true
|
||||||
|
isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.libretube.obj
|
package com.github.libretube.obj
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
@ -26,9 +28,69 @@ data class Streams(
|
|||||||
val livestream: Boolean?,
|
val livestream: Boolean?,
|
||||||
val proxyUrl: String?,
|
val proxyUrl: String?,
|
||||||
val chapters: List<ChapterSegment>?
|
val chapters: List<ChapterSegment>?
|
||||||
) {
|
) : Parcelable {
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
|
||||||
|
parcel.readValue(Int::class.java.classLoader) as? Int,
|
||||||
|
parcel.readValue(Long::class.java.classLoader) as? Long,
|
||||||
|
parcel.readValue(Long::class.java.classLoader) as? Long,
|
||||||
|
parcel.readValue(Int::class.java.classLoader) as? Int,
|
||||||
|
TODO("audioStreams"),
|
||||||
|
TODO("videoStreams"),
|
||||||
|
TODO("relatedStreams"),
|
||||||
|
TODO("subtitles"),
|
||||||
|
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
|
||||||
|
parcel.readString(),
|
||||||
|
TODO("chapters")
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
constructor() : this(
|
constructor() : this(
|
||||||
"", "", "", "", "", "", "", "", "", "", null, -1, -1, -1, -1, emptyList(), emptyList(),
|
"", "", "", "", "", "", "", "", "", "", null, -1, -1, -1, -1, emptyList(), emptyList(),
|
||||||
emptyList(), emptyList(), null, "", emptyList()
|
emptyList(), emptyList(), null, "", emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(title)
|
||||||
|
parcel.writeString(description)
|
||||||
|
parcel.writeString(uploadDate)
|
||||||
|
parcel.writeString(uploader)
|
||||||
|
parcel.writeString(uploaderUrl)
|
||||||
|
parcel.writeString(uploaderAvatar)
|
||||||
|
parcel.writeString(thumbnailUrl)
|
||||||
|
parcel.writeString(hls)
|
||||||
|
parcel.writeString(dash)
|
||||||
|
parcel.writeString(lbryId)
|
||||||
|
parcel.writeValue(uploaderVerified)
|
||||||
|
parcel.writeValue(duration)
|
||||||
|
parcel.writeValue(views)
|
||||||
|
parcel.writeValue(likes)
|
||||||
|
parcel.writeValue(dislikes)
|
||||||
|
parcel.writeValue(livestream)
|
||||||
|
parcel.writeString(proxyUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<Streams> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): Streams {
|
||||||
|
return Streams(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<Streams?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user