Revert "Integrate with libass-android to support SSA/ASS subtitles in ExoPlayer" (#1051)

Unfortunately, there seem to be issues with switching to
`libass-android`, so reverting those changes so I can cut a release and
troubleshoot later.

Reverts damontecres/Wholphin#569
This commit is contained in:
Ray 2026-03-06 16:16:12 -05:00 committed by GitHub
parent ce44e9593b
commit 36162d6fc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 514 additions and 575 deletions

View file

@ -231,7 +231,6 @@ dependencies {
implementation(libs.androidx.media3.exoplayer.dash) implementation(libs.androidx.media3.exoplayer.dash)
implementation(libs.androidx.media3.ui) implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.ui.compose) implementation(libs.androidx.media3.ui.compose)
implementation(libs.ass.media)
implementation(libs.coil.core) implementation(libs.coil.core)
implementation(libs.coil.compose) implementation(libs.coil.compose)

View file

@ -10,28 +10,22 @@ import androidx.datastore.core.DataStore
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.Renderer import androidx.media3.exoplayer.Renderer
import androidx.media3.exoplayer.RenderersFactory
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector import androidx.media3.exoplayer.mediacodec.MediaCodecSelector
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.video.MediaCodecVideoRenderer import androidx.media3.exoplayer.video.MediaCodecVideoRenderer
import androidx.media3.exoplayer.video.VideoRendererEventListener import androidx.media3.exoplayer.video.VideoRendererEventListener
import androidx.media3.extractor.DefaultExtractorsFactory 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.MediaExtensionStatus
import com.github.damontecres.wholphin.preferences.PlaybackPreferences import com.github.damontecres.wholphin.preferences.PlaybackPreferences
import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.util.mpv.MpvPlayer import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.peerless2012.ass.media.AssHandler
import io.github.peerless2012.ass.media.factory.AssRenderersFactory
import io.github.peerless2012.ass.media.kt.withAssMkvSupport
import io.github.peerless2012.ass.media.parser.AssSubtitleParserFactory
import io.github.peerless2012.ass.media.type.AssRenderType
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
@ -52,17 +46,71 @@ class PlayerFactory
var currentPlayer: Player? = null var currentPlayer: Player? = null
private set 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.PREFER_MPV,
PlayerBackend.MPV,
-> {
val enableHardwareDecoding =
prefs?.mpvOptions?.enableHardwareDecoding
?: AppPreference.MpvHardwareDecoding.defaultValue
val useGpuNext =
prefs?.mpvOptions?.useGpuNext
?: AppPreference.MpvGpuNext.defaultValue
MpvPlayer(context, enableHardwareDecoding, useGpuNext)
.apply {
playWhenReady = true
}
}
PlayerBackend.EXO_PLAYER,
PlayerBackend.UNRECOGNIZED,
-> {
val extensions = prefs?.overrides?.mediaExtensionsEnabled
val decodeAv1 = prefs?.overrides?.decodeAv1 == true
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(
WholphinRenderersFactory(context, decodeAv1)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(rendererMode),
).build()
.apply {
playWhenReady = true
}
}
}
currentPlayer = newPlayer
return newPlayer
}
suspend fun createVideoPlayer( suspend fun createVideoPlayer(
backend: PlayerBackend, backend: PlayerBackend,
prefs: PlaybackPreferences, prefs: PlaybackPreferences,
): PlayerCreation { ): Player {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (currentPlayer?.isReleased == false) { if (currentPlayer?.isReleased == false) {
Timber.w("Player was not released before trying to create a new one!") Timber.w("Player was not released before trying to create a new one!")
currentPlayer?.release() currentPlayer?.release()
} }
} }
var assHandler: AssHandler? = null
val newPlayer = val newPlayer =
when (backend) { when (backend) {
PlayerBackend.PREFER_MPV, PlayerBackend.PREFER_MPV,
@ -77,9 +125,8 @@ class PlayerFactory
PlayerBackend.UNRECOGNIZED, PlayerBackend.UNRECOGNIZED,
-> { -> {
val extensions = prefs.overrides.mediaExtensionsEnabled val extensions = prefs.overrides.mediaExtensionsEnabled
val directPlayAss = prefs.overrides.directPlayAss
val decodeAv1 = prefs.overrides.decodeAv1 val decodeAv1 = prefs.overrides.decodeAv1
Timber.v("extensions=$extensions, directPlayAss=$directPlayAss") Timber.v("extensions=$extensions")
val rendererMode = val rendererMode =
when (extensions) { when (extensions) {
MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
@ -87,42 +134,17 @@ class PlayerFactory
MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
} }
val dataSourceFactory = DefaultDataSource.Factory(context)
val extractorsFactory = DefaultExtractorsFactory()
var renderersFactory: RenderersFactory =
WholphinRenderersFactory(context, decodeAv1)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(rendererMode)
val mediaSourceFactory =
if (directPlayAss) {
assHandler = AssHandler(AssRenderType.OVERLAY_OPEN_GL)
val assSubtitleParserFactory = AssSubtitleParserFactory(assHandler)
renderersFactory = AssRenderersFactory(assHandler, renderersFactory)
DefaultMediaSourceFactory(
dataSourceFactory,
extractorsFactory.withAssMkvSupport(
assSubtitleParserFactory,
assHandler,
),
).setSubtitleParserFactory(assSubtitleParserFactory)
} else {
DefaultMediaSourceFactory(
dataSourceFactory,
extractorsFactory,
)
}
ExoPlayer ExoPlayer
.Builder(context) .Builder(context)
.setMediaSourceFactory(mediaSourceFactory) .setRenderersFactory(
.setRenderersFactory(renderersFactory) WholphinRenderersFactory(context, decodeAv1)
.build() .setEnableDecoderFallback(true)
.apply { .setExtensionRendererMode(rendererMode),
assHandler?.init(this) ).build()
}
} }
} }
currentPlayer = newPlayer currentPlayer = newPlayer
return PlayerCreation(newPlayer, assHandler) return newPlayer
} }
} }
@ -135,11 +157,6 @@ val Player.isReleased: Boolean
} }
} }
data class PlayerCreation(
val player: Player,
val assHandler: AssHandler? = null,
)
// Code is adapted from https://github.com/androidx/media/blob/release/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java#L436 // Code is adapted from https://github.com/androidx/media/blob/release/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java#L436
class WholphinRenderersFactory( class WholphinRenderersFactory(
context: Context, context: Context,

View file

@ -1,8 +1,5 @@
package com.github.damontecres.wholphin.ui.playback package com.github.damontecres.wholphin.ui.playback
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.annotation.Dimension import androidx.annotation.Dimension
import androidx.annotation.OptIn import androidx.annotation.OptIn
@ -45,18 +42,16 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.core.view.children
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleStartEffect import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.SubtitleView import androidx.media3.ui.SubtitleView
import androidx.media3.ui.compose.PlayerSurface import androidx.media3.ui.compose.PlayerSurface
@ -88,7 +83,6 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.Media3SubtitleOverride import com.github.damontecres.wholphin.util.Media3SubtitleOverride
import com.github.damontecres.wholphin.util.mpv.MpvPlayer import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import io.github.peerless2012.ass.media.widget.AssSubtitleView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -133,7 +127,8 @@ fun PlaybackPage(
LoadingState.Success -> { LoadingState.Success -> {
val playerState by viewModel.currentPlayer.collectAsState() val playerState by viewModel.currentPlayer.collectAsState()
PlaybackPageContent( PlaybackPageContent(
playerState = playerState!!, player = playerState!!.player,
playerBackend = playerState!!.backend,
preferences = preferences, preferences = preferences,
destination = destination, destination = destination,
viewModel = viewModel, viewModel = viewModel,
@ -146,15 +141,13 @@ fun PlaybackPage(
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@Composable @Composable
fun PlaybackPageContent( fun PlaybackPageContent(
playerState: PlayerState, player: Player,
playerBackend: PlayerBackend,
preferences: UserPreferences, preferences: UserPreferences,
destination: Destination, destination: Destination,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: PlaybackViewModel, viewModel: PlaybackViewModel,
) { ) {
val player = playerState.player
val playerBackend = playerState.backend
val prefs = preferences.appPreferences.playbackPreferences val prefs = preferences.appPreferences.playbackPreferences
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
@ -327,14 +320,10 @@ fun PlaybackPageContent(
.focusRequester(focusRequester) .focusRequester(focusRequester)
.focusable(), .focusable(),
) { ) {
var playerSurfaceSize by remember { mutableStateOf(IntSize.Zero) }
PlayerSurface( PlayerSurface(
player = player, player = player,
surfaceType = SURFACE_TYPE_SURFACE_VIEW, surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier = modifier = scaledModifier,
scaledModifier.onSizeChanged {
playerSurfaceSize = it
},
) )
if (presentationState.coverSurface) { if (presentationState.coverSurface) {
Box( Box(
@ -427,7 +416,7 @@ fun PlaybackPageContent(
remember(subtitleSettings) { subtitleSettings.imageSubtitleOpacity / 100f } remember(subtitleSettings) { subtitleSettings.imageSubtitleOpacity / 100f }
// Subtitles // Subtitles
if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled && !presentationState.coverSurface) { if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled) {
val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f) val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f)
val isImageSubtitles = remember(cues) { cues.firstOrNull()?.bitmap != null } val isImageSubtitles = remember(cues) { cues.firstOrNull()?.bitmap != null }
AndroidView( AndroidView(
@ -438,42 +427,12 @@ fun PlaybackPageContent(
setFixedTextSize(Dimension.SP, it.fontSize.toFloat()) setFixedTextSize(Dimension.SP, it.fontSize.toFloat())
setBottomPaddingFraction(it.margin.toFloat() / 100f) setBottomPaddingFraction(it.margin.toFloat() / 100f)
} }
playerState.assHandler?.let { assHandler ->
if (prefs.overrides.directPlayAss) {
Timber.v("Adding AssSubtitleView")
addView(
AssSubtitleView(context, assHandler).apply {
layoutParams =
FrameLayout
.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
).apply { gravity = Gravity.CENTER }
},
)
}
}
} }
}, },
update = { update = {
it.setCues(cues) it.setCues(cues)
Media3SubtitleOverride(subtitleSettings.calculateEdgeSize(density)) Media3SubtitleOverride(subtitleSettings.calculateEdgeSize(density))
.apply(it) .apply(it)
it.children.firstOrNull { it is AssSubtitleView }?.let {
(it as? AssSubtitleView)?.apply {
val resized =
layoutParams.let { it.width != playerSurfaceSize.width || it.height != playerSurfaceSize.height }
if (resized) {
Timber.v("Resizing AssSubtitleView: $playerSurfaceSize")
layoutParams =
FrameLayout
.LayoutParams(
playerSurfaceSize.width,
playerSurfaceSize.height,
).apply { gravity = Gravity.CENTER }
}
}
}
}, },
onReset = { onReset = {
it.setCues(null) it.setCues(null)

View file

@ -73,7 +73,6 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.peerless2012.ass.media.AssHandler
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -219,8 +218,7 @@ class PlaybackViewModel
isHdr: Boolean, isHdr: Boolean,
is4k: Boolean, is4k: Boolean,
) { ) {
val softwareDecoding = val softwareDecoding = !preferences.appPreferences.playbackPreferences.mpvOptions.enableHardwareDecoding
!preferences.appPreferences.playbackPreferences.mpvOptions.enableHardwareDecoding
val playerBackend = val playerBackend =
when (preferences.appPreferences.playbackPreferences.playerBackend) { when (preferences.appPreferences.playbackPreferences.playerBackend) {
PlayerBackend.UNRECOGNIZED, PlayerBackend.UNRECOGNIZED,
@ -239,14 +237,13 @@ class PlaybackViewModel
disconnectPlayer() disconnectPlayer()
} }
val playerCreation = player =
playerFactory.createVideoPlayer( playerFactory.createVideoPlayer(
playerBackend, playerBackend,
preferences.appPreferences.playbackPreferences, preferences.appPreferences.playbackPreferences,
) )
this.player = playerCreation.player
currentPlayer.update { currentPlayer.update {
PlayerState(playerCreation.player, playerBackend, playerCreation.assHandler) PlayerState(player, playerBackend)
} }
configurePlayer() configurePlayer()
} }
@ -1441,7 +1438,6 @@ class PlaybackViewModel
data class PlayerState( data class PlayerState(
val player: Player, val player: Player,
val backend: PlayerBackend, val backend: PlayerBackend,
val assHandler: AssHandler?,
) )
data class MediaSegmentState( data class MediaSegmentState(

View file

@ -67,7 +67,6 @@ import com.github.damontecres.wholphin.ui.playback.isDirectionalDpad
import com.github.damontecres.wholphin.ui.playback.isDpad import com.github.damontecres.wholphin.ui.playback.isDpad
import com.github.damontecres.wholphin.ui.playback.isEnterKey import com.github.damontecres.wholphin.ui.playback.isEnterKey
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.LoadingState
import org.jellyfin.sdk.model.api.MediaType import org.jellyfin.sdk.model.api.MediaType
import timber.log.Timber import timber.log.Timber
import kotlin.math.abs import kotlin.math.abs
@ -89,20 +88,7 @@ fun SlideshowPage(
), ),
) { ) {
val context = LocalContext.current val context = LocalContext.current
val loading by viewModel.loading.collectAsState()
when (val st = loading) {
is LoadingState.Error -> {
ErrorMessage(st, modifier)
}
LoadingState.Loading,
LoadingState.Pending,
-> {
LoadingPage(modifier)
}
LoadingState.Success -> {
val loadingState by viewModel.loadingState.observeAsState(ImageLoadingState.Loading) val loadingState by viewModel.loadingState.observeAsState(ImageLoadingState.Loading)
val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter()) val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter())
val position by viewModel.position.observeAsState(0) val position by viewModel.position.observeAsState(0)
@ -409,8 +395,7 @@ fun SlideshowPage(
) )
} }
transformOrigin = transformOrigin = TransformOrigin(xTransform, yTransform)
TransformOrigin(xTransform, yTransform)
}.rotate(rotateAnimation), }.rotate(rotateAnimation),
model = model =
ImageRequest ImageRequest
@ -512,6 +497,4 @@ fun SlideshowPage(
) )
} }
} }
}
}
} }

