Merge branch 'main' into develop/mpv

This commit is contained in:
Damontecres 2025-11-11 16:02:14 -05:00
commit 96c856e8fc
No known key found for this signature in database
10 changed files with 261 additions and 122 deletions

View file

@ -37,6 +37,7 @@ import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.util.AppUpgradeHandler import com.github.damontecres.wholphin.util.AppUpgradeHandler
import com.github.damontecres.wholphin.util.PlaybackLifecycleObserver
import com.github.damontecres.wholphin.util.ServerEventListener import com.github.damontecres.wholphin.util.ServerEventListener
import com.github.damontecres.wholphin.util.UpdateChecker import com.github.damontecres.wholphin.util.UpdateChecker
import com.github.damontecres.wholphin.util.profile.createDeviceProfile import com.github.damontecres.wholphin.util.profile.createDeviceProfile
@ -70,13 +71,17 @@ class MainActivity : AppCompatActivity() {
@Inject @Inject
lateinit var serverEventListener: ServerEventListener lateinit var serverEventListener: ServerEventListener
@Inject
lateinit var playbackLifecycleObserver: PlaybackLifecycleObserver
@OptIn(ExperimentalTvMaterial3Api::class) @OptIn(ExperimentalTvMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Timber.i("MainActivity.onCreate")
lifecycle.addObserver(playbackLifecycleObserver)
if (savedInstanceState == null) { if (savedInstanceState == null) {
appUpgradeHandler.copySubfont(false) appUpgradeHandler.copySubfont(false)
} }
Timber.i("MainActivity.onCreate")
setContent { setContent {
CoilConfig(okHttpClient, false) CoilConfig(okHttpClient, false)
val appPreferences by userPreferencesDataStore.data.collectAsState(null) val appPreferences by userPreferencesDataStore.data.collectAsState(null)
@ -178,11 +183,4 @@ class MainActivity : AppCompatActivity() {
appUpgradeHandler.run() appUpgradeHandler.run()
} }
} }
override fun onPause() {
if (navigationManager.backStack.lastOrNull() is Destination.Playback) {
navigationManager.goBack()
}
super.onPause()
}
} }

View file

@ -223,7 +223,7 @@ fun RecommendedMovie(
viewModel.init() viewModel.init()
} }
val loading by viewModel.loading.observeAsState(LoadingState.Loading) val loading by viewModel.loading.observeAsState(LoadingState.Loading)
val rows by viewModel.rows.collectAsState(listOf()) val rows by viewModel.rows.collectAsState()
when (val state = loading) { when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state) is LoadingState.Error -> ErrorMessage(state)

View file

@ -253,7 +253,7 @@ fun RecommendedTvShow(
} }
val loading by viewModel.loading.observeAsState(LoadingState.Loading) val loading by viewModel.loading.observeAsState(LoadingState.Loading)
val rows by viewModel.rows.collectAsState(listOf()) val rows by viewModel.rows.collectAsState()
when (val state = loading) { when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state) is LoadingState.Error -> ErrorMessage(state)

View file

@ -91,7 +91,9 @@ fun HomePage(
firstLoad = false firstLoad = false
} }
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading) val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
val homeRows by viewModel.homeRows.observeAsState(listOf()) val refreshing by viewModel.refreshState.observeAsState(LoadingState.Loading)
val watchingRows by viewModel.watchingRows.observeAsState(listOf())
val latestRows by viewModel.latestRows.observeAsState(listOf())
LaunchedEffect(loading) { LaunchedEffect(loading) {
val state = loading val state = loading
if (!firstLoad && state is LoadingState.Error) { if (!firstLoad && state is LoadingState.Error) {
@ -106,7 +108,7 @@ fun HomePage(
} }
} }
when (val state = if (firstLoad) loading else LoadingState.Success) { when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state) is LoadingState.Error -> ErrorMessage(state)
LoadingState.Loading, LoadingState.Loading,
@ -118,7 +120,7 @@ fun HomePage(
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) } var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending) val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
HomePageContent( HomePageContent(
homeRows, watchingRows + latestRows,
onClickItem = { onClickItem = {
viewModel.navigationManager.navigateTo(it.destination()) viewModel.navigationManager.navigateTo(it.destination())
}, },
@ -154,7 +156,7 @@ fun HomePage(
items = dialogItems, items = dialogItems,
) )
}, },
loadingState = loading, loadingState = refreshing,
showClock = preferences.appPreferences.interfacePreferences.showClock, showClock = preferences.appPreferences.interfacePreferences.showClock,
modifier = modifier, modifier = modifier,
) )
@ -277,7 +279,7 @@ fun HomePageContent(
-> { -> {
Column( Column(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier, modifier = Modifier.animateItem(),
) { ) {
Text( Text(
text = r.title, text = r.title,
@ -295,7 +297,7 @@ fun HomePageContent(
is HomeRowLoadingState.Error -> { is HomeRowLoadingState.Error -> {
Column( Column(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier, modifier = Modifier.animateItem(),
) { ) {
Text( Text(
text = r.title, text = r.title,
@ -323,7 +325,10 @@ fun HomePageContent(
} }
}, },
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
modifier = Modifier.fillMaxWidth(), modifier =
Modifier
.fillMaxWidth()
.animateItem(),
cardContent = { index, item, cardModifier, onClick, onLongClick -> cardContent = { index, item, cardModifier, onClick, onLongClick ->
// TODO better aspect ration handling? // TODO better aspect ration handling?
BannerCard( BannerCard(

View file

@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler
@ -51,12 +52,18 @@ class HomeViewModel
val navDrawerItemRepository: NavDrawerItemRepository, val navDrawerItemRepository: NavDrawerItemRepository,
) : ViewModel() { ) : ViewModel() {
val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending) val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending)
val homeRows = MutableLiveData<List<HomeRowLoadingState>>() val refreshState = MutableLiveData<LoadingState>(LoadingState.Pending)
val watchingRows = MutableLiveData<List<HomeRowLoadingState>>(listOf())
val latestRows = MutableLiveData<List<HomeRowLoadingState>>(listOf())
private lateinit var preferences: UserPreferences private lateinit var preferences: UserPreferences
fun init(preferences: UserPreferences): Job { fun init(preferences: UserPreferences): Job {
val reload = loadingState.value != LoadingState.Success
if (reload) {
loadingState.value = LoadingState.Loading loadingState.value = LoadingState.Loading
}
refreshState.value = LoadingState.Loading
this.preferences = preferences this.preferences = preferences
val prefs = preferences.appPreferences.homePagePreferences val prefs = preferences.appPreferences.homePagePreferences
val limit = prefs.maxItemsPerRow val limit = prefs.maxItemsPerRow
@ -84,9 +91,7 @@ class HomeViewModel
prefs.enableRewatchingNextUp, prefs.enableRewatchingNextUp,
prefs.combineContinueNext, prefs.combineContinueNext,
) )
val latest = getLatest(userDto, limit, includedIds) val watching =
val homeRows =
buildList { buildList {
if (resume.isNotEmpty()) { if (resume.isNotEmpty()) {
add( add(
@ -104,12 +109,20 @@ class HomeViewModel
), ),
) )
} }
addAll(latest)
} }
val latest = getLatest(userDto, limit, includedIds)
val pendingLatest = latest.map { HomeRowLoadingState.Loading(it.title) }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
this@HomeViewModel.homeRows.value = homeRows this@HomeViewModel.watchingRows.value = watching
if (reload) {
this@HomeViewModel.latestRows.value = pendingLatest
}
loadingState.value = LoadingState.Success loadingState.value = LoadingState.Success
} }
loadLatest(latest)
refreshState.setValueOnMain(LoadingState.Success)
} }
} }
} }
@ -140,7 +153,7 @@ class HomeViewModel
.getResumeItems(request) .getResumeItems(request)
.content .content
.items .items
.map { BaseItem.Companion.from(it, api, true) } .map { BaseItem.from(it, api, true) }
return items return items
} }
@ -174,29 +187,21 @@ class HomeViewModel
user: UserDto, user: UserDto,
limit: Int, limit: Int,
includedIds: List<UUID>, includedIds: List<UUID>,
): List<HomeRowLoadingState> { ): List<LatestData> {
val latestMediaIncludes = val excluded = user.configuration?.latestItemsExcludes.orEmpty()
user.configuration
?.orderedViews
.orEmpty()
.toMutableList()
.apply {
removeAll(user.configuration?.latestItemsExcludes.orEmpty())
}.filter { includedIds.contains(it) }
val views by api.userViewsApi.getUserViews() val views by api.userViewsApi.getUserViews()
val rows = val latestData =
latestMediaIncludes views.items
.mapNotNull { viewId -> views.items.firstOrNull { it.id == viewId } } .filter {
.filter { it.collectionType in supportedLatestCollectionTypes } it.id in includedIds && it.id !in excluded &&
.mapNotNull { view -> it.collectionType in supportedLatestCollectionTypes
}.mapNotNull { view ->
val title = val title =
if (view.collectionType == CollectionType.LIVETV) { if (view.collectionType == CollectionType.LIVETV) {
context.getString(R.string.recently_recorded) context.getString(R.string.recently_recorded)
} else { } else {
view.name?.let { context.getString(R.string.recently_added_in, it) } view.name?.let { context.getString(R.string.recently_added_in, it) }
} ?: context.getString(R.string.recently_added) } ?: context.getString(R.string.recently_added)
try {
val viewId = val viewId =
if (view.collectionType == CollectionType.LIVETV) { if (view.collectionType == CollectionType.LIVETV) {
api.liveTvApi api.liveTvApi
@ -218,6 +223,17 @@ class HomeViewModel
limit = limit, limit = limit,
isPlayed = null, // Server will handle user's preference isPlayed = null, // Server will handle user's preference
) )
LatestData(title, request)
}
}
return latestData
}
private suspend fun loadLatest(latestData: List<LatestData>) {
val rows =
latestData.map { (title, request) ->
try {
val latest = val latest =
api.userLibraryApi api.userLibraryApi
.getLatestMedia(request) .getLatestMedia(request)
@ -227,7 +243,6 @@ class HomeViewModel
title = title, title = title,
items = latest, items = latest,
) )
}
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.e(ex, "Exception fetching %s", title) Timber.e(ex, "Exception fetching %s", title)
HomeRowLoadingState.Error( HomeRowLoadingState.Error(
@ -236,8 +251,7 @@ class HomeViewModel
) )
} }
} }
latestRows.setValueOnMain(rows)
return rows
} }
fun setWatched( fun setWatched(
@ -275,3 +289,8 @@ val supportedLatestCollectionTypes =
CollectionType.TVSHOWS, CollectionType.TVSHOWS,
CollectionType.LIVETV, CollectionType.LIVETV,
) )
data class LatestData(
val title: String,
val request: GetLatestMediaRequest,
)

View file

@ -148,6 +148,20 @@ fun DestinationContent(
BaseItemKind.USER_VIEW -> BaseItemKind.USER_VIEW ->
when (destination.item?.data?.collectionType) { when (destination.item?.data?.collectionType) {
CollectionType.TVSHOWS ->
CollectionFolderTv(
preferences,
destination,
modifier,
)
CollectionType.MOVIES ->
CollectionFolderMovie(
preferences,
destination,
modifier,
)
CollectionType.LIVETV -> CollectionType.LIVETV ->
CollectionFolderLiveTv( CollectionFolderLiveTv(
preferences, preferences,

View file

@ -17,8 +17,6 @@ import androidx.media3.common.Player
import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.Tracks import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ItemPlaybackDao import com.github.damontecres.wholphin.data.ItemPlaybackDao
import com.github.damontecres.wholphin.data.ItemPlaybackRepository import com.github.damontecres.wholphin.data.ItemPlaybackRepository
@ -33,7 +31,6 @@ import com.github.damontecres.wholphin.data.model.chooseSource
import com.github.damontecres.wholphin.data.model.chooseStream import com.github.damontecres.wholphin.data.model.chooseStream
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.MediaExtensionStatus
import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.preferences.ShowNextUpWhen import com.github.damontecres.wholphin.preferences.ShowNextUpWhen
import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior
@ -50,11 +47,11 @@ import com.github.damontecres.wholphin.util.EqualityMutableLiveData
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.PlayerFactory
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
import com.github.damontecres.wholphin.util.TrackSupport import com.github.damontecres.wholphin.util.TrackSupport
import com.github.damontecres.wholphin.util.checkForSupport import com.github.damontecres.wholphin.util.checkForSupport
import com.github.damontecres.wholphin.util.formatDateTime import com.github.damontecres.wholphin.util.formatDateTime
import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile
import com.github.damontecres.wholphin.util.seasonEpisodePadded import com.github.damontecres.wholphin.util.seasonEpisodePadded
import com.github.damontecres.wholphin.util.subtitleMimeTypes import com.github.damontecres.wholphin.util.subtitleMimeTypes
@ -65,12 +62,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
@ -123,44 +118,11 @@ class PlaybackViewModel
val serverRepository: ServerRepository, val serverRepository: ServerRepository,
val itemPlaybackRepository: ItemPlaybackRepository, val itemPlaybackRepository: ItemPlaybackRepository,
val appPreferences: DataStore<AppPreferences>, val appPreferences: DataStore<AppPreferences>,
private val playerFactory: PlayerFactory,
) : ViewModel(), ) : ViewModel(),
Player.Listener { Player.Listener {
val player by lazy { val player by lazy {
val prefs = runBlocking { appPreferences.data.firstOrNull()?.playbackPreferences } playerFactory.createVideoPlayer()
val backend = prefs?.playerBackend ?: AppPreference.PlayerBackendPref.defaultValue
when (backend) {
PlayerBackend.MPV -> {
val enableHardwareDecoding =
prefs?.mpvOptions?.enableHardwareDecoding
?: AppPreference.MpvHardwareDecoding.defaultValue
MpvPlayer(context, enableHardwareDecoding)
.apply {
playWhenReady = true
}
}
PlayerBackend.EXO_PLAYER,
PlayerBackend.UNRECOGNIZED,
-> {
val extensions = prefs?.overrides?.mediaExtensionsEnabled
Timber.v("extensions=$extensions")
val rendererMode =
when (extensions) {
MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
MediaExtensionStatus.MES_PREFERRED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
}
ExoPlayer
.Builder(context)
.setRenderersFactory(
DefaultRenderersFactory(context)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(rendererMode),
).build()
}
}
} }
val loading = MutableLiveData<LoadingState>(LoadingState.Loading) val loading = MutableLiveData<LoadingState>(LoadingState.Loading)

View file

@ -0,0 +1,46 @@
package com.github.damontecres.wholphin.util
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.nav.NavigationManager
import dagger.hilt.android.scopes.ActivityRetainedScoped
import javax.inject.Inject
/**
* Observes the activity lifecycle in order to pause/resume/stop playback
*/
@ActivityRetainedScoped
class PlaybackLifecycleObserver
@Inject
constructor(
private val navigationManager: NavigationManager,
private val playerFactory: PlayerFactory,
) : DefaultLifecycleObserver {
private var wasPlaying: Boolean? = null
override fun onStart(owner: LifecycleOwner) {
wasPlaying = null
}
override fun onResume(owner: LifecycleOwner) {
if (wasPlaying == true) {
playerFactory.currentPlayer?.let {
if (!it.isReleased) it.play()
}
}
}
override fun onPause(owner: LifecycleOwner) {
playerFactory.currentPlayer?.let {
wasPlaying = it.isPlaying
it.pause()
}
}
override fun onStop(owner: LifecycleOwner) {
if (navigationManager.backStack.lastOrNull() is Destination.Playback) {
navigationManager.goBack()
}
}
}

View file

@ -0,0 +1,95 @@
@file:OptIn(markerClass = [UnstableApi::class])
package com.github.damontecres.wholphin.util
import android.content.Context
import androidx.annotation.OptIn
import androidx.datastore.core.DataStore
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.MediaExtensionStatus
import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
/**
* Constructs a [Player] instance for video playback
*/
@Singleton
class PlayerFactory
@Inject
constructor(
@param:ApplicationContext private val context: Context,
private val appPreferences: DataStore<AppPreferences>,
) {
@Volatile
var currentPlayer: Player? = null
private set
fun createVideoPlayer(): Player {
if (currentPlayer?.isReleased == false) {
Timber.w("Player was not released before trying to create a new one!")
currentPlayer?.release()
}
val prefs = runBlocking { appPreferences.data.firstOrNull()?.playbackPreferences }
val backend = prefs?.playerBackend ?: AppPreference.PlayerBackendPref.defaultValue
val newPlayer =
when (backend) {
PlayerBackend.MPV -> {
val enableHardwareDecoding =
prefs?.mpvOptions?.enableHardwareDecoding
?: AppPreference.MpvHardwareDecoding.defaultValue
MpvPlayer(context, enableHardwareDecoding)
.apply {
playWhenReady = true
}
}
PlayerBackend.EXO_PLAYER,
PlayerBackend.UNRECOGNIZED,
-> {
val extensions =
runBlocking { appPreferences.data.firstOrNull() }?.playbackPreferences?.overrides?.mediaExtensionsEnabled
Timber.v("extensions=$extensions")
val rendererMode =
when (extensions) {
MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
MediaExtensionStatus.MES_PREFERRED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
}
ExoPlayer
.Builder(context)
.setRenderersFactory(
DefaultRenderersFactory(context)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(rendererMode),
).build()
.apply {
playWhenReady = true
}
}
}
currentPlayer = newPlayer
return newPlayer
}
}
val Player.isReleased: Boolean
get() {
return when (this) {
is ExoPlayer -> isReleased
is MpvPlayer -> isReleased
else -> throw IllegalStateException("Unknown Player type: ${this::class.qualifiedName}")
}
}

View file

@ -76,13 +76,13 @@ class TrackActivityPlaybackListener(
fun release() { fun release() {
task.cancel() task.cancel()
TIMER.purge() TIMER.purge()
val position = player.currentPosition.milliseconds.inWholeTicks val position = player.currentPosition.milliseconds
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) { coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) {
Timber.v("reportPlaybackStopped for ${itemPlayback.itemId}") Timber.v("reportPlaybackStopped for ${itemPlayback.itemId} at $position")
api.playStateApi.reportPlaybackStopped( api.playStateApi.reportPlaybackStopped(
PlaybackStopInfo( PlaybackStopInfo(
itemId = itemPlayback.itemId, itemId = itemPlayback.itemId,
positionTicks = position, positionTicks = position.inWholeTicks,
failed = false, failed = false,
playSessionId = playback.playSessionId, playSessionId = playback.playSessionId,
liveStreamId = playback.liveStreamId, liveStreamId = playback.liveStreamId,