Subject: [PATCH] Shared exoplayer --- Index: app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt --- a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt (revision 6383ee1beedfa5650eb0573242f9338767e6c08c) +++ b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt (date 1720797775408) @@ -66,7 +66,7 @@ private var nowPlayingNotification: NowPlayingNotification? = null private lateinit var playerBinding: ExoStyledPlayerControlViewBinding - private val playerViewModel: PlayerViewModel by viewModels() + private val playerViewModel by viewModels { PlayerViewModel.Factory } private val chaptersViewModel: ChaptersViewModel by viewModels() private val watchPositionTimer = PauseableTimer( @@ -164,7 +164,6 @@ player = PlayerHelper.createPlayer(this, trackSelector, false) player.setWakeMode(C.WAKE_MODE_LOCAL) player.addListener(playerListener) - playerViewModel.player = player playerView = binding.player playerView.setShowSubtitleButton(true) @@ -307,7 +306,6 @@ nowPlayingNotification = null watchPositionTimer.destroy() - playerViewModel.player = null runCatching { player.stop() player.release() Index: app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt b/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt --- a/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt (revision 6383ee1beedfa5650eb0573242f9338767e6c08c) +++ b/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt (date 1720797997358) @@ -1,9 +1,11 @@ package com.github.libretube.ui.models import android.content.Context -import androidx.annotation.OptIn import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector @@ -24,9 +26,10 @@ import java.io.IOException @UnstableApi -class PlayerViewModel : ViewModel() { - var player: ExoPlayer? = null - var trackSelector: DefaultTrackSelector? = null +class PlayerViewModel( + val player: ExoPlayer, + val trackSelector: DefaultTrackSelector, +) : ViewModel() { // data to remember for recovery on orientation change private var streamsInfo: Streams? = null @@ -82,13 +85,24 @@ } } - @OptIn(UnstableApi::class) - fun keepOrCreatePlayer(context: Context): Pair { - if (!isOrientationChangeInProgress || player == null || trackSelector == null) { - this.trackSelector = DefaultTrackSelector(context) - this.player = PlayerHelper.createPlayer(context, trackSelector!!, false) + companion object { + val Factory = viewModelFactory { + initializer { + val context = this[APPLICATION_KEY]!! + val trackSelector = DefaultTrackSelector(context) + PlayerViewModel( + player = PlayerHelper.createPlayer(context, trackSelector, false), + trackSelector = trackSelector, + ) + } } + } - return this.player!! to this.trackSelector!! + override fun onCleared() { + super.onCleared() + player.pause() + + player.stop() + player.release() } } Index: app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt --- a/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt (revision 6383ee1beedfa5650eb0573242f9338767e6c08c) +++ b/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt (date 1720797829024) @@ -23,6 +23,7 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope +import androidx.media3.common.util.UnstableApi import com.github.libretube.R import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.IntentData @@ -55,13 +56,14 @@ import kotlinx.coroutines.launch import kotlin.math.abs +@UnstableApi class AudioPlayerFragment : Fragment(), AudioPlayerOptions { private var _binding: FragmentAudioPlayerBinding? = null val binding get() = _binding!! private lateinit var audioHelper: AudioHelper private val mainActivity get() = context as MainActivity - private val viewModel: PlayerViewModel by activityViewModels() + private val viewModel by activityViewModels { PlayerViewModel.Factory } private val chaptersModel: ChaptersViewModel by activityViewModels() // for the transition @@ -370,7 +372,7 @@ } private fun handleServiceConnection() { - viewModel.player = playerService?.player +// viewModel.player = playerService?.player playerService?.onStateOrPlayingChanged = { isPlaying -> updatePlayPauseButton() isPaused = !isPlaying @@ -394,8 +396,6 @@ activity?.unbindService(connection) } - viewModel.player = null - super.onDestroy() } Index: app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt --- a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt (revision 6383ee1beedfa5650eb0573242f9338767e6c08c) +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt (date 1721114024216) @@ -15,6 +15,7 @@ import androidx.core.view.updatePadding import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope +import androidx.media3.common.util.UnstableApi import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ItemTouchHelper @@ -49,6 +50,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +@UnstableApi class PlaylistFragment : DynamicLayoutManagerFragment() { private var _binding: FragmentPlaylistBinding? = null private val binding get() = _binding!! @@ -67,7 +69,7 @@ private var isBookmarked = false // view models - private val playerViewModel: PlayerViewModel by activityViewModels() + private val playerViewModel: PlayerViewModel by activityViewModels { PlayerViewModel.Factory } private var selectedSortOrder = PreferenceHelper.getInt(PreferenceKeys.PLAYLIST_SORT_ORDER, 0) set(value) { PreferenceHelper.putInt(PreferenceKeys.PLAYLIST_SORT_ORDER, value) Index: app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt (revision 6383ee1beedfa5650eb0573242f9338767e6c08c) +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt (date 1721113906326) @@ -44,9 +44,7 @@ import androidx.media3.common.PlaybackException import androidx.media3.common.Player import androidx.media3.datasource.cronet.CronetDataSource -import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.hls.HlsMediaSource -import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R import com.github.libretube.api.CronetHelper @@ -127,7 +125,7 @@ private val doubleTapOverlayBinding get() = binding.doubleTapOverlay.binding private val playerGestureControlsViewBinding get() = binding.playerGestureControlsView.binding - private val viewModel: PlayerViewModel by activityViewModels() + private val viewModel by activityViewModels { PlayerViewModel.Factory } private val commentsViewModel: CommentsViewModel by activityViewModels() private val chaptersViewModel: ChaptersViewModel by activityViewModels() @@ -140,8 +138,6 @@ private var isShort = false // data and objects stored for the player - private lateinit var exoPlayer: ExoPlayer - private lateinit var trackSelector: DefaultTrackSelector private lateinit var streams: Streams private var isPlayerTransitioning = true @@ -199,7 +195,7 @@ override fun onReceive(context: Context, intent: Intent) { val event = intent.serializableExtra(PlayerHelper.CONTROL_TYPE) ?: return - if (PlayerHelper.handlePlayerAction(exoPlayer, event)) return + if (PlayerHelper.handlePlayerAction(viewModel.player, event)) return when (event) { PlayerEvent.Next -> { @@ -277,7 +273,7 @@ } if (events.contains(Player.EVENT_TRACKS_CHANGED)) { - PlayerHelper.setPreferredAudioQuality(requireContext(), exoPlayer, trackSelector) + PlayerHelper.setPreferredAudioQuality(requireContext(), viewModel.player, viewModel.trackSelector) } } @@ -286,9 +282,9 @@ // set the playback speed to one if having reached the end of a livestream if (playbackState == Player.STATE_BUFFERING && binding.player.isLive && - exoPlayer.duration - exoPlayer.currentPosition < 700 + viewModel.player.duration - viewModel.player.currentPosition < 700 ) { - exoPlayer.setPlaybackSpeed(1f) + viewModel.player.setPlaybackSpeed(1f) } // check if video has ended, next video is available and autoplay is enabled/the video is part of a played playlist. @@ -322,7 +318,7 @@ if (playbackState == Player.STATE_BUFFERING) { if (bufferingTimeoutTask == null) { bufferingTimeoutTask = Runnable { - exoPlayer.pause() + viewModel.player.pause() } } @@ -340,7 +336,7 @@ override fun onPlayerError(error: PlaybackException) { super.onPlayerError(error) try { - exoPlayer.play() + viewModel.player.play() } catch (e: Exception) { e.printStackTrace() } @@ -522,7 +518,7 @@ } binding.playImageView.setOnClickListener { - exoPlayer.togglePlayPauseState() + viewModel.player.togglePlayPauseState() } activity?.supportFragmentManager @@ -554,7 +550,7 @@ IntentData.shareObjectType to ShareObjectType.VIDEO, IntentData.shareData to ShareData( currentVideo = streams.title, - currentPosition = exoPlayer.currentPosition / 1000 + currentPosition = viewModel.player.currentPosition / 1000 ) ) val newShareDialog = ShareDialog() @@ -581,7 +577,7 @@ binding.relPlayerBackground.setOnClickListener { // pause the current player - exoPlayer.pause() + viewModel.player.pause() // start the background mode playOnBackground() @@ -651,7 +647,7 @@ BackgroundHelper.playOnBackground( requireContext(), videoId, - exoPlayer.currentPosition, + viewModel.player.currentPosition, playlistId, channelId, keepQueue = true, @@ -664,8 +660,8 @@ private fun updateFullscreenOrientation() { if (PlayerHelper.autoFullscreenEnabled || !this::streams.isInitialized) return - val height = streams.videoStreams.firstOrNull()?.height ?: exoPlayer.videoSize.height - val width = streams.videoStreams.firstOrNull()?.width ?: exoPlayer.videoSize.width + val height = streams.videoStreams.firstOrNull()?.height ?: viewModel.player.videoSize.height + val width = streams.videoStreams.firstOrNull()?.width ?: viewModel.player.videoSize.width mainActivity.requestedOrientation = PlayerHelper.getOrientation(width, height) } @@ -745,17 +741,15 @@ val isInteractive = requireContext().getSystemService()!!.isInteractive // disable video stream since it's not needed when screen off - if (!isInteractive && this::trackSelector.isInitialized) { - trackSelector.updateParameters { + if (!isInteractive) { + viewModel.trackSelector.updateParameters { setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, true) } } // pause player if screen off and setting enabled - if (this::exoPlayer.isInitialized && !isInteractive && - PlayerHelper.pausePlayerOnScreenOffEnabled - ) { - exoPlayer.pause() + if (!isInteractive && PlayerHelper.pausePlayerOnScreenOffEnabled) { + viewModel.player.pause() } // the app was put somewhere in the background - remember to not automatically continue @@ -777,11 +771,9 @@ } // re-enable and load video stream - if (this::trackSelector.isInitialized) { - trackSelector.updateParameters { + viewModel.trackSelector.updateParameters { setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, false) } - } } override fun onDestroy() { @@ -794,29 +786,12 @@ watchPositionTimer.destroy() handler.removeCallbacksAndMessages(null) - if (this::exoPlayer.isInitialized) { - exoPlayer.removeListener(playerListener) - exoPlayer.pause() - - runCatching { - // the player could also be a different instance because a new player fragment - // got created in the meanwhile - if (!viewModel.isOrientationChangeInProgress) { - if (viewModel.player == exoPlayer) { - viewModel.player = null - viewModel.trackSelector = null - } - - exoPlayer.stop() - exoPlayer.release() - } - } + viewModel.player.removeListener(playerListener) - if (PlayerHelper.pipEnabled) { - // disable the auto PiP mode for SDK >= 32 - PictureInPictureCompat - .setPictureInPictureParams(requireActivity(), pipParams) - } + if (PlayerHelper.pipEnabled) { + // disable the auto PiP mode for SDK >= 32 + PictureInPictureCompat + .setPictureInPictureParams(requireActivity(), pipParams) } runCatching { @@ -866,18 +841,18 @@ // save the watch position if video isn't finished and option enabled private fun saveWatchPosition() { - if (this::exoPlayer.isInitialized && !isPlayerTransitioning && PlayerHelper.watchPositionsVideo) { - PlayerHelper.saveWatchPosition(exoPlayer, videoId) + if (viewModel.player != null && !isPlayerTransitioning && PlayerHelper.watchPositionsVideo) { + PlayerHelper.saveWatchPosition(viewModel.player, videoId) } } private fun checkForSegments() { - if (!exoPlayer.isPlaying || !PlayerHelper.sponsorBlockEnabled) return + if (!viewModel.player.isPlaying || !PlayerHelper.sponsorBlockEnabled) return handler.postDelayed(this::checkForSegments, 100) if (!viewModel.sponsorBlockEnabled || viewModel.segments.isEmpty()) return - exoPlayer.checkForSegments( + viewModel.player.checkForSegments( requireContext(), viewModel.segments, viewModel.sponsorBlockConfig @@ -886,12 +861,12 @@ if (viewModel.isMiniPlayerVisible.value == true) return@let binding.sbSkipBtn.isVisible = true binding.sbSkipBtn.setOnClickListener { - exoPlayer.seekTo((segment.segmentStartAndEnd.second * 1000f).toLong()) + viewModel.player.seekTo((segment.segmentStartAndEnd.second * 1000f).toLong()) segment.skipped = true } return } - if (!exoPlayer.isInSegment(viewModel.segments)) binding.sbSkipBtn.isGone = true + if (!viewModel.player.isInSegment(viewModel.segments)) binding.sbSkipBtn.isGone = true } private fun playVideo() { @@ -902,7 +877,7 @@ // reset the comments to become reloaded later commentsViewModel.reset() - lifecycleScope.launch(Dispatchers.Main) { + viewLifecycleOwner.lifecycleScope.launch { viewModel.fetchVideoInfo(requireContext(), videoId).let { (streams, errorMessage) -> if (errorMessage != null) { context?.toastFromMainDispatcher(errorMessage, Toast.LENGTH_LONG) @@ -946,17 +921,17 @@ binding.player.apply { useController = false - player = exoPlayer + player = viewModel.player } initializePlayerView() // don't continue playback when the fragment is re-created after Android killed it val wasIntentStopped = requireArguments().getBoolean(IntentData.wasIntentStopped, false) - exoPlayer.playWhenReady = PlayerHelper.playAutomatically && !wasIntentStopped + viewModel.player.playWhenReady = PlayerHelper.playAutomatically && !wasIntentStopped requireArguments().putBoolean(IntentData.wasIntentStopped, false) - exoPlayer.prepare() + viewModel.player.prepare() if (binding.playerMotionLayout.progress != 1.0f) { // show controllers when not in picture in picture mode @@ -974,7 +949,7 @@ fetchSponsorBlockSegments() if (streams.category == Streams.categoryMusic) { - exoPlayer.setPlaybackSpeed(1f) + viewModel.player.setPlaybackSpeed(1f) } viewModel.isOrientationChangeInProgress = false @@ -1005,7 +980,7 @@ */ private fun playNextVideo(nextId: String? = null) { if (nextId == null && PlayingQueue.repeatMode == Player.REPEAT_MODE_ONE) { - exoPlayer.seekTo(0) + viewModel.player.seekTo(0) return } @@ -1038,7 +1013,7 @@ private fun initializePlayerView() { // initialize the player view actions binding.player.initialize(doubleTapOverlayBinding, playerGestureControlsViewBinding, chaptersViewModel) - binding.player.initPlayerOptions(viewModel, viewLifecycleOwner, trackSelector, this) + binding.player.initPlayerOptions(viewModel, viewLifecycleOwner, viewModel.trackSelector, this) binding.descriptionLayout.setStreams(streams) @@ -1124,7 +1099,7 @@ if (videoId == this.videoId) { // try finding the time stamp of the url and seek to it if found uri.getQueryParameter("t")?.toTimeInSeconds()?.let { - exoPlayer.seekTo(it * 1000) + viewModel.player.seekTo(it * 1000) } } else { // YouTube video link without time or not the current video, thus load in player @@ -1133,7 +1108,7 @@ } private fun updatePlayPauseButton() { - binding.playImageView.setImageResource(PlayerHelper.getPlayPauseActionIcon(exoPlayer)) + binding.playImageView.setImageResource(PlayerHelper.getPlayPauseActionIcon(viewModel.player)) } private suspend fun initializeHighlight(highlight: Segment) { @@ -1173,14 +1148,14 @@ private fun setMediaSource(uri: Uri, mimeType: String) { val mediaItem = createMediaItem(uri, mimeType) - exoPlayer.setMediaItem(mediaItem) + viewModel.player.setMediaItem(mediaItem) } /** * Get all available player resolutions */ private fun getAvailableResolutions(): List { - val resolutions = exoPlayer.currentTracks.groups.asSequence() + val resolutions = viewModel.player.currentTracks.groups.asSequence() .flatMap { group -> (0 until group.length).map { group.getTrackFormat(it).height @@ -1196,7 +1171,7 @@ private fun initStreamSources() { // use the video's default audio track when starting playback - trackSelector.updateParameters { + viewModel.trackSelector.updateParameters { setPreferredAudioRoleFlags(C.ROLE_FLAG_MAIN) } @@ -1210,13 +1185,13 @@ withContext(Dispatchers.Main) { // support for time stamped links if (timeStamp != 0L) { - exoPlayer.seekTo(timeStamp * 1000) + viewModel.player.seekTo(timeStamp * 1000) // delete the time stamp because it already got consumed timeStamp = 0L } else if (!streams.livestream) { // seek to the saved watch position PlayerHelper.getStoredWatchPosition(videoId, streams.duration)?.let { - exoPlayer.seekTo(it) + viewModel.player.seekTo(it) } } } @@ -1230,7 +1205,7 @@ resolution } - trackSelector.updateParameters { + viewModel.trackSelector.updateParameters { setMaxVideoSize(Int.MAX_VALUE, transformedResolution) setMinVideoSize(Int.MIN_VALUE, transformedResolution) } @@ -1239,8 +1214,6 @@ private fun updateResolutionOnFullscreenChange(isFullscreen: Boolean) { // this occurs when the user has the phone in landscape mode and it thus rotates when // opening a new video - if (!this::trackSelector.isInitialized) return - if (!isFullscreen && noFullscreenResolution != null) { setPlayerResolution(noFullscreenResolution!!) } else if (fullscreenResolution != null) { @@ -1290,7 +1263,7 @@ MimeTypes.APPLICATION_M3U8 ) ) - withContext(Dispatchers.Main) { exoPlayer.setMediaSource(mediaSource) } + withContext(Dispatchers.Main) { viewModel.player.setMediaSource(mediaSource) } return } // NO STREAM FOUND @@ -1303,16 +1276,11 @@ } private fun createExoPlayer() { - viewModel.keepOrCreatePlayer(requireContext()).let { (player, trackSelector) -> - this.exoPlayer = player - this.trackSelector = trackSelector - } - - exoPlayer.setWakeMode(C.WAKE_MODE_NETWORK) - exoPlayer.addListener(playerListener) + viewModel.player.setWakeMode(C.WAKE_MODE_NETWORK) + viewModel.player.addListener(playerListener) // control for the track sources like subtitles and audio source - trackSelector.updateParameters { + viewModel.trackSelector.updateParameters { val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs if (enabledVideoCodecs != "all") { // map the codecs to their corresponding mimetypes @@ -1333,7 +1301,7 @@ if (viewModel.nowPlayingNotification == null) { viewModel.nowPlayingNotification = NowPlayingNotification( requireContext(), - exoPlayer, + viewModel.player, NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_ONLINE ) } @@ -1396,7 +1364,7 @@ override fun onQualityClicked() { // get the available resolutions val resolutions = getAvailableResolutions() - val currentQuality = trackSelector.parameters.maxVideoHeight + val currentQuality = viewModel.trackSelector.parameters.maxVideoHeight // Dialog for quality selection BaseBottomSheet() @@ -1421,7 +1389,7 @@ override fun onAudioStreamClicked() { val context = requireContext() val audioLanguagesAndRoleFlags = PlayerHelper.getAudioLanguagesAndRoleFlagsFromTrackGroups( - exoPlayer.currentTracks.groups, + viewModel.player.currentTracks.groups, false ) val audioLanguages = audioLanguagesAndRoleFlags.map { @@ -1451,7 +1419,7 @@ } else { baseBottomSheet.setSimpleItems(audioLanguages) { index -> val selectedAudioFormat = audioLanguagesAndRoleFlags[index] - trackSelector.updateParameters { + viewModel.trackSelector.updateParameters { setPreferredAudioLanguage(selectedAudioFormat.first) setPreferredAudioRoleFlags(selectedAudioFormat.second) } @@ -1467,7 +1435,7 @@ override fun onStatsClicked() { if (!this::streams.isInitialized) return - val videoStats = getVideoStats(exoPlayer, videoId) + val videoStats = getVideoStats(viewModel.player, videoId) StatsSheet() .apply { arguments = bundleOf(IntentData.videoStats to videoStats) } .show(childFragmentManager) @@ -1488,7 +1456,7 @@ // close button got clicked in PiP mode // pause the video and keep the app alive if (lifecycle.currentState == Lifecycle.State.CREATED) { - exoPlayer.pause() + viewModel.player.pause() viewModel.nowPlayingNotification?.cancelNotification() closedVideo = true } @@ -1502,7 +1470,7 @@ } } - private fun updateCurrentSubtitle(subtitle: Subtitle?) = trackSelector.updateParameters { + private fun updateCurrentSubtitle(subtitle: Subtitle?) = viewModel.trackSelector.updateParameters { val roleFlags = if (subtitle?.code != null) getSubtitleRoleFlags(subtitle) else 0 setPreferredTextRoleFlags(roleFlags) setPreferredTextLanguage(subtitle?.code) @@ -1512,17 +1480,17 @@ if (shouldStartPiP()) { PictureInPictureCompat.enterPictureInPictureMode(requireActivity(), pipParams) } else if (PlayerHelper.pauseOnQuit) { - exoPlayer.pause() + viewModel.player.pause() } } private val pipParams get() = PictureInPictureParamsCompat.Builder() - .setActions(PlayerHelper.getPiPModeActions(requireActivity(), exoPlayer.isPlaying)) - .setAutoEnterEnabled(PlayerHelper.pipEnabled && exoPlayer.isPlaying) + .setActions(PlayerHelper.getPiPModeActions(requireActivity(), viewModel.player.isPlaying)) + .setAutoEnterEnabled(PlayerHelper.pipEnabled && viewModel.player.isPlaying) .apply { - if (exoPlayer.isPlaying) { - setAspectRatio(exoPlayer.videoSize) + if (viewModel.player.isPlaying) { + setAspectRatio(viewModel.player.videoSize) } } .build() @@ -1543,7 +1511,7 @@ } private fun shouldStartPiP(): Boolean { - return shouldUsePip() && exoPlayer.isPlaying && + return shouldUsePip() && viewModel.player.isPlaying && !BackgroundHelper.isBackgroundServiceRunning(requireContext()) } @@ -1557,9 +1525,7 @@ val orientation = resources.configuration.orientation if (viewModel.isFullscreen.value != true && orientation != playerLayoutOrientation) { // remember the current position before recreating the activity - if (this::exoPlayer.isInitialized) { - arguments?.putLong(IntentData.timeStamp, exoPlayer.currentPosition / 1000) - } + arguments?.putLong(IntentData.timeStamp, viewModel.player.currentPosition / 1000) playerLayoutOrientation = orientation viewModel.isOrientationChangeInProgress = true Index: app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt --- a/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt (revision 6383ee1beedfa5650eb0573242f9338767e6c08c) +++ b/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt (date 1721113906302) @@ -8,7 +8,6 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.MarginLayoutParams -import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import androidx.core.view.children import androidx.core.view.isGone @@ -16,6 +15,7 @@ import androidx.core.view.updateLayoutParams import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope +import androidx.media3.common.util.UnstableApi import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -51,12 +51,13 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +@UnstableApi class SubscriptionsFragment : DynamicLayoutManagerFragment() { private var _binding: FragmentSubscriptionsBinding? = null private val binding get() = _binding!! private val viewModel: SubscriptionsViewModel by activityViewModels() - private val playerModel: PlayerViewModel by activityViewModels() + private val playerModel by activityViewModels { PlayerViewModel.Factory } private val channelGroupsModel: EditChannelGroupsModel by activityViewModels() private var selectedFilterGroup set(value) = PreferenceHelper.putInt(PreferenceKeys.SELECTED_CHANNEL_GROUP, value) Index: app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt --- a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt (revision 6383ee1beedfa5650eb0573242f9338767e6c08c) +++ b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt (date 1721114024210) @@ -23,6 +23,7 @@ import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView import androidx.lifecycle.lifecycleScope +import androidx.media3.common.util.UnstableApi import androidx.navigation.NavController import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment @@ -56,6 +57,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +@UnstableApi class MainActivity : BaseActivity() { lateinit var binding: ActivityMainBinding lateinit var navController: NavController @@ -64,7 +66,7 @@ private var startFragmentId = R.id.homeFragment - private val playerViewModel: PlayerViewModel by viewModels() + private val playerViewModel: PlayerViewModel by viewModels { PlayerViewModel.Factory } private val searchViewModel: SearchViewModel by viewModels() private val subscriptionsViewModel: SubscriptionsViewModel by viewModels()