diff --git a/app/src/main/java/com/github/libretube/dialogs/DownloadDialog.kt b/app/src/main/java/com/github/libretube/dialogs/DownloadDialog.kt index 99bf915a9..9af8c717a 100644 --- a/app/src/main/java/com/github/libretube/dialogs/DownloadDialog.kt +++ b/app/src/main/java/com/github/libretube/dialogs/DownloadDialog.kt @@ -1,8 +1,12 @@ package com.github.libretube.dialogs +import android.Manifest import android.app.Dialog import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle +import android.os.Environment import android.util.Log import android.util.TypedValue import android.view.View @@ -13,14 +17,18 @@ import android.widget.RadioButton import android.widget.RadioGroup import android.widget.Spinner import android.widget.TextView +import androidx.core.app.ActivityCompat import androidx.core.text.HtmlCompat import androidx.fragment.app.DialogFragment import com.github.libretube.DownloadService +import com.github.libretube.MainActivity import com.github.libretube.R +import com.github.libretube.obj.Streams import com.google.android.material.dialog.MaterialAlertDialogBuilder class DownloadDialog : DialogFragment() { private val TAG = "DownloadDialog" + var streams: Streams = Streams() var vidName = arrayListOf() var vidUrl = arrayListOf() var audioName = arrayListOf() @@ -32,12 +40,62 @@ class DownloadDialog : DialogFragment() { private lateinit var videoId: String override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { - vidName = arguments?.getStringArrayList("videoName") as ArrayList - vidUrl = arguments?.getStringArrayList("videoUrl") as ArrayList - audioName = arguments?.getStringArrayList("audioName") as ArrayList - audioUrl = arguments?.getStringArrayList("audioUrl") as ArrayList - duration = arguments?.getInt("duration")!! - videoId = arguments?.getString("videoId")!! + streams = arguments?.getParcelable("streams")!! + videoId = arguments?.getString("video_id")!! + + val mainActivity = activity as MainActivity + Log.e(TAG, "download button clicked!") + 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() + vidName.add("No video") + var vidUrl = arrayListOf() + vidUrl.add("") + for (vid in streams?.videoStreams!!) { + val name = vid.quality + " " + vid.format + vidName.add(name) + vidUrl.add(vid.url!!) + } + var audioName = arrayListOf() + audioName.add("No audio") + var audioUrl = arrayListOf() + audioUrl.add("") + for (audio in streams?.audioStreams!!) { + val name = audio.quality + " " + audio.format + audioName.add(name) + audioUrl.add(audio.url!!) + } + val builder = MaterialAlertDialogBuilder(it) // Get the layout inflater val inflater = requireActivity().layoutInflater @@ -115,12 +173,4 @@ class DownloadDialog : DialogFragment() { builder.create() } ?: throw IllegalStateException("Activity cannot be null") } - - override fun onDestroy() { - vidName.clear() - vidUrl.clear() - audioUrl.clear() - audioName.clear() - super.onDestroy() - } } diff --git a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt index b0a529f3f..45dc4d5a4 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt @@ -1,19 +1,16 @@ package com.github.libretube.fragments -import android.Manifest import android.annotation.SuppressLint import android.app.NotificationManager import android.content.Context import android.content.DialogInterface import android.content.Intent import android.content.pm.ActivityInfo -import android.content.pm.PackageManager import android.graphics.Rect import android.net.Uri import android.os.Build import android.os.Build.VERSION.SDK_INT import android.os.Bundle -import android.os.Environment import android.support.v4.media.session.MediaSessionCompat import android.text.Html import android.text.TextUtils @@ -34,7 +31,6 @@ import android.widget.TextView import android.widget.Toast import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.app.ActivityCompat import androidx.core.net.toUri import androidx.core.os.bundleOf 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.RetrofitInstance import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem.SubtitleConfiguration @@ -97,8 +94,6 @@ class PlayerFragment : Fragment() { private val TAG = "PlayerFragment" private var videoId: String? = null - private var param2: String? = null - private var lastProgress: Float = 0.toFloat() private var sId: Int = 0 private var eId: Int = 0 private var paused = false @@ -116,7 +111,6 @@ class PlayerFragment : Fragment() { private lateinit var exoPlayerView: StyledPlayerView private lateinit var motionLayout: MotionLayout private lateinit var exoPlayer: ExoPlayer - private lateinit var mediaSource: MediaSource private lateinit var segmentData: Segments private lateinit var relDownloadVideo: LinearLayout @@ -154,6 +148,7 @@ class PlayerFragment : Fragment() { val playerMotionLayout = view.findViewById(R.id.playerMotionLayout) motionLayout = playerMotionLayout exoPlayerView = view.findViewById(R.id.player) + view.findViewById(R.id.player_description).text = videoId playerMotionLayout.addTransitionListener(object : MotionLayout.TransitionListener { override fun onTransitionStarted( @@ -203,6 +198,7 @@ class PlayerFragment : Fragment() { ) { } }) + playerMotionLayout.progress = 1.toFloat() playerMotionLayout.transitionToStart() fetchJson(view) @@ -328,14 +324,6 @@ class PlayerFragment : Fragment() { 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() { super.onDestroy() try { @@ -423,401 +411,349 @@ class PlayerFragment : Fragment() { } } } - initializePlayerView(view, response) + runOnUiThread { + createExoPlayer(view) + prepareExoPlayerView() + setResolutionAndSubtitles(view, response) + exoPlayer.prepare() + exoPlayer.play() + initializePlayerView(view, response) + } } } run() } + private fun prepareExoPlayerView() { + exoPlayerView.setShowSubtitleButton(true) + exoPlayerView.setShowNextButton(false) + exoPlayerView.setShowPreviousButton(false) + exoPlayerView.setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) + // exoPlayerView.controllerShowTimeoutMs = 1500 + exoPlayerView.controllerHideOnTouch = true + exoPlayerView.player = exoPlayer + } + private fun initializePlayerView(view: View, response: Streams) { - isLoading = false - runOnUiThread { - createExoPlayer(view) + view.findViewById(R.id.player_views_info).text = + response.views.formatShort() + " views • " + response.uploadDate + view.findViewById(R.id.textLike).text = response.likes.formatShort() + val channelImage = view.findViewById(R.id.player_channelImage) + Picasso.get().load(response.uploaderAvatar).into(channelImage) + view.findViewById(R.id.player_channelName).text = response.uploader - exoPlayerView.setShowSubtitleButton(true) - exoPlayerView.setShowNextButton(false) - exoPlayerView.setShowPreviousButton(false) - exoPlayerView.setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) - // exoPlayerView.controllerShowTimeoutMs = 1500 - exoPlayerView.controllerHideOnTouch = true - exoPlayerView.player = exoPlayer + view.findViewById(R.id.title_textView).text = response.title + view.findViewById(R.id.player_title).text = response.title + view.findViewById(R.id.player_description).text = response.description - var videosNameArray: Array = arrayOf() - videosNameArray += "HLS" - for (vid in response.videoStreams!!) { - val name = vid.quality + " " + vid.format - videosNameArray += name - } - var subtitle = mutableListOf() - 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 sharedPreferences = - PreferenceManager.getDefaultSharedPreferences(requireContext()) - val defres = sharedPreferences.getString("default_res", "")!! - when { - defres != "" -> { - var foundRes = false - run lit@{ - response.videoStreams.forEachIndexed { index, pipedStream -> - if (pipedStream.quality!!.contains(defres)) { - foundRes = true - val dataSourceFactory: DataSource.Factory = - DefaultHttpDataSource.Factory() - val videoItem: MediaItem = MediaItem.Builder() - .setUri(response.videoStreams[index].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[index].quality == "720p" || - response.videoStreams[index].quality == "1080p" || - response.videoStreams[index].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(R.id.quality_text).text = - videosNameArray[index + 1] - return@lit - } else if (index + 1 == response.videoStreams.size) { - val mediaItem: MediaItem = MediaItem.Builder() - .setUri(response.hls) - .setSubtitleConfigurations(subtitle) - .build() - exoPlayer.setMediaItem(mediaItem) - } - } - } - } - response.hls != null -> { - 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[0].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[0].quality == "720p" || - response.videoStreams[0].quality == "1080p" || - response.videoStreams[0].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(R.id.quality_text).text = videosNameArray[1] - } - } - - // /exoPlayer.getMediaItemAt(5) - exoPlayer.prepare() - exoPlayer.play() - - view.findViewById(R.id.title_textView).text = response.title - view.findViewById(R.id.player_title).text = response.title - view.findViewById(R.id.player_description).text = response.description - - view.findViewById(R.id.quality_select).setOnClickListener { - // Dialog for quality selection - val builder: MaterialAlertDialogBuilder? = activity?.let { - MaterialAlertDialogBuilder(it) - } - var lastPosition = exoPlayer.currentPosition - builder!!.setTitle(R.string.choose_quality_dialog) - .setItems( - videosNameArray, - DialogInterface.OnClickListener { _, which -> - whichQuality = which - if (response.subtitles.isNotEmpty()) { - var subtitle = - mutableListOf() - 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) - } - exoPlayer.seekTo(lastPosition) - view.findViewById(R.id.quality_text).text = - videosNameArray[which] - } + // 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 ) - val dialog = builder.create() - 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 - ) { + override fun onPlayerStateChanged( + playWhenReady: Boolean, + playbackState: Int + ) { - exoPlayerView.keepScreenOn = !( - playbackState == Player.STATE_IDLE || - playbackState == Player.STATE_ENDED || - !playWhenReady - ) + exoPlayerView.keepScreenOn = !( + playbackState == Player.STATE_IDLE || + playbackState == Player.STATE_ENDED || + !playWhenReady + ) - if (playWhenReady && playbackState == Player.STATE_READY) { - // media actually playing - view.findViewById(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(R.id.play_imageView) - .setImageResource(R.drawable.ic_play) - } else { - // player paused in any state - view.findViewById(R.id.play_imageView) - .setImageResource(R.drawable.ic_play) - } - } - }) - - relatedRecView.adapter = TrendingAdapter( - response.relatedStreams!!, - childFragmentManager - ) - val description = response.description!! - view.findViewById(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() - } + if (playWhenReady && playbackState == Player.STATE_READY) { + // media actually playing + view.findViewById(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(R.id.play_imageView) + .setImageResource(R.drawable.ic_play) } else { - description + // player paused in any state + view.findViewById(R.id.play_imageView) + .setImageResource(R.drawable.ic_play) } - - view.findViewById(R.id.player_views_info).text = - response.views.formatShort() + " views • " + response.uploadDate - view.findViewById(R.id.textLike).text = response.likes.formatShort() - val channelImage = view.findViewById(R.id.player_channelImage) - Picasso.get().load(response.uploaderAvatar).into(channelImage) - view.findViewById(R.id.player_channelName).text = response.uploader - view.findViewById(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(R.id.mainMotionLayout).transitionToEnd() - view.findViewById(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(R.id.player_subscribe) - isSubscribed(subButton, channelId!!) - view.findViewById(R.id.save).setOnClickListener { - val newFragment = AddtoPlaylistDialog() + }) + + // share button + view.findViewById(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("videoId", videoId) + bundle.putString("video_id", videoId) + bundle.putParcelable("streams", response) newFragment.arguments = bundle - newFragment.show(childFragmentManager, "AddToPlaylist") + newFragment.show(childFragmentManager, "Download") + } else { + Toast.makeText(context, R.string.dlisinprogress, Toast.LENGTH_SHORT) + .show() } } - // share button - view.findViewById(R.id.relPlayer_share).setOnClickListener { - showShareDialog(requireContext(), videoId!!) + } else { + Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show() + } + + if (response.hls != null) { + view.findViewById(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() + } } - // 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() - vidName.add("No video") - var vidUrl = arrayListOf() - vidUrl.add("") - for (vid in response.videoStreams) { - val name = vid.quality + " " + vid.format - vidName.add(name) - vidUrl.add(vid.url!!) - } - var audioName = arrayListOf() - audioName.add("No audio") - var audioUrl = arrayListOf() - 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() - } + } + + relatedRecView.adapter = TrendingAdapter( + response.relatedStreams!!, + childFragmentManager + ) + val description = response.description!! + view.findViewById(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 { - Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show() + description } - if (response.hls != null) { - view.findViewById(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() + + view.findViewById(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(R.id.mainMotionLayout).transitionToEnd() + view.findViewById(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(R.id.player_subscribe) + isSubscribed(subButton, channelId!!) + view.findViewById(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 = arrayOf() + videosNameArray += "HLS" + for (vid in response.videoStreams!!) { + val name = vid.quality + " " + vid.format + videosNameArray += name + } + var subtitle = mutableListOf() + 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 sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(requireContext()) + val defres = sharedPreferences.getString("default_res", "")!! + when { + defres != "" -> { + run lit@{ + response.videoStreams.forEachIndexed { index, pipedStream -> + if (pipedStream.quality!!.contains(defres)) { + val dataSourceFactory: DataSource.Factory = + DefaultHttpDataSource.Factory() + val videoItem: MediaItem = MediaItem.Builder() + .setUri(response.videoStreams[index].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[index].quality == "720p" || + response.videoStreams[index].quality == "1080p" || + response.videoStreams[index].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(R.id.quality_text).text = + videosNameArray[index + 1] + return@lit + } else if (index + 1 == response.videoStreams.size) { + val mediaItem: MediaItem = MediaItem.Builder() + .setUri(response.hls) + .setSubtitleConfigurations(subtitle) + .build() + exoPlayer.setMediaItem(mediaItem) + } } } } + response.hls != null -> { + 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[0].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[0].quality == "720p" || + response.videoStreams[0].quality == "1080p" || + response.videoStreams[0].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(R.id.quality_text).text = videosNameArray[1] + } + } + + view.findViewById(R.id.quality_select).setOnClickListener { + // Dialog for quality selection + val builder: MaterialAlertDialogBuilder? = activity?.let { + MaterialAlertDialogBuilder(it) + } + var lastPosition = exoPlayer.currentPosition + builder!!.setTitle(R.string.choose_quality_dialog) + .setItems( + videosNameArray, + DialogInterface.OnClickListener { _, which -> + whichQuality = which + if (response.subtitles.isNotEmpty()) { + var subtitle = + mutableListOf() + 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) + } + exoPlayer.seekTo(lastPosition) + view.findViewById(R.id.quality_text).text = + videosNameArray[which] + } + ) + val dialog = builder.create() + dialog.show() } } @@ -831,13 +767,21 @@ class PlayerFragment : Fragment() { cronetDataSourceFactory ) + // handles the audio focus val audioAttributes = AudioAttributes.Builder() .setUsage(C.USAGE_MEDIA) .setContentType(C.CONTENT_TYPE_MOVIE) .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) .setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory)) + .setLoadControl(loadControl) .setSeekBackIncrementMs(5000) .setSeekForwardIncrementMs(5000) .build() @@ -996,6 +940,7 @@ class PlayerFragment : Fragment() { commentsRecView.adapter = commentsAdapter nextPage = commentsResponse.nextpage commentsLoaded = true + isLoading = false } } diff --git a/app/src/main/java/com/github/libretube/obj/Streams.kt b/app/src/main/java/com/github/libretube/obj/Streams.kt index 5837b680d..f127ae160 100644 --- a/app/src/main/java/com/github/libretube/obj/Streams.kt +++ b/app/src/main/java/com/github/libretube/obj/Streams.kt @@ -1,5 +1,7 @@ package com.github.libretube.obj +import android.os.Parcel +import android.os.Parcelable import com.fasterxml.jackson.annotation.JsonIgnoreProperties @JsonIgnoreProperties(ignoreUnknown = true) @@ -26,9 +28,69 @@ data class Streams( val livestream: Boolean?, val proxyUrl: String?, val chapters: List? -) { +) : 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( "", "", "", "", "", "", "", "", "", "", null, -1, -1, -1, -1, emptyList(), 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 { + override fun createFromParcel(parcel: Parcel): Streams { + return Streams(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } }