Merge pull request #342 from Bnyro/notification

Notification while playing videos
This commit is contained in:
Bnyro 2022-06-01 08:56:01 +02:00 committed by GitHub
commit 2c175cde7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 477 additions and 418 deletions

View File

@ -2,6 +2,7 @@ package com.github.libretube
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
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
@ -13,6 +14,7 @@ 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.os.Environment
import android.support.v4.media.session.MediaSessionCompat
import android.text.Html import android.text.Html
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
@ -47,6 +49,7 @@ import com.github.libretube.adapters.TrendingAdapter
import com.github.libretube.obj.PipedStream import com.github.libretube.obj.PipedStream
import com.github.libretube.obj.Segment import com.github.libretube.obj.Segment
import com.github.libretube.obj.Segments import com.github.libretube.obj.Segments
import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe import com.github.libretube.obj.Subscribe
import com.github.libretube.util.CronetHelper import com.github.libretube.util.CronetHelper
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
@ -57,10 +60,12 @@ import com.google.android.exoplayer2.MediaItem.fromUri
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.cronet.CronetDataSource import com.google.android.exoplayer2.ext.cronet.CronetDataSource
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.MergingMediaSource import com.google.android.exoplayer2.source.MergingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.ui.StyledPlayerView import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSource import com.google.android.exoplayer2.upstream.DefaultDataSource
@ -105,6 +110,11 @@ class PlayerFragment : Fragment() {
private lateinit var relDownloadVideo: LinearLayout private lateinit var relDownloadVideo: LinearLayout
private lateinit var mediaSession: MediaSessionCompat
private lateinit var mediaSessionConnector: MediaSessionConnector
private lateinit var playerNotification: PlayerNotificationManager
private val notificationId = 1
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
@ -308,6 +318,11 @@ class PlayerFragment : Fragment() {
super.onDestroy() super.onDestroy()
try { try {
exoPlayer.release() exoPlayer.release()
// kill notification
val nManager = context?.getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager
nManager.cancel(notificationId)
} catch (e: Exception) { } catch (e: Exception) {
} }
} }
@ -386,447 +401,480 @@ class PlayerFragment : Fragment() {
} }
} }
} }
initializePlayerView(view, response)
}
}
run()
}
isLoading = false private fun initializePlayerView(view: View, response: Streams) {
var videosNameArray: Array<CharSequence> = arrayOf() isLoading = false
videosNameArray += "HLS" var videosNameArray: Array<CharSequence> = arrayOf()
for (vid in response.videoStreams!!) { videosNameArray += "HLS"
val name = vid.quality + " " + vid.format for (vid in response.videoStreams!!) {
videosNameArray += name 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()
)
}
createExoPlayer(view)
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
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<TextView>(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)
}
}
}
} }
runOnUiThread { response.hls != null -> {
var subtitle = mutableListOf<SubtitleConfiguration>() val mediaItem: MediaItem = MediaItem.Builder()
if (response.subtitles!!.isNotEmpty()) { .setUri(response.hls)
subtitle.add( .setSubtitleConfigurations(subtitle)
SubtitleConfiguration.Builder(response.subtitles[0].url!!.toUri()) .build()
.setMimeType(response.subtitles[0].mimeType!!) // The correct MIME type (required). exoPlayer.setMediaItem(mediaItem)
.setLanguage(response.subtitles[0].code) // The subtitle language (optional). }
.build() 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<TextView>(R.id.quality_text).text = videosNameArray[1]
}
}
// /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 {
// 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<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)
}
exoPlayer.seekTo(lastPosition)
view.findViewById<TextView>(R.id.quality_text).text =
videosNameArray[which]
}
)
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
) )
} }
}
val cronetEngine: CronetEngine = CronetHelper.getCronetEngine() override fun onPlayerStateChanged(
val cronetDataSourceFactory: CronetDataSource.Factory = playWhenReady: Boolean,
CronetDataSource.Factory(cronetEngine, Executors.newCachedThreadPool()) playbackState: Int
) {
val dataSourceFactory = DefaultDataSource.Factory( exoPlayerView.keepScreenOn = !(
requireContext(), playbackState == Player.STATE_IDLE ||
cronetDataSourceFactory playbackState == Player.STATE_ENDED ||
) !playWhenReady
)
val audioAttributes = AudioAttributes.Builder() if (playWhenReady && playbackState == Player.STATE_READY) {
.setUsage(C.USAGE_MEDIA) // media actually playing
.setContentType(C.CONTENT_TYPE_MOVIE) view.findViewById<ImageView>(R.id.play_imageView)
.build() .setImageResource(R.drawable.ic_pause)
} else if (playWhenReady) {
exoPlayer = ExoPlayer.Builder(view.context) // might be idle (plays after prepare()),
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory)) // buffering (plays when data available)
.setSeekBackIncrementMs(5000) // or ended (plays when seek away from end)
.setSeekForwardIncrementMs(5000) view.findViewById<ImageView>(R.id.play_imageView)
.build() .setImageResource(R.drawable.ic_play)
exoPlayerView.setShowSubtitleButton(true) } else {
exoPlayerView.setShowNextButton(false) // player paused in any state
exoPlayerView.setShowPreviousButton(false) view.findViewById<ImageView>(R.id.play_imageView)
exoPlayerView.setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) .setImageResource(R.drawable.ic_play)
// exoPlayerView.controllerShowTimeoutMs = 1500
exoPlayerView.controllerHideOnTouch = true
exoPlayer.setAudioAttributes(audioAttributes, true)
exoPlayerView.player = exoPlayer
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<TextView>(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<TextView>(R.id.quality_text).text = videosNameArray[1]
}
} }
}
})
// /exoPlayer.getMediaItemAt(5) relatedRecView.adapter = TrendingAdapter(
exoPlayer.prepare() response.relatedStreams!!,
exoPlayer.play() childFragmentManager
)
view.findViewById<TextView>(R.id.title_textView).text = response.title val description = response.description!!
view.findViewById<TextView>(R.id.player_title).text = response.title view.findViewById<TextView>(R.id.player_description).text =
view.findViewById<TextView>(R.id.player_description).text = response.description // detect whether the description is html formatted
if (description.contains("<") && description.contains(">")) {
view.findViewById<ImageButton>(R.id.quality_select).setOnClickListener { if (SDK_INT >= Build.VERSION_CODES.N) {
// Dialog for quality selection Html.fromHtml(description, Html.FROM_HTML_MODE_COMPACT)
val builder: MaterialAlertDialogBuilder? = activity?.let { .trim()
MaterialAlertDialogBuilder(it) } else {
} Html.fromHtml(description).trim()
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<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)
}
exoPlayer.seekTo(lastPosition)
view.findViewById<TextView>(R.id.quality_text).text =
videosNameArray[which]
}
)
val dialog = builder.create()
dialog.show()
} }
// Listener for play and pause icon change } else {
exoPlayer.addListener(object : com.google.android.exoplayer2.Player.Listener { description
override fun onIsPlayingChanged(isPlaying: Boolean) { }
if (isPlaying && SponsorBlockSettings.sponsorBlockEnabled) {
exoPlayerView.postDelayed( view.findViewById<TextView>(R.id.player_views_info).text =
this@PlayerFragment::checkForSegments, response.views.formatShort() + " views • " + response.uploadDate
100 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 {
val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireContext())
val instancePref = sharedPreferences.getString(
"instance",
"https://pipedapi.kavin.rocks"
)!!
val instance = "&instance=${URLEncoder.encode(instancePref, "UTF-8")}"
val shareOptions = arrayOf(
getString(R.string.piped),
getString(R.string.instance),
getString(R.string.youtube)
)
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.share))
.setItems(
shareOptions,
DialogInterface.OnClickListener { _, id ->
val url = when (id) {
0 -> "https://piped.kavin.rocks/watch?v=$videoId"
1 -> "https://piped.kavin.rocks/watch?v=$videoId$instance"
2 -> "https://youtu.be/$videoId"
else -> "https://piped.kavin.rocks/watch?v=$videoId"
} }
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_TEXT, url)
intent.type = "text/plain"
startActivity(Intent.createChooser(intent, "Share Url To:"))
} }
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!! .show()
view.findViewById<TextView>(R.id.player_description).text = }
// detect whether the description is html formatted // check if livestream
if (description.contains("<") && description.contains(">")) { if (response.duration!! > 0) {
if (SDK_INT >= Build.VERSION_CODES.N) { // download clicked
Html.fromHtml(description, Html.FROM_HTML_MODE_COMPACT) relDownloadVideo.setOnClickListener {
.trim() if (!IS_DOWNLOAD_RUNNING) {
} else { val mainActivity = activity as MainActivity
Html.fromHtml(description).trim() 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 { } else {
description 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>()
view.findViewById<TextView>(R.id.player_views_info).text = vidName.add("No video")
response.views.formatShort() + " views • " + response.uploadDate var vidUrl = arrayListOf<String>()
view.findViewById<TextView>(R.id.textLike).text = response.likes.formatShort() vidUrl.add("")
val channelImage = view.findViewById<ImageView>(R.id.player_channelImage) for (vid in response.videoStreams) {
Picasso.get().load(response.uploaderAvatar).into(channelImage) val name = vid.quality + " " + vid.format
view.findViewById<TextView>(R.id.player_channelName).text = response.uploader vidName.add(name)
view.findViewById<RelativeLayout>(R.id.player_channel).setOnClickListener { vidUrl.add(vid.url!!)
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")
} }
} var audioName = arrayListOf<String>()
// share button audioName.add("No audio")
view.findViewById<LinearLayout>(R.id.relPlayer_share).setOnClickListener { var audioUrl = arrayListOf<String>()
val sharedPreferences = audioUrl.add("")
PreferenceManager.getDefaultSharedPreferences(requireContext()) for (audio in response.audioStreams!!) {
val instancePref = sharedPreferences.getString( val name = audio.quality + " " + audio.format
"instance", audioName.add(name)
"https://pipedapi.kavin.rocks" audioUrl.add(audio.url!!)
)!! }
val instance = "&instance=${URLEncoder.encode(instancePref, "UTF-8")}" val newFragment = DownloadDialog()
val shareOptions = arrayOf( var bundle = Bundle()
getString(R.string.piped), bundle.putStringArrayList("videoName", vidName)
getString(R.string.instance), bundle.putStringArrayList("videoUrl", vidUrl)
getString(R.string.youtube) bundle.putStringArrayList("audioName", audioName)
) bundle.putStringArrayList("audioUrl", audioUrl)
MaterialAlertDialogBuilder(requireContext()) bundle.putString("videoId", videoId)
.setTitle(getString(R.string.share)) bundle.putInt("duration", response.duration)
.setItems( newFragment.arguments = bundle
shareOptions, newFragment.show(childFragmentManager, "Download")
DialogInterface.OnClickListener { _, id -> } else {
val url = when (id) { Toast.makeText(context, R.string.dlisinprogress, Toast.LENGTH_SHORT)
0 -> "https://piped.kavin.rocks/watch?v=$videoId"
1 -> "https://piped.kavin.rocks/watch?v=$videoId$instance"
2 -> "https://youtu.be/$videoId"
else -> "https://piped.kavin.rocks/watch?v=$videoId"
}
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_TEXT, url)
intent.type = "text/plain"
startActivity(Intent.createChooser(intent, "Share Url To:"))
}
)
.show() .show()
} }
// check if livestream }
if (response.duration!! > 0) { } else {
// download clicked Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
relDownloadVideo.setOnClickListener { }
if (!IS_DOWNLOAD_RUNNING) { if (response.hls != null) {
val mainActivity = activity as MainActivity view.findViewById<LinearLayout>(R.id.relPlayer_vlc).setOnClickListener {
Log.e(TAG, "download button clicked!") exoPlayer.pause()
if (SDK_INT >= Build.VERSION_CODES.R) { try {
Log.d("myz", "" + SDK_INT) val vlcRequestCode = 42
if (!Environment.isExternalStorageManager()) { val uri: Uri = Uri.parse(response.hls)
ActivityCompat.requestPermissions( val vlcIntent = Intent(Intent.ACTION_VIEW)
mainActivity, vlcIntent.setPackage("org.videolan.vlc")
arrayOf( vlcIntent.setDataAndTypeAndNormalize(uri, "video/*")
Manifest.permission.READ_EXTERNAL_STORAGE, vlcIntent.putExtra("title", response.title)
Manifest.permission.MANAGE_EXTERNAL_STORAGE vlcIntent.putExtra("from_start", false)
), vlcIntent.putExtra("position", exoPlayer.currentPosition)
1 startActivityForResult(vlcIntent, vlcRequestCode)
) // permission request code is just an int } catch (e: Exception) {
} Toast.makeText(context, R.string.vlcerror, Toast.LENGTH_SHORT)
} else { .show()
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()
}
}
} }
} }
} }
} }
run() }
private fun createExoPlayer(view: View) {
val cronetEngine: CronetEngine = CronetHelper.getCronetEngine()
val cronetDataSourceFactory: CronetDataSource.Factory =
CronetDataSource.Factory(cronetEngine, Executors.newCachedThreadPool())
val dataSourceFactory = DefaultDataSource.Factory(
requireContext(),
cronetDataSourceFactory
)
val audioAttributes = AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MOVIE)
.build()
exoPlayer = ExoPlayer.Builder(view.context)
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
.setSeekBackIncrementMs(5000)
.setSeekForwardIncrementMs(5000)
.build()
exoPlayer.setAudioAttributes(audioAttributes, true)
setMediaItem(requireContext())
initializePlayerNotification(requireContext())
}
private fun setMediaItem(c: Context) {
mediaSession = MediaSessionCompat(c, this.javaClass.name)
mediaSession.isActive = true
/* might be useful for setting the notification title
mediaSession.setMetadata(MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "")
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, "")
.build()
)
*/
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlayer(exoPlayer)
}
private fun initializePlayerNotification(c: Context) {
playerNotification = PlayerNotificationManager.Builder(c, notificationId, "background_mode")
.build()
playerNotification.setPlayer(exoPlayer)
playerNotification.setMediaSessionToken(mediaSession.sessionToken)
} }
private fun isSubscribed(button: MaterialButton, channel_id: String) { private fun isSubscribed(button: MaterialButton, channel_id: String) {

View File

@ -1,6 +1,7 @@
package com.github.libretube package com.github.libretube
import android.Manifest import android.Manifest
import android.app.NotificationManager
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
@ -389,6 +390,9 @@ class SettingsActivity :
.unregisterOnSharedPreferenceChangeListener(this) .unregisterOnSharedPreferenceChangeListener(this)
if (requireMainActivityRestart) { if (requireMainActivityRestart) {
requireMainActivityRestart = false requireMainActivityRestart = false
// kill player notification
val nManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
nManager.cancelAll()
restartMainActivity(this) restartMainActivity(this)
finishAffinity() finishAffinity()
} else { } else {

View File

@ -1,9 +1,11 @@
package com.github.libretube package com.github.libretube
import android.app.NotificationManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.util.* import java.util.*
@ -79,6 +81,11 @@ fun changeIcon(context: Context, newLogoActivityAlias: String) {
// Needed due to different MainActivity Aliases because of the app icons // Needed due to different MainActivity Aliases because of the app icons
fun restartMainActivity(context: Context) { fun restartMainActivity(context: Context) {
// kill player notification
val nManager = context
.getSystemService(AppCompatActivity.NOTIFICATION_SERVICE) as NotificationManager
nManager.cancelAll()
// restart to MainActivity
val pm: PackageManager = context.packageManager val pm: PackageManager = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName) val intent = pm.getLaunchIntentForPackage(context.packageName)
intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK