refactor: don't recreate player on orientation change

This commit is contained in:
Bnyro 2024-02-18 16:53:34 +01:00
parent 206479ce31
commit 0a4ccae79a
5 changed files with 216 additions and 149 deletions

View File

@ -146,8 +146,10 @@ class OfflinePlayerService : LifecycleService() {
} }
override fun onDestroy() { override fun onDestroy() {
nowPlayingNotification?.destroySelfAndPlayer() nowPlayingNotification?.destroySelf()
player?.stop()
player?.release()
player = null player = null
nowPlayingNotification = null nowPlayingNotification = null

View File

@ -387,7 +387,10 @@ class OnlinePlayerService : LifecycleService() {
// reset the playing queue // reset the playing queue
PlayingQueue.resetToDefaults() PlayingQueue.resetToDefaults()
if (this::nowPlayingNotification.isInitialized) nowPlayingNotification.destroySelfAndPlayer() if (this::nowPlayingNotification.isInitialized) nowPlayingNotification.destroySelf()
player?.stop()
player?.release()
// called when the user pressed stop in the notification // called when the user pressed stop in the notification
// stop the service from being in the foreground and remove the notification // stop the service from being in the foreground and remove the notification

View File

@ -50,10 +50,7 @@ import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.CronetHelper import com.github.libretube.api.CronetHelper
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.ChapterSegment import com.github.libretube.api.obj.ChapterSegment
import com.github.libretube.api.obj.Message
import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.Streams import com.github.libretube.api.obj.Streams
import com.github.libretube.api.obj.Subtitle import com.github.libretube.api.obj.Subtitle
@ -82,7 +79,6 @@ import com.github.libretube.helpers.IntentHelper
import com.github.libretube.helpers.NavBarHelper import com.github.libretube.helpers.NavBarHelper
import com.github.libretube.helpers.NavigationHelper import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PlayerHelper.SPONSOR_HIGHLIGHT_CATEGORY
import com.github.libretube.helpers.PlayerHelper.checkForSegments import com.github.libretube.helpers.PlayerHelper.checkForSegments
import com.github.libretube.helpers.PlayerHelper.getVideoStats import com.github.libretube.helpers.PlayerHelper.getVideoStats
import com.github.libretube.helpers.PlayerHelper.isInSegment import com.github.libretube.helpers.PlayerHelper.isInSegment
@ -116,14 +112,10 @@ import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.TextUtils import com.github.libretube.util.TextUtils
import com.github.libretube.util.TextUtils.toTimeInSeconds import com.github.libretube.util.TextUtils.toTimeInSeconds
import com.github.libretube.util.YoutubeHlsPlaylistParser import com.github.libretube.util.YoutubeHlsPlaylistParser
import com.github.libretube.util.deArrow
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import retrofit2.HttpException
import java.io.IOException
import java.util.* import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.abs import kotlin.math.abs
@ -140,18 +132,16 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
private val viewModel: PlayerViewModel by activityViewModels() private val viewModel: PlayerViewModel by activityViewModels()
private val commentsViewModel: CommentsViewModel by activityViewModels() private val commentsViewModel: CommentsViewModel by activityViewModels()
/** // Video information passed by the intent
* Video information passed by the intent
*/
private lateinit var videoId: String private lateinit var videoId: String
private var playlistId: String? = null private var playlistId: String? = null
private var channelId: String? = null private var channelId: String? = null
private var keepQueue = false private var keepQueue = false
private var timeStamp = 0L private var timeStamp = 0L
/** // data and objects stored for the player
* Video information fetched at runtime private lateinit var exoPlayer: ExoPlayer
*/ private lateinit var trackSelector: DefaultTrackSelector
private lateinit var streams: Streams private lateinit var streams: Streams
// progress state of the motion layout transition // progress state of the motion layout transition
@ -159,11 +149,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
private var transitionEndId = 0 private var transitionEndId = 0
private var isTransitioning = true private var isTransitioning = true
// data and objects stored for the player
private lateinit var exoPlayer: ExoPlayer
private lateinit var trackSelector: DefaultTrackSelector
private var currentSubtitle = Subtitle(code = PlayerHelper.defaultSubtitleCode)
// if null, it's been set to automatic // if null, it's been set to automatic
private var fullscreenResolution: Int? = null private var fullscreenResolution: Int? = null
@ -176,13 +161,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
Executors.newCachedThreadPool() Executors.newCachedThreadPool()
) )
// for the player notification
private lateinit var nowPlayingNotification: NowPlayingNotification
// SponsorBlock // SponsorBlock
private var segments = listOf<Segment>()
private var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled private var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled
private var sponsorBlockConfig = PlayerHelper.getSponsorBlockCategories()
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
@ -311,9 +291,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
override fun onPlaybackStateChanged(playbackState: Int) { override fun onPlaybackStateChanged(playbackState: Int) {
saveWatchPosition() saveWatchPosition()
if (playbackState == Player.STATE_READY) {
}
// set the playback speed to one if having reached the end of a livestream // set the playback speed to one if having reached the end of a livestream
if (playbackState == Player.STATE_BUFFERING && binding.player.isLive && if (playbackState == Player.STATE_BUFFERING && binding.player.isLive &&
exoPlayer.duration - exoPlayer.currentPosition < 700 exoPlayer.duration - exoPlayer.currentPosition < 700
@ -468,7 +445,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
if (currentId == transitionStartId) { if (currentId == transitionStartId) {
viewModel.isMiniPlayerVisible.value = false viewModel.isMiniPlayerVisible.value = false
// re-enable captions // re-enable captions
updateCurrentSubtitle(currentSubtitle) updateCurrentSubtitle(viewModel.currentSubtitle)
binding.player.useController = true binding.player.useController = true
commentsViewModel.setCommentSheetExpand(true) commentsViewModel.setCommentSheetExpand(true)
mainMotionLayout.progress = 0F mainMotionLayout.progress = 0F
@ -608,7 +585,12 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
val hlsStream = withContext(Dispatchers.IO) { val hlsStream = withContext(Dispatchers.IO) {
ProxyHelper.unwrapStreamUrl(streams.hls!!).toUri() ProxyHelper.unwrapStreamUrl(streams.hls!!).toUri()
} }
IntentHelper.openWithExternalPlayer(context, hlsStream, streams.title, streams.uploader) IntentHelper.openWithExternalPlayer(
context,
hlsStream,
streams.title,
streams.uploader
)
} }
} }
@ -795,7 +777,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
if (closedVideo) { if (closedVideo) {
closedVideo = false closedVideo = false
nowPlayingNotification.refreshNotification() viewModel.nowPlayingNotification?.refreshNotification()
} }
// re-enable and load video stream // re-enable and load video stream
@ -810,7 +792,14 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
super.onDestroy() super.onDestroy()
if (this::exoPlayer.isInitialized) { if (this::exoPlayer.isInitialized) {
if (viewModel.player == exoPlayer) viewModel.player = null exoPlayer.removeListener(playerListener)
// the player could also be a different instance because a new player fragment
// got created in the meanwhile
if (!viewModel.shouldUseExistingPlayer && viewModel.player == exoPlayer) {
viewModel.player = null
viewModel.trackSelector = null
}
exoPlayer.pause() exoPlayer.pause()
@ -839,7 +828,13 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
try { try {
saveWatchPosition() saveWatchPosition()
nowPlayingNotification.destroySelfAndPlayer() viewModel.nowPlayingNotification?.destroySelf()
viewModel.nowPlayingNotification = null
if (!viewModel.shouldUseExistingPlayer) {
exoPlayer.stop()
exoPlayer.release()
}
(context as MainActivity).requestOrientationChange() (context as MainActivity).requestOrientationChange()
} catch (e: Exception) { } catch (e: Exception) {
@ -867,9 +862,9 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
if (!exoPlayer.isPlaying || !PlayerHelper.sponsorBlockEnabled) return if (!exoPlayer.isPlaying || !PlayerHelper.sponsorBlockEnabled) return
handler.postDelayed(this::checkForSegments, 100) handler.postDelayed(this::checkForSegments, 100)
if (!sponsorBlockEnabled || segments.isEmpty()) return if (!sponsorBlockEnabled || viewModel.segments.isEmpty()) return
exoPlayer.checkForSegments(requireContext(), segments, sponsorBlockConfig) exoPlayer.checkForSegments(requireContext(), viewModel.segments, viewModel.sponsorBlockConfig)
?.let { segment -> ?.let { segment ->
if (viewModel.isMiniPlayerVisible.value == true) return@let if (viewModel.isMiniPlayerVisible.value == true) return@let
binding.sbSkipBtn.isVisible = true binding.sbSkipBtn.isVisible = true
@ -879,7 +874,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
} }
return return
} }
if (!exoPlayer.isInSegment(segments)) binding.sbSkipBtn.isGone = true if (!exoPlayer.isInSegment(viewModel.segments)) binding.sbSkipBtn.isGone = true
} }
private fun playVideo() { private fun playVideo() {
@ -890,22 +885,16 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
// reset the comments to become reloaded later // reset the comments to become reloaded later
commentsViewModel.reset() commentsViewModel.reset()
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.Main) {
streams = try { viewModel.fetchVideoInfo(requireContext(), videoId).let { (streams, errorMessage) ->
RetrofitInstance.api.getStreams(videoId).apply { if (errorMessage != null) {
relatedStreams = relatedStreams.deArrow()
}
} catch (e: IOException) {
context?.toastFromMainDispatcher(R.string.unknown_error, Toast.LENGTH_LONG)
return@launch
} catch (e: HttpException) {
val errorMessage = e.response()?.errorBody()?.string()?.runCatching {
JsonHelper.json.decodeFromString<Message>(this).message
}?.getOrNull() ?: context?.getString(R.string.server_error).orEmpty()
context?.toastFromMainDispatcher(errorMessage, Toast.LENGTH_LONG) context?.toastFromMainDispatcher(errorMessage, Toast.LENGTH_LONG)
return@launch return@launch
} }
this@PlayerFragment.streams = streams!!
}
val isFirstVideo = PlayingQueue.isEmpty() val isFirstVideo = PlayingQueue.isEmpty()
if (isFirstVideo) { if (isFirstVideo) {
PlayingQueue.updateQueue(streams.toStreamItem(videoId), playlistId, channelId) PlayingQueue.updateQueue(streams.toStreamItem(videoId), playlistId, channelId)
@ -926,12 +915,11 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
streamItem.url?.toID()?.let { playNextVideo(it) } streamItem.url?.toID()?.let { playNextVideo(it) }
} }
withContext(Dispatchers.Main) {
// hide the button to skip SponsorBlock segments manually // hide the button to skip SponsorBlock segments manually
binding.sbSkipBtn.isGone = true binding.sbSkipBtn.isGone = true
// set media sources for the player // set media sources for the player
initStreamSources() if (!viewModel.shouldUseExistingPlayer) initStreamSources()
if (PreferenceHelper.getBoolean(PreferenceKeys.AUTO_FULLSCREEN_SHORTS, false) && if (PreferenceHelper.getBoolean(PreferenceKeys.AUTO_FULLSCREEN_SHORTS, false) &&
isShort && binding.playerMotionLayout.progress == 0f isShort && binding.playerMotionLayout.progress == 0f
@ -966,10 +954,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
// show the player notification // show the player notification
initializePlayerNotification() initializePlayerNotification()
// Since the highlight is also a chapter, we need to fetch the other segments
// first
fetchSponsorBlockSegments()
// enable the chapters dialog in the player // enable the chapters dialog in the player
playerBinding.chapterName.setOnClickListener { playerBinding.chapterName.setOnClickListener {
updateMaxSheetHeight() updateMaxSheetHeight()
@ -986,38 +970,35 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
setCurrentChapterName() setCurrentChapterName()
fetchSponsorBlockSegments()
if (streams.category == Streams.categoryMusic) { if (streams.category == Streams.categoryMusic) {
exoPlayer.setPlaybackSpeed(1f) exoPlayer.setPlaybackSpeed(1f)
} }
}
viewModel.shouldUseExistingPlayer = false
} }
} }
/** private suspend fun fetchSponsorBlockSegments() {
* fetch the segments for SponsorBlock viewModel.sponsorBlockConfig = PlayerHelper.getSponsorBlockCategories()
*/
private fun fetchSponsorBlockSegments() { // Since the highlight is also a chapter, we need to fetch the other segments
lifecycleScope.launch(Dispatchers.IO) { // first
runCatching { viewModel.fetchSponsorBlockSegments(videoId)
if (sponsorBlockConfig.isEmpty()) return@launch
segments = if (viewModel.segments.isEmpty()) return
RetrofitInstance.api.getSegments(
videoId,
JsonHelper.json.encodeToString(sponsorBlockConfig.keys)
).segments
if (segments.isEmpty()) return@launch
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
playerBinding.exoProgress.setSegments(segments) playerBinding.exoProgress.setSegments(viewModel.segments)
playerBinding.sbToggle.isVisible = true playerBinding.sbToggle.isVisible = true
updateDisplayedDuration() updateDisplayedDuration()
} }
segments.firstOrNull { it.category == SPONSOR_HIGHLIGHT_CATEGORY }?.let { viewModel.segments.firstOrNull { it.category == PlayerHelper.SPONSOR_HIGHLIGHT_CATEGORY }
?.let {
initializeHighlight(it) initializeHighlight(it)
} }
} }
}
}
// used for autoplay and skipping to next video // used for autoplay and skipping to next video
private fun playNextVideo(nextId: String? = null) { private fun playNextVideo(nextId: String? = null) {
@ -1151,10 +1132,12 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
* Update the displayed duration of the video * Update the displayed duration of the video
*/ */
private fun updateDisplayedDuration() { private fun updateDisplayedDuration() {
val duration = exoPlayer.duration / 1000 if (!this::streams.isInitialized || streams.livestream || _binding == null) return
if (duration < 0 || streams.livestream || _binding == null) return
val durationWithoutSegments = duration - segments.sumOf { val duration = exoPlayer.duration / 1000
if (duration < 0) return
val durationWithoutSegments = duration - viewModel.segments.sumOf {
val (start, end) = it.segmentStartAndEnd val (start, end) = it.segmentStartAndEnd
end.toDouble() - start.toDouble() end.toDouble() - start.toDouble()
}.toLong() }.toLong()
@ -1276,7 +1259,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
} }
// set the default subtitle if available // set the default subtitle if available
updateCurrentSubtitle(currentSubtitle) updateCurrentSubtitle(viewModel.currentSubtitle)
// set media source and resolution in the beginning // set media source and resolution in the beginning
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
@ -1384,9 +1367,15 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
} }
private fun createExoPlayer() { private fun createExoPlayer() {
// control for the track sources like subtitles and audio source viewModel.keepOrCreatePlayer(requireContext()).let { (player, trackSelector) ->
trackSelector = DefaultTrackSelector(requireContext()) this.exoPlayer = player
this.trackSelector = trackSelector
}
exoPlayer.setWakeMode(C.WAKE_MODE_NETWORK)
exoPlayer.addListener(playerListener)
// control for the track sources like subtitles and audio source
trackSelector.updateParameters { trackSelector.updateParameters {
val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs
if (enabledVideoCodecs != "all") { if (enabledVideoCodecs != "all") {
@ -1399,21 +1388,15 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
this.setPreferredVideoMimeType(mimeType) this.setPreferredVideoMimeType(mimeType)
} }
} }
PlayerHelper.applyPreferredAudioQuality(requireContext(), trackSelector) PlayerHelper.applyPreferredAudioQuality(requireContext(), trackSelector)
exoPlayer = PlayerHelper.createPlayer(requireContext(), trackSelector, false)
exoPlayer.setWakeMode(C.WAKE_MODE_NETWORK)
exoPlayer.addListener(playerListener)
viewModel.player = exoPlayer
} }
/** /**
* show the [NowPlayingNotification] for the current video * show the [NowPlayingNotification] for the current video
*/ */
private fun initializePlayerNotification() { private fun initializePlayerNotification() {
if (!this::nowPlayingNotification.isInitialized) { if (viewModel.nowPlayingNotification == null) {
nowPlayingNotification = NowPlayingNotification( viewModel.nowPlayingNotification = NowPlayingNotification(
requireContext(), requireContext(),
exoPlayer, exoPlayer,
NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_ONLINE NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_ONLINE
@ -1424,7 +1407,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
streams.uploader, streams.uploader,
streams.thumbnailUrl streams.thumbnailUrl
) )
nowPlayingNotification.updatePlayerNotification(videoId, playerNotificationData) viewModel.nowPlayingNotification?.updatePlayerNotification(videoId, playerNotificationData)
} }
/** /**
@ -1462,7 +1445,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
) { index -> ) { index ->
val subtitle = subtitles.getOrNull(index) ?: return@setSimpleItems val subtitle = subtitles.getOrNull(index) ?: return@setSimpleItems
updateCurrentSubtitle(subtitle) updateCurrentSubtitle(subtitle)
this.currentSubtitle = subtitle viewModel.currentSubtitle = subtitle
} }
.show(childFragmentManager) .show(childFragmentManager)
} }
@ -1571,11 +1554,11 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
// pause the video and keep the app alive // pause the video and keep the app alive
if (lifecycle.currentState == Lifecycle.State.CREATED) { if (lifecycle.currentState == Lifecycle.State.CREATED) {
exoPlayer.pause() exoPlayer.pause()
nowPlayingNotification.cancelNotification() viewModel.nowPlayingNotification?.cancelNotification()
closedVideo = true closedVideo = true
} }
updateCurrentSubtitle(currentSubtitle) updateCurrentSubtitle(viewModel.currentSubtitle)
// unset fullscreen if it's not been enabled before the start of PiP // unset fullscreen if it's not been enabled before the start of PiP
if (viewModel.isFullscreen.value != true) { if (viewModel.isFullscreen.value != true) {
@ -1670,10 +1653,13 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
val orientation = resources.configuration.orientation val orientation = resources.configuration.orientation
if (viewModel.isFullscreen.value != true && orientation != playerLayoutOrientation) { if (viewModel.isFullscreen.value != true && orientation != playerLayoutOrientation) {
// remember the current position before recreating the activity
if (this::exoPlayer.isInitialized) { if (this::exoPlayer.isInitialized) {
arguments?.putLong(IntentData.timeStamp, exoPlayer.currentPosition / 1000) arguments?.putLong(IntentData.timeStamp, exoPlayer.currentPosition / 1000)
} }
playerLayoutOrientation = orientation playerLayoutOrientation = orientation
viewModel.shouldUseExistingPlayer = true
activity?.recreate() activity?.recreate()
} }
} }

View File

@ -1,12 +1,46 @@
package com.github.libretube.ui.models package com.github.libretube.ui.models
import android.content.Context
import androidx.annotation.OptIn
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import com.github.libretube.R
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.ChapterSegment import com.github.libretube.api.obj.ChapterSegment
import com.github.libretube.api.obj.Message
import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.Streams
import com.github.libretube.api.obj.Subtitle
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.util.NowPlayingNotification
import com.github.libretube.util.deArrow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import retrofit2.HttpException
import java.io.IOException
class PlayerViewModel : ViewModel() { class PlayerViewModel : ViewModel() {
var player: ExoPlayer? = null var player: ExoPlayer? = null
var trackSelector: DefaultTrackSelector? = null
// data to remember for recovery on orientation change
private var streamsInfo: Streams? = null
var nowPlayingNotification: NowPlayingNotification? = null
var segments = listOf<Segment>()
var currentSubtitle = Subtitle(code = PlayerHelper.defaultSubtitleCode)
var sponsorBlockConfig = PlayerHelper.getSponsorBlockCategories()
/**
* Whether to continue using the current player
* Set to true if the activity will be recreated due to an orientation change
*/
var shouldUseExistingPlayer = false
val isMiniPlayerVisible = MutableLiveData(false) val isMiniPlayerVisible = MutableLiveData(false)
val isFullscreen = MutableLiveData(false) val isFullscreen = MutableLiveData(false)
@ -16,4 +50,49 @@ class PlayerViewModel : ViewModel() {
val chaptersLiveData = MutableLiveData<List<ChapterSegment>>() val chaptersLiveData = MutableLiveData<List<ChapterSegment>>()
val chapters get() = chaptersLiveData.value.orEmpty() val chapters get() = chaptersLiveData.value.orEmpty()
/**
* @return pair of the stream info and the error message if the request was not successful
*/
suspend fun fetchVideoInfo(context: Context, videoId: String): Pair<Streams?, String?> =
withContext(Dispatchers.IO) {
if (shouldUseExistingPlayer && streamsInfo != null) return@withContext streamsInfo to null
streamsInfo = try {
RetrofitInstance.api.getStreams(videoId).apply {
relatedStreams = relatedStreams.deArrow()
}
} catch (e: IOException) {
return@withContext null to context.getString(R.string.unknown_error)
} catch (e: HttpException) {
val errorMessage = e.response()?.errorBody()?.string()?.runCatching {
JsonHelper.json.decodeFromString<Message>(this).message
}?.getOrNull() ?: context.getString(R.string.server_error)
return@withContext null to errorMessage
}
return@withContext streamsInfo to null
}
suspend fun fetchSponsorBlockSegments(videoId: String) = withContext(Dispatchers.IO) {
if (sponsorBlockConfig.isEmpty() || shouldUseExistingPlayer) return@withContext
runCatching {
segments =
RetrofitInstance.api.getSegments(
videoId,
JsonHelper.json.encodeToString(sponsorBlockConfig.keys)
).segments
}
}
@OptIn(UnstableApi::class)
fun keepOrCreatePlayer(context: Context): Pair<ExoPlayer, DefaultTrackSelector> {
if (!shouldUseExistingPlayer || player == null || trackSelector == null) {
this.trackSelector = DefaultTrackSelector(context)
this.player = PlayerHelper.createPlayer(context, trackSelector!!, false)
}
return this.player!! to this.trackSelector!!
}
} }

View File

@ -391,12 +391,9 @@ class NowPlayingNotification(
/** /**
* Destroy the [NowPlayingNotification] * Destroy the [NowPlayingNotification]
*/ */
fun destroySelfAndPlayer() { fun destroySelf() {
mediaSession.release() mediaSession.release()
player.stop()
player.release()
runCatching { runCatching {
context.unregisterReceiver(notificationActionReceiver) context.unregisterReceiver(notificationActionReceiver)
} }