mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Merge branch 'main' into develop/mpv
This commit is contained in:
commit
96c856e8fc
10 changed files with 261 additions and 122 deletions
|
|
@ -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.theme.WholphinTheme
|
||||
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.UpdateChecker
|
||||
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
||||
|
|
@ -70,13 +71,17 @@ class MainActivity : AppCompatActivity() {
|
|||
@Inject
|
||||
lateinit var serverEventListener: ServerEventListener
|
||||
|
||||
@Inject
|
||||
lateinit var playbackLifecycleObserver: PlaybackLifecycleObserver
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Timber.i("MainActivity.onCreate")
|
||||
lifecycle.addObserver(playbackLifecycleObserver)
|
||||
if (savedInstanceState == null) {
|
||||
appUpgradeHandler.copySubfont(false)
|
||||
}
|
||||
Timber.i("MainActivity.onCreate")
|
||||
setContent {
|
||||
CoilConfig(okHttpClient, false)
|
||||
val appPreferences by userPreferencesDataStore.data.collectAsState(null)
|
||||
|
|
@ -178,11 +183,4 @@ class MainActivity : AppCompatActivity() {
|
|||
appUpgradeHandler.run()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (navigationManager.backStack.lastOrNull() is Destination.Playback) {
|
||||
navigationManager.goBack()
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ fun RecommendedMovie(
|
|||
viewModel.init()
|
||||
}
|
||||
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) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ fun RecommendedTvShow(
|
|||
}
|
||||
|
||||
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) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@ fun HomePage(
|
|||
firstLoad = false
|
||||
}
|
||||
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) {
|
||||
val state = loading
|
||||
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)
|
||||
|
||||
LoadingState.Loading,
|
||||
|
|
@ -118,7 +120,7 @@ fun HomePage(
|
|||
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
HomePageContent(
|
||||
homeRows,
|
||||
watchingRows + latestRows,
|
||||
onClickItem = {
|
||||
viewModel.navigationManager.navigateTo(it.destination())
|
||||
},
|
||||
|
|
@ -154,7 +156,7 @@ fun HomePage(
|
|||
items = dialogItems,
|
||||
)
|
||||
},
|
||||
loadingState = loading,
|
||||
loadingState = refreshing,
|
||||
showClock = preferences.appPreferences.interfacePreferences.showClock,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
@ -277,7 +279,7 @@ fun HomePageContent(
|
|||
-> {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
Text(
|
||||
text = r.title,
|
||||
|
|
@ -295,7 +297,7 @@ fun HomePageContent(
|
|||
is HomeRowLoadingState.Error -> {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
Text(
|
||||
text = r.title,
|
||||
|
|
@ -323,7 +325,10 @@ fun HomePageContent(
|
|||
}
|
||||
},
|
||||
onLongClickItem = onLongClickItem,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.animateItem(),
|
||||
cardContent = { index, item, cardModifier, onClick, onLongClick ->
|
||||
// TODO better aspect ration handling?
|
||||
BannerCard(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.preferences.UserPreferences
|
|||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
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.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
|
|
@ -51,12 +52,18 @@ class HomeViewModel
|
|||
val navDrawerItemRepository: NavDrawerItemRepository,
|
||||
) : ViewModel() {
|
||||
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
|
||||
|
||||
fun init(preferences: UserPreferences): Job {
|
||||
loadingState.value = LoadingState.Loading
|
||||
val reload = loadingState.value != LoadingState.Success
|
||||
if (reload) {
|
||||
loadingState.value = LoadingState.Loading
|
||||
}
|
||||
refreshState.value = LoadingState.Loading
|
||||
this.preferences = preferences
|
||||
val prefs = preferences.appPreferences.homePagePreferences
|
||||
val limit = prefs.maxItemsPerRow
|
||||
|
|
@ -84,9 +91,7 @@ class HomeViewModel
|
|||
prefs.enableRewatchingNextUp,
|
||||
prefs.combineContinueNext,
|
||||
)
|
||||
val latest = getLatest(userDto, limit, includedIds)
|
||||
|
||||
val homeRows =
|
||||
val watching =
|
||||
buildList {
|
||||
if (resume.isNotEmpty()) {
|
||||
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) {
|
||||
this@HomeViewModel.homeRows.value = homeRows
|
||||
this@HomeViewModel.watchingRows.value = watching
|
||||
if (reload) {
|
||||
this@HomeViewModel.latestRows.value = pendingLatest
|
||||
}
|
||||
loadingState.value = LoadingState.Success
|
||||
}
|
||||
loadLatest(latest)
|
||||
refreshState.setValueOnMain(LoadingState.Success)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -140,7 +153,7 @@ class HomeViewModel
|
|||
.getResumeItems(request)
|
||||
.content
|
||||
.items
|
||||
.map { BaseItem.Companion.from(it, api, true) }
|
||||
.map { BaseItem.from(it, api, true) }
|
||||
return items
|
||||
}
|
||||
|
||||
|
|
@ -174,70 +187,71 @@ class HomeViewModel
|
|||
user: UserDto,
|
||||
limit: Int,
|
||||
includedIds: List<UUID>,
|
||||
): List<HomeRowLoadingState> {
|
||||
val latestMediaIncludes =
|
||||
user.configuration
|
||||
?.orderedViews
|
||||
.orEmpty()
|
||||
.toMutableList()
|
||||
.apply {
|
||||
removeAll(user.configuration?.latestItemsExcludes.orEmpty())
|
||||
}.filter { includedIds.contains(it) }
|
||||
|
||||
): List<LatestData> {
|
||||
val excluded = user.configuration?.latestItemsExcludes.orEmpty()
|
||||
val views by api.userViewsApi.getUserViews()
|
||||
val rows =
|
||||
latestMediaIncludes
|
||||
.mapNotNull { viewId -> views.items.firstOrNull { it.id == viewId } }
|
||||
.filter { it.collectionType in supportedLatestCollectionTypes }
|
||||
.mapNotNull { view ->
|
||||
val latestData =
|
||||
views.items
|
||||
.filter {
|
||||
it.id in includedIds && it.id !in excluded &&
|
||||
it.collectionType in supportedLatestCollectionTypes
|
||||
}.mapNotNull { view ->
|
||||
val title =
|
||||
if (view.collectionType == CollectionType.LIVETV) {
|
||||
context.getString(R.string.recently_recorded)
|
||||
} else {
|
||||
view.name?.let { context.getString(R.string.recently_added_in, it) }
|
||||
} ?: context.getString(R.string.recently_added)
|
||||
try {
|
||||
val viewId =
|
||||
if (view.collectionType == CollectionType.LIVETV) {
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(
|
||||
userId = user.id,
|
||||
).content.items
|
||||
.firstOrNull()
|
||||
?.id
|
||||
} else {
|
||||
view.id
|
||||
}
|
||||
viewId?.let {
|
||||
val request =
|
||||
GetLatestMediaRequest(
|
||||
fields = SlimItemFields,
|
||||
imageTypeLimit = 1,
|
||||
parentId = viewId,
|
||||
groupItems = true,
|
||||
limit = limit,
|
||||
isPlayed = null, // Server will handle user's preference
|
||||
)
|
||||
val latest =
|
||||
api.userLibraryApi
|
||||
.getLatestMedia(request)
|
||||
.content
|
||||
.map { BaseItem.from(it, api, true) }
|
||||
HomeRowLoadingState.Success(
|
||||
title = title,
|
||||
items = latest,
|
||||
)
|
||||
val viewId =
|
||||
if (view.collectionType == CollectionType.LIVETV) {
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(
|
||||
userId = user.id,
|
||||
).content.items
|
||||
.firstOrNull()
|
||||
?.id
|
||||
} else {
|
||||
view.id
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Exception fetching %s", title)
|
||||
HomeRowLoadingState.Error(
|
||||
title = title,
|
||||
exception = ex,
|
||||
)
|
||||
viewId?.let {
|
||||
val request =
|
||||
GetLatestMediaRequest(
|
||||
fields = SlimItemFields,
|
||||
imageTypeLimit = 1,
|
||||
parentId = viewId,
|
||||
groupItems = true,
|
||||
limit = limit,
|
||||
isPlayed = null, // Server will handle user's preference
|
||||
)
|
||||
LatestData(title, request)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
return latestData
|
||||
}
|
||||
|
||||
private suspend fun loadLatest(latestData: List<LatestData>) {
|
||||
val rows =
|
||||
latestData.map { (title, request) ->
|
||||
try {
|
||||
val latest =
|
||||
api.userLibraryApi
|
||||
.getLatestMedia(request)
|
||||
.content
|
||||
.map { BaseItem.from(it, api, true) }
|
||||
HomeRowLoadingState.Success(
|
||||
title = title,
|
||||
items = latest,
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Exception fetching %s", title)
|
||||
HomeRowLoadingState.Error(
|
||||
title = title,
|
||||
exception = ex,
|
||||
)
|
||||
}
|
||||
}
|
||||
latestRows.setValueOnMain(rows)
|
||||
}
|
||||
|
||||
fun setWatched(
|
||||
|
|
@ -275,3 +289,8 @@ val supportedLatestCollectionTypes =
|
|||
CollectionType.TVSHOWS,
|
||||
CollectionType.LIVETV,
|
||||
)
|
||||
|
||||
data class LatestData(
|
||||
val title: String,
|
||||
val request: GetLatestMediaRequest,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -148,6 +148,20 @@ fun DestinationContent(
|
|||
|
||||
BaseItemKind.USER_VIEW ->
|
||||
when (destination.item?.data?.collectionType) {
|
||||
CollectionType.TVSHOWS ->
|
||||
CollectionFolderTv(
|
||||
preferences,
|
||||
destination,
|
||||
modifier,
|
||||
)
|
||||
|
||||
CollectionType.MOVIES ->
|
||||
CollectionFolderMovie(
|
||||
preferences,
|
||||
destination,
|
||||
modifier,
|
||||
)
|
||||
|
||||
CollectionType.LIVETV ->
|
||||
CollectionFolderLiveTv(
|
||||
preferences,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ import androidx.media3.common.Player
|
|||
import androidx.media3.common.TrackSelectionOverride
|
||||
import androidx.media3.common.Tracks
|
||||
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.data.ItemPlaybackDao
|
||||
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.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.preferences.ShowNextUpWhen
|
||||
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.LoadingExceptionHandler
|
||||
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.TrackSupport
|
||||
import com.github.damontecres.wholphin.util.checkForSupport
|
||||
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.seasonEpisodePadded
|
||||
import com.github.damontecres.wholphin.util.subtitleMimeTypes
|
||||
|
|
@ -65,12 +62,10 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
|
||||
|
|
@ -123,44 +118,11 @@ class PlaybackViewModel
|
|||
val serverRepository: ServerRepository,
|
||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||
val appPreferences: DataStore<AppPreferences>,
|
||||
private val playerFactory: PlayerFactory,
|
||||
) : ViewModel(),
|
||||
Player.Listener {
|
||||
val player by lazy {
|
||||
val prefs = runBlocking { appPreferences.data.firstOrNull()?.playbackPreferences }
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
playerFactory.createVideoPlayer()
|
||||
}
|
||||
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
|
|
@ -76,13 +76,13 @@ class TrackActivityPlaybackListener(
|
|||
fun release() {
|
||||
task.cancel()
|
||||
TIMER.purge()
|
||||
val position = player.currentPosition.milliseconds.inWholeTicks
|
||||
val position = player.currentPosition.milliseconds
|
||||
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
Timber.v("reportPlaybackStopped for ${itemPlayback.itemId}")
|
||||
Timber.v("reportPlaybackStopped for ${itemPlayback.itemId} at $position")
|
||||
api.playStateApi.reportPlaybackStopped(
|
||||
PlaybackStopInfo(
|
||||
itemId = itemPlayback.itemId,
|
||||
positionTicks = position,
|
||||
positionTicks = position.inWholeTicks,
|
||||
failed = false,
|
||||
playSessionId = playback.playSessionId,
|
||||
liveStreamId = playback.liveStreamId,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue