fix(player): playback position reset on process death and playback continues playing when the device is locked
Closes #389 #390
This commit is contained in:
parent
58cbd9cf9d
commit
7a3b67f64b
4 changed files with 22 additions and 8 deletions
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".PlayerActivity"
|
android:name=".PlayerActivity"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||||
android:screenOrientation="sensorLandscape" />
|
android:screenOrientation="sensorLandscape" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -30,6 +30,7 @@ abstract class BasePlayerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
viewModel.playWhenReady = viewModel.player.playWhenReady == true
|
viewModel.playWhenReady = viewModel.player.playWhenReady == true
|
||||||
viewModel.player.playWhenReady = false
|
viewModel.player.playWhenReady = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ import dev.jdtech.jellyfin.mpv.TrackType
|
||||||
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
||||||
import dev.jdtech.jellyfin.utils.PreviewScrubListener
|
import dev.jdtech.jellyfin.utils.PreviewScrubListener
|
||||||
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import dev.jdtech.jellyfin.player.video.R as PlayerVideoR
|
import dev.jdtech.jellyfin.player.video.R as PlayerVideoR
|
||||||
|
|
||||||
|
@ -46,7 +45,6 @@ class PlayerActivity : BasePlayerActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Timber.d("Creating player activity")
|
|
||||||
|
|
||||||
binding = ActivityPlayerBinding.inflate(layoutInflater)
|
binding = ActivityPlayerBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
|
@ -44,6 +45,7 @@ constructor(
|
||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val jellyfinRepository: JellyfinRepository,
|
private val jellyfinRepository: JellyfinRepository,
|
||||||
private val appPreferences: AppPreferences,
|
private val appPreferences: AppPreferences,
|
||||||
|
private val savedStateHandle: SavedStateHandle,
|
||||||
) : ViewModel(), Player.Listener {
|
) : ViewModel(), Player.Listener {
|
||||||
val player: Player
|
val player: Player
|
||||||
|
|
||||||
|
@ -71,8 +73,8 @@ constructor(
|
||||||
|
|
||||||
val trackSelector = DefaultTrackSelector(application)
|
val trackSelector = DefaultTrackSelector(application)
|
||||||
var playWhenReady = true
|
var playWhenReady = true
|
||||||
private var currentMediaItemIndex = 0
|
private var currentMediaItemIndex = savedStateHandle["mediaItemIndex"] ?: 0
|
||||||
private var playbackPosition: Long = 0
|
private var playbackPosition: Long = savedStateHandle["position"] ?: 0
|
||||||
|
|
||||||
var playbackSpeed: Float = 1f
|
var playbackSpeed: Float = 1f
|
||||||
var disableSubtitle: Boolean = false
|
var disableSubtitle: Boolean = false
|
||||||
|
@ -116,6 +118,10 @@ constructor(
|
||||||
fun initializePlayer(
|
fun initializePlayer(
|
||||||
items: Array<PlayerItem>,
|
items: Array<PlayerItem>,
|
||||||
) {
|
) {
|
||||||
|
// Skip initialization when there are already items
|
||||||
|
if (this.items.isNotEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.items = items
|
this.items = items
|
||||||
player.addListener(this)
|
player.addListener(this)
|
||||||
|
|
||||||
|
@ -156,10 +162,16 @@ constructor(
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val startPosition = if (playbackPosition == 0L) {
|
||||||
|
items.getOrNull(currentMediaItemIndex)?.playbackPosition ?: C.TIME_UNSET
|
||||||
|
} else {
|
||||||
|
playbackPosition
|
||||||
|
}
|
||||||
|
|
||||||
player.setMediaItems(
|
player.setMediaItems(
|
||||||
mediaItems,
|
mediaItems,
|
||||||
currentMediaItemIndex,
|
currentMediaItemIndex,
|
||||||
items.getOrNull(currentMediaItemIndex)?.playbackPosition ?: C.TIME_UNSET,
|
startPosition,
|
||||||
)
|
)
|
||||||
if (appPreferences.playerMpv) { // For some reason, adding a 1ms delay between these two lines fixes a crash when playing with mpv from downloads
|
if (appPreferences.playerMpv) { // For some reason, adding a 1ms delay between these two lines fixes a crash when playing with mpv from downloads
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -191,9 +203,9 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentTrickPlay.value = null
|
_currentTrickPlay.value = null
|
||||||
playWhenReady = player.playWhenReady
|
playWhenReady = false
|
||||||
playbackPosition = position
|
playbackPosition = 0L
|
||||||
currentMediaItemIndex = player.currentMediaItemIndex
|
currentMediaItemIndex = 0
|
||||||
player.removeListener(this)
|
player.removeListener(this)
|
||||||
player.release()
|
player.release()
|
||||||
}
|
}
|
||||||
|
@ -201,6 +213,7 @@ constructor(
|
||||||
private fun pollPosition(player: Player) {
|
private fun pollPosition(player: Player) {
|
||||||
val playbackProgressRunnable = object : Runnable {
|
val playbackProgressRunnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
savedStateHandle["position"] = player.currentPosition
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) {
|
if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) {
|
||||||
val itemId = UUID.fromString(player.currentMediaItem!!.mediaId)
|
val itemId = UUID.fromString(player.currentMediaItem!!.mediaId)
|
||||||
|
@ -240,6 +253,7 @@ constructor(
|
||||||
|
|
||||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
Timber.d("Playing MediaItem: ${mediaItem?.mediaId}")
|
Timber.d("Playing MediaItem: ${mediaItem?.mediaId}")
|
||||||
|
savedStateHandle["mediaItemIndex"] = player.currentMediaItemIndex
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
items.first { it.itemId.toString() == player.currentMediaItem?.mediaId }
|
items.first { it.itemId.toString() == player.currentMediaItem?.mediaId }
|
||||||
|
|
Loading…
Reference in a new issue