View file

@ -16,7 +16,6 @@ import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.PlaybackEffect import com.github.damontecres.wholphin.data.model.PlaybackEffect
import com.github.damontecres.wholphin.data.model.VideoFilter import com.github.damontecres.wholphin.data.model.VideoFilter
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.PlayerFactory import com.github.damontecres.wholphin.services.PlayerFactory
import com.github.damontecres.wholphin.services.ScreensaverService import com.github.damontecres.wholphin.services.ScreensaverService
@ -31,7 +30,6 @@ import com.github.damontecres.wholphin.ui.util.ThrottledLiveData
import com.github.damontecres.wholphin.util.ApiRequestPager import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -79,8 +77,9 @@ class SlideshowViewModel
fun create(slideshow: Destination.Slideshow): SlideshowViewModel fun create(slideshow: Destination.Slideshow): SlideshowViewModel
} }
lateinit var player: Player val player by lazy {
private set playerFactory.createVideoPlayer()
}
private var saveFilters = true private var saveFilters = true
@ -105,8 +104,6 @@ class SlideshowViewModel
private val _image = MutableLiveData<ImageState>() private val _image = MutableLiveData<ImageState>()
val image: LiveData<ImageState> = _image val image: LiveData<ImageState> = _image
val loading = MutableStateFlow<LoadingState>(LoadingState.Pending)
val loadingState = MutableLiveData<ImageLoadingState>(ImageLoadingState.Loading) val loadingState = MutableLiveData<ImageLoadingState>(ImageLoadingState.Loading)
private val _imageFilter = MutableLiveData(VideoFilter()) private val _imageFilter = MutableLiveData(VideoFilter())
val imageFilter = ThrottledLiveData(_imageFilter, 500L) val imageFilter = ThrottledLiveData(_imageFilter, 500L)
@ -116,26 +113,22 @@ class SlideshowViewModel
init { init {
addCloseable { addCloseable {
screensaverService.keepScreenOn(false) screensaverService.keepScreenOn(false)
if (this@SlideshowViewModel::player.isInitialized) {
player.removeListener(this@SlideshowViewModel) player.removeListener(this@SlideshowViewModel)
player.release() player.release()
} }
}
viewModelScope.launchIO {
try {
val appPreferences = userPreferencesService.getCurrent().appPreferences
val playerCreation =
playerFactory.createVideoPlayer(
backend = PlayerBackend.EXO_PLAYER,
appPreferences.playbackPreferences,
)
player = playerCreation.player
player.addListener(this@SlideshowViewModel) player.addListener(this@SlideshowViewModel)
viewModelScope.launchIO {
val photoPrefs = appPreferences.photoPreferences val photoPrefs = userPreferencesService.getCurrent().appPreferences.photoPreferences
slideshowDelay = slideshowDelay =
photoPrefs.slideshowDuration.takeIf { it >= AppPreference.SlideshowDuration.min } photoPrefs.slideshowDuration.takeIf { it >= AppPreference.SlideshowDuration.min }
?: AppPreference.SlideshowDuration.defaultValue ?: AppPreference.SlideshowDuration.defaultValue
// val album =
// api.userLibraryApi
// .getItem(
// itemId = slideshowSettings.parentId,
// ).content
// .let { BaseItem(it, false) }
// this@SlideshowViewModel.album.setValueOnMain(album)
val includeItemTypes = val includeItemTypes =
if (photoPrefs.slideshowPlayVideos) { if (photoPrefs.slideshowPlayVideos) {
listOf(BaseItemKind.PHOTO, BaseItemKind.VIDEO) listOf(BaseItemKind.PHOTO, BaseItemKind.VIDEO)
@ -170,13 +163,8 @@ class SlideshowViewModel
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
.init(slideshowSettings.index) .init(slideshowSettings.index)
this@SlideshowViewModel._pager.setValueOnMain(pager) this@SlideshowViewModel._pager.setValueOnMain(pager)
loading.update { LoadingState.Success }
updatePosition(slideshowSettings.index)?.join() updatePosition(slideshowSettings.index)?.join()
if (slideshowSettings.startSlideshow) onMain { startSlideshow() } if (slideshowSettings.startSlideshow) onMain { startSlideshow() }
} catch (ex: Exception) {
Timber.e(ex, "Error")
loading.update { LoadingState.Error(ex) }
}
} }
} }

View file

@ -312,7 +312,7 @@
<string name="check_for_updates">Check for updates</string> <string name="check_for_updates">Check for updates</string>
<string name="combine_continue_next_summary">Applies to TV Series only</string> <string name="combine_continue_next_summary">Applies to TV Series only</string>
<string name="combine_continue_next"><![CDATA[Combine Continue Watching & Next Up]]></string> <string name="combine_continue_next"><![CDATA[Combine Continue Watching & Next Up]]></string>
<string name="direct_play_ass">Use libass for ASS subtitles</string> <string name="direct_play_ass">Direct play ASS subtitles</string>
<string name="direct_play_pgs">Direct play PGS subtitles</string> <string name="direct_play_pgs">Direct play PGS subtitles</string>
<string name="downmix_stereo">Always downmix to stereo</string> <string name="downmix_stereo">Always downmix to stereo</string>
<string name="ffmpeg_extension_pref">Use FFmpeg decoder module</string> <string name="ffmpeg_extension_pref">Use FFmpeg decoder module</string>

View file

@ -40,7 +40,6 @@ preferenceKtx = "1.2.1"
tvprovider = "1.1.0" tvprovider = "1.1.0"
workRuntimeKtx = "2.11.1" workRuntimeKtx = "2.11.1"
paletteKtx = "1.0.0" paletteKtx = "1.0.0"
assMedia = "0.4.0"
kotlinxCoroutinesTest = "1.10.2" kotlinxCoroutinesTest = "1.10.2"
coreTesting = "2.2.0" coreTesting = "2.2.0"
openapi-generator = "7.20.0" openapi-generator = "7.20.0"
@ -135,8 +134,6 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t
androidx-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "coreTesting" } androidx-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "coreTesting" }
androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" } androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
ass-media = { group = "io.github.peerless2012", name = "ass-media", version.ref = "assMedia" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }