Merge branch 'main' into fea/music

This commit is contained in:
Damontecres 2026-03-06 18:38:17 -05:00
commit 8ddd4bd747
No known key found for this signature in database
23 changed files with 1547 additions and 715 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

@ -251,7 +251,7 @@ suspend fun upgradeApp(
} }
} }
if (previous.isEqualOrBefore(Version.fromString("0.5.0-6-g0"))) { if (previous.isEqualOrBefore(Version.fromString("0.5.3-0-g0"))) {
appPreferences.updateData { appPreferences.updateData {
it.updateScreensaverPreferences { it.updateScreensaverPreferences {
startDelay = ScreensaverPreference.DEFAULT_START_DELAY startDelay = ScreensaverPreference.DEFAULT_START_DELAY

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

@ -161,27 +161,32 @@ class ScreensaverService
if (pager.isEmpty()) { if (pager.isEmpty()) {
emit(null) emit(null)
} else { } else {
val duration =
userPreferencesService
.getCurrent()
.appPreferences
.interfacePreferences.screensaverPreference.duration.milliseconds
while (true) { while (true) {
val item = pager.getBlocking(index) try {
Timber.v("Next index=%s, item=%s", index, item?.id) val item = pager.getBlocking(index)
if (item != null) { Timber.v("Next index=%s, item=%s", index, item?.id)
val backdropUrl = if (item != null) {
if (item.type == BaseItemKind.PHOTO) { val backdropUrl =
api.libraryApi.getDownloadUrl(item.id) if (item.type == BaseItemKind.PHOTO) {
} else { api.libraryApi.getDownloadUrl(item.id)
imageUrlService.getItemImageUrl(item, ImageType.BACKDROP) } else {
} imageUrlService.getItemImageUrl(item, ImageType.BACKDROP)
val title =
if (item.type == BaseItemKind.PHOTO) {
item.data.premiereDate?.let {
formatDate(it.toLocalDate())
} }
} else { val title =
item.title if (item.type == BaseItemKind.PHOTO) {
} item.data.premiereDate?.let {
val logoUrl = imageUrlService.getItemImageUrl(item, ImageType.LOGO) formatDate(it.toLocalDate())
if (backdropUrl != null) { }
try { } else {
item.title
}
val logoUrl = imageUrlService.getItemImageUrl(item, ImageType.LOGO)
if (backdropUrl != null) {
context.imageLoader context.imageLoader
.enqueue( .enqueue(
ImageRequest ImageRequest
@ -191,18 +196,17 @@ class ScreensaverService
).job ).job
.await() .await()
emit(CurrentItem(item, backdropUrl, logoUrl, title ?: "")) emit(CurrentItem(item, backdropUrl, logoUrl, title ?: ""))
} catch (_: CancellationException) { delay(duration)
break
} }
val duration =
userPreferencesService
.getCurrent()
.appPreferences
.interfacePreferences.screensaverPreference.duration.milliseconds
delay(duration)
} }
} catch (_: CancellationException) {
break
} catch (ex: Exception) {
Timber.e(ex, "Error fetching next item")
delay(duration)
} }
index++ index++
if (index > pager.lastIndex) index = 0
} }
} }
}.flowOn(Dispatchers.Default).cancellable() }.flowOn(Dispatchers.Default).cancellable()

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

@ -74,7 +74,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
@ -221,8 +220,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,
@ -241,14 +239,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()
} }
@ -1444,7 +1441,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,429 +88,413 @@ fun SlideshowPage(
), ),
) { ) {
val context = LocalContext.current val context = LocalContext.current
val loading by viewModel.loading.collectAsState()
when (val st = loading) { val loadingState by viewModel.loadingState.observeAsState(ImageLoadingState.Loading)
is LoadingState.Error -> { val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter())
ErrorMessage(st, modifier) val position by viewModel.position.observeAsState(0)
val pager by viewModel.pager.observeAsState()
val imageState by viewModel.image.observeAsState()
var zoomFactor by rememberSaveable { mutableFloatStateOf(1f) }
val isZoomed = zoomFactor * 100 > 102
var rotation by rememberSaveable { mutableFloatStateOf(0f) }
var showOverlay by rememberSaveable { mutableStateOf(false) }
var showFilterDialog by rememberSaveable { mutableStateOf(false) }
var panX by rememberSaveable { mutableFloatStateOf(0f) }
var panY by rememberSaveable { mutableFloatStateOf(0f) }
val slideshowControls =
object : SlideshowControls {
override fun startSlideshow() {
showOverlay = false
viewModel.startSlideshow()
}
override fun stopSlideshow() {
viewModel.stopSlideshow()
}
} }
LoadingState.Loading, val rotateAnimation: Float by animateFloatAsState(
LoadingState.Pending, targetValue = rotation,
-> { label = "image_rotation",
LoadingPage(modifier) )
val zoomAnimation: Float by animateFloatAsState(
targetValue = zoomFactor,
label = "image_zoom",
)
val panXAnimation: Float by animateFloatAsState(
targetValue = panX,
label = "image_panX",
)
val panYAnimation: Float by animateFloatAsState(
targetValue = panY,
label = "image_panY",
)
val slideshowState by viewModel.slideshow.collectAsState()
val slideshowActive by viewModel.slideshowActive.collectAsState(false)
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.tryRequestFocus()
}
val density = LocalDensity.current
val screenHeight = LocalWindowInfo.current.containerSize.height
val screenWidth = LocalWindowInfo.current.containerSize.width
val maxPanX = screenWidth * .75f
val maxPanY = screenHeight * .75f
fun reset(resetRotate: Boolean) {
zoomFactor = 1f
panX = 0f
panY = 0f
if (resetRotate) rotation = 0f
}
fun pan(
xFactor: Int,
yFactor: Int,
) {
if (xFactor != 0) {
panX = (panX + with(density) { xFactor.dp.toPx() }).coerceIn(-maxPanX, maxPanX)
} }
if (yFactor != 0) {
panY = (panY + with(density) { yFactor.dp.toPx() }).coerceIn(-maxPanY, maxPanY)
}
}
LoadingState.Success -> { fun zoom(factor: Float) {
val loadingState by viewModel.loadingState.observeAsState(ImageLoadingState.Loading) if (factor < 0) {
val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter()) val diffFactor = factor / (zoomFactor - 1f)
val position by viewModel.position.observeAsState(0) // zooming out
val pager by viewModel.pager.observeAsState() val panXDiff = abs(panX * diffFactor)
val imageState by viewModel.image.observeAsState() val panYDiff = abs(panY * diffFactor)
if (DEBUG) {
Timber.d(
"zoomFactor=$zoomFactor, factor=$factor, panX=$panX, panY=$panY, panXDiff=$panXDiff, panYDiff=$panYDiff",
)
}
if (panX > 0f) {
panX -= panXDiff
} else if (panX < 0f) {
panX += panXDiff
}
if (panY > 0f) {
panY -= panYDiff
} else if (panY < 0f) {
panY += panYDiff
}
}
zoomFactor = (zoomFactor + factor).coerceIn(1f, 5f)
if (!isZoomed) {
// Always reset if not zoomed
panX = 0f
panY = 0f
}
}
var zoomFactor by rememberSaveable { mutableFloatStateOf(1f) } LaunchedEffect(imageState) {
val isZoomed = zoomFactor * 100 > 102 reset(true)
var rotation by rememberSaveable { mutableFloatStateOf(0f) } }
var showOverlay by rememberSaveable { mutableStateOf(false) } val player = viewModel.player
var showFilterDialog by rememberSaveable { mutableStateOf(false) } val presentationState = rememberPresentationState(player)
var panX by rememberSaveable { mutableFloatStateOf(0f) } LaunchedEffect(slideshowActive) {
var panY by rememberSaveable { mutableFloatStateOf(0f) } player.repeatMode =
if (slideshowState.enabled) Player.REPEAT_MODE_OFF else Player.REPEAT_MODE_ONE
}
val slideshowControls = var longPressing by remember { mutableStateOf(false) }
object : SlideshowControls {
override fun startSlideshow() {
showOverlay = false
viewModel.startSlideshow()
}
override fun stopSlideshow() { val contentModifier =
viewModel.stopSlideshow() Modifier
} .clickable(
} interactionSource = null,
indication = null,
val rotateAnimation: Float by animateFloatAsState( onClick = {
targetValue = rotation, showOverlay = !showOverlay
label = "image_rotation", },
)
val zoomAnimation: Float by animateFloatAsState(
targetValue = zoomFactor,
label = "image_zoom",
)
val panXAnimation: Float by animateFloatAsState(
targetValue = panX,
label = "image_panX",
)
val panYAnimation: Float by animateFloatAsState(
targetValue = panY,
label = "image_panY",
) )
val slideshowState by viewModel.slideshow.collectAsState() Box(
val slideshowActive by viewModel.slideshowActive.collectAsState(false) modifier =
modifier
.background(Color.Black)
.focusRequester(focusRequester)
.focusable()
.onKeyEvent {
val isOverlayShowing = showOverlay || showFilterDialog
var result = false
if (!isOverlayShowing) {
if (longPressing && it.type == KeyEventType.KeyUp) {
// User stopped long pressing, so cancel the zooming action, but still consume the event so it doesn't move the image
longPressing = false
return@onKeyEvent true
}
longPressing =
it.nativeKeyEvent.isLongPress ||
it.nativeKeyEvent.repeatCount > 0
if (longPressing) {
when (it.key) {
Key.DirectionUp -> zoom(.05f)
Key.DirectionDown -> zoom(-.05f)
val focusRequester = remember { FocusRequester() } // These work, but feel awkward because Up/Down zoom, so you can't long press them to pan
// Key.DirectionLeft -> panX += with(density) { 15.dp.toPx() }
LaunchedEffect(Unit) { // Key.DirectionRight -> panX -= with(density) { 15.dp.toPx() }
focusRequester.tryRequestFocus()
}
val density = LocalDensity.current
val screenHeight = LocalWindowInfo.current.containerSize.height
val screenWidth = LocalWindowInfo.current.containerSize.width
val maxPanX = screenWidth * .75f
val maxPanY = screenHeight * .75f
fun reset(resetRotate: Boolean) {
zoomFactor = 1f
panX = 0f
panY = 0f
if (resetRotate) rotation = 0f
}
fun pan(
xFactor: Int,
yFactor: Int,
) {
if (xFactor != 0) {
panX = (panX + with(density) { xFactor.dp.toPx() }).coerceIn(-maxPanX, maxPanX)
}
if (yFactor != 0) {
panY = (panY + with(density) { yFactor.dp.toPx() }).coerceIn(-maxPanY, maxPanY)
}
}
fun zoom(factor: Float) {
if (factor < 0) {
val diffFactor = factor / (zoomFactor - 1f)
// zooming out
val panXDiff = abs(panX * diffFactor)
val panYDiff = abs(panY * diffFactor)
if (DEBUG) {
Timber.d(
"zoomFactor=$zoomFactor, factor=$factor, panX=$panX, panY=$panY, panXDiff=$panXDiff, panYDiff=$panYDiff",
)
}
if (panX > 0f) {
panX -= panXDiff
} else if (panX < 0f) {
panX += panXDiff
}
if (panY > 0f) {
panY -= panYDiff
} else if (panY < 0f) {
panY += panYDiff
}
}
zoomFactor = (zoomFactor + factor).coerceIn(1f, 5f)
if (!isZoomed) {
// Always reset if not zoomed
panX = 0f
panY = 0f
}
}
LaunchedEffect(imageState) {
reset(true)
}
val player = viewModel.player
val presentationState = rememberPresentationState(player)
LaunchedEffect(slideshowActive) {
player.repeatMode =
if (slideshowState.enabled) Player.REPEAT_MODE_OFF else Player.REPEAT_MODE_ONE
}
var longPressing by remember { mutableStateOf(false) }
val contentModifier =
Modifier
.clickable(
interactionSource = null,
indication = null,
onClick = {
showOverlay = !showOverlay
},
)
Box(
modifier =
modifier
.background(Color.Black)
.focusRequester(focusRequester)
.focusable()
.onKeyEvent {
val isOverlayShowing = showOverlay || showFilterDialog
var result = false
if (!isOverlayShowing) {
if (longPressing && it.type == KeyEventType.KeyUp) {
// User stopped long pressing, so cancel the zooming action, but still consume the event so it doesn't move the image
longPressing = false
return@onKeyEvent true
}
longPressing =
it.nativeKeyEvent.isLongPress ||
it.nativeKeyEvent.repeatCount > 0
if (longPressing) {
when (it.key) {
Key.DirectionUp -> zoom(.05f)
Key.DirectionDown -> zoom(-.05f)
// These work, but feel awkward because Up/Down zoom, so you can't long press them to pan
// Key.DirectionLeft -> panX += with(density) { 15.dp.toPx() }
// Key.DirectionRight -> panX -= with(density) { 15.dp.toPx() }
}
return@onKeyEvent true
}
}
if (it.type != KeyEventType.KeyUp) {
result = false
} else if (!isOverlayShowing && isZoomed && isDirectionalDpad(it)) {
// Image is zoomed in
when (it.key) {
Key.DirectionLeft -> pan(30, 0)
Key.DirectionRight -> pan(-30, 0)
Key.DirectionUp -> pan(0, 30)
Key.DirectionDown -> pan(0, -30)
}
result = true
} else if (!isOverlayShowing && isZoomed && it.key == Key.Back) {
reset(false)
result = true
} else if (!isOverlayShowing && (it.key == Key.DirectionLeft || it.key == Key.DirectionRight)) {
when (it.key) {
Key.DirectionLeft, Key.DirectionUpLeft, Key.DirectionDownLeft -> {
if (!viewModel.previousImage()) {
Toast
.makeText(
context,
R.string.slideshow_at_beginning,
Toast.LENGTH_SHORT,
).show()
}
}
Key.DirectionRight, Key.DirectionUpRight, Key.DirectionDownRight -> {
if (!viewModel.nextImage()) {
Toast
.makeText(
context,
R.string.no_more_images,
Toast.LENGTH_SHORT,
).show()
}
}
}
} else if (isOverlayShowing && it.key == Key.Back) {
showOverlay = false
viewModel.unpauseSlideshow()
result = true
} else if (!isOverlayShowing && (isDpad(it) || isEnterKey(it))) {
showOverlay = true
viewModel.pauseSlideshow()
result = true
}
if (result) {
// Handled the key, so reset the slideshow timer
viewModel.pulseSlideshow()
}
result
},
) {
when (loadingState) {
ImageLoadingState.Error -> {
ErrorMessage("Error loading image", null, modifier)
}
ImageLoadingState.Loading -> {
LoadingPage(modifier)
}
is ImageLoadingState.Success -> {
imageState?.let { imageState ->
if (imageState.image.data.mediaType == MediaType.VIDEO) {
LaunchedEffect(imageState.id) {
val mediaItem =
MediaItem
.Builder()
.setUri(imageState.url)
.build()
player.setMediaItem(mediaItem)
player.repeatMode =
if (slideshowState.enabled) {
Player.REPEAT_MODE_OFF
} else {
Player.REPEAT_MODE_ONE
}
player.prepare()
player.play()
viewModel.pulseSlideshow(Long.MAX_VALUE)
}
LifecycleStartEffect(Unit) {
onStopOrDispose {
player.stop()
}
}
val contentScale = ContentScale.Fit
val scaledModifier =
contentModifier.resizeWithContentScale(
contentScale,
presentationState.videoSizeDp,
)
PlayerSurface(
player = player,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier =
scaledModifier
.fillMaxSize()
.graphicsLayer {
scaleX = zoomAnimation
scaleY = zoomAnimation
translationX = panXAnimation
translationY = panYAnimation
}.rotate(rotateAnimation),
)
if (presentationState.coverSurface) {
Box(
Modifier
.matchParentSize()
.background(Color.Black),
)
}
} else {
val colorFilter =
remember(imageState.id, imageFilter) {
if (imageFilter.hasImageFilter()) {
ColorMatrixColorFilter(imageFilter.colorMatrix)
} else {
null
}
}
// If the image loading is large, show the thumbnail while waiting
// TODO
val showLoadingThumbnail = true
SubcomposeAsyncImage(
modifier =
contentModifier
.fillMaxSize()
.graphicsLayer {
scaleX = zoomAnimation
scaleY = zoomAnimation
translationX = panXAnimation
translationY = panYAnimation
val xTransform =
(screenWidth - panXAnimation) / (screenWidth * 2)
val yTransform =
(screenHeight - panYAnimation) / (screenHeight * 2)
if (DEBUG) {
Timber.d(
"graphicsLayer: xTransform=$xTransform, yTransform=$yTransform",
)
}
transformOrigin =
TransformOrigin(xTransform, yTransform)
}.rotate(rotateAnimation),
model =
ImageRequest
.Builder(LocalContext.current)
.data(imageState.url)
.size(Size.ORIGINAL)
.crossfade(!showLoadingThumbnail)
.build(),
contentDescription = null,
contentScale = ContentScale.Fit,
colorFilter = colorFilter,
error = {
Text(
modifier =
Modifier
.align(Alignment.Center),
text = "Error loading image",
color = MaterialTheme.colorScheme.onBackground,
)
},
loading = {
ImageLoadingPlaceholder(
thumbnailUrl = imageState.thumbnailUrl,
showThumbnail = showLoadingThumbnail,
colorFilter = colorFilter,
modifier = Modifier.fillMaxSize(),
)
},
// Ensure that if an image takes a long time to load, it won't be skipped
onLoading = {
viewModel.pulseSlideshow(Long.MAX_VALUE)
},
onSuccess = {
viewModel.pulseSlideshow()
},
onError = {
Timber.e(
it.result.throwable,
"Error loading image ${imageState.id}",
)
Toast
.makeText(
context,
"Error loading image: ${it.result.throwable.localizedMessage}",
Toast.LENGTH_LONG,
).show()
viewModel.pulseSlideshow()
},
)
} }
return@onKeyEvent true
} }
} }
} if (it.type != KeyEventType.KeyUp) {
AnimatedVisibility( result = false
showOverlay, } else if (!isOverlayShowing && isZoomed && isDirectionalDpad(it)) {
enter = slideInVertically { it }, // Image is zoomed in
exit = slideOutVertically { it }, when (it.key) {
modifier = Modifier.align(Alignment.BottomStart), Key.DirectionLeft -> pan(30, 0)
) { Key.DirectionRight -> pan(-30, 0)
imageState?.let { imageState -> Key.DirectionUp -> pan(0, 30)
ImageOverlay( Key.DirectionDown -> pan(0, -30)
}
result = true
} else if (!isOverlayShowing && isZoomed && it.key == Key.Back) {
reset(false)
result = true
} else if (!isOverlayShowing && (it.key == Key.DirectionLeft || it.key == Key.DirectionRight)) {
when (it.key) {
Key.DirectionLeft, Key.DirectionUpLeft, Key.DirectionDownLeft -> {
if (!viewModel.previousImage()) {
Toast
.makeText(
context,
R.string.slideshow_at_beginning,
Toast.LENGTH_SHORT,
).show()
}
}
Key.DirectionRight, Key.DirectionUpRight, Key.DirectionDownRight -> {
if (!viewModel.nextImage()) {
Toast
.makeText(
context,
R.string.no_more_images,
Toast.LENGTH_SHORT,
).show()
}
}
}
} else if (isOverlayShowing && it.key == Key.Back) {
showOverlay = false
viewModel.unpauseSlideshow()
result = true
} else if (!isOverlayShowing && (isDpad(it) || isEnterKey(it))) {
showOverlay = true
viewModel.pauseSlideshow()
result = true
}
if (result) {
// Handled the key, so reset the slideshow timer
viewModel.pulseSlideshow()
}
result
},
) {
when (loadingState) {
ImageLoadingState.Error -> {
ErrorMessage("Error loading image", null, modifier)
}
ImageLoadingState.Loading -> {
LoadingPage(modifier)
}
is ImageLoadingState.Success -> {
imageState?.let { imageState ->
if (imageState.image.data.mediaType == MediaType.VIDEO) {
LaunchedEffect(imageState.id) {
val mediaItem =
MediaItem
.Builder()
.setUri(imageState.url)
.build()
player.setMediaItem(mediaItem)
player.repeatMode =
if (slideshowState.enabled) {
Player.REPEAT_MODE_OFF
} else {
Player.REPEAT_MODE_ONE
}
player.prepare()
player.play()
viewModel.pulseSlideshow(Long.MAX_VALUE)
}
LifecycleStartEffect(Unit) {
onStopOrDispose {
player.stop()
}
}
val contentScale = ContentScale.Fit
val scaledModifier =
contentModifier.resizeWithContentScale(
contentScale,
presentationState.videoSizeDp,
)
PlayerSurface(
player = player,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier =
scaledModifier
.fillMaxSize()
.graphicsLayer {
scaleX = zoomAnimation
scaleY = zoomAnimation
translationX = panXAnimation
translationY = panYAnimation
}.rotate(rotateAnimation),
)
if (presentationState.coverSurface) {
Box(
Modifier
.matchParentSize()
.background(Color.Black),
)
}
} else {
val colorFilter =
remember(imageState.id, imageFilter) {
if (imageFilter.hasImageFilter()) {
ColorMatrixColorFilter(imageFilter.colorMatrix)
} else {
null
}
}
// If the image loading is large, show the thumbnail while waiting
// TODO
val showLoadingThumbnail = true
SubcomposeAsyncImage(
modifier = modifier =
contentModifier contentModifier
.fillMaxWidth() .fillMaxSize()
.background(AppColors.TransparentBlack50), .graphicsLayer {
onDismiss = { showOverlay = false }, scaleX = zoomAnimation
player = player, scaleY = zoomAnimation
slideshowControls = slideshowControls, translationX = panXAnimation
slideshowEnabled = slideshowState.enabled, translationY = panYAnimation
image = imageState,
position = position, val xTransform =
count = pager?.size ?: -1, (screenWidth - panXAnimation) / (screenWidth * 2)
onClickItem = {}, val yTransform =
onLongClickItem = {}, (screenHeight - panYAnimation) / (screenHeight * 2)
onZoom = ::zoom, if (DEBUG) {
onRotate = { rotation += it }, Timber.d(
onReset = { reset(true) }, "graphicsLayer: xTransform=$xTransform, yTransform=$yTransform",
onShowFilterDialogClick = { )
showFilterDialog = true }
showOverlay = false
viewModel.pauseSlideshow() transformOrigin = TransformOrigin(xTransform, yTransform)
}.rotate(rotateAnimation),
model =
ImageRequest
.Builder(LocalContext.current)
.data(imageState.url)
.size(Size.ORIGINAL)
.crossfade(!showLoadingThumbnail)
.build(),
contentDescription = null,
contentScale = ContentScale.Fit,
colorFilter = colorFilter,
error = {
Text(
modifier =
Modifier
.align(Alignment.Center),
text = "Error loading image",
color = MaterialTheme.colorScheme.onBackground,
)
},
loading = {
ImageLoadingPlaceholder(
thumbnailUrl = imageState.thumbnailUrl,
showThumbnail = showLoadingThumbnail,
colorFilter = colorFilter,
modifier = Modifier.fillMaxSize(),
)
},
// Ensure that if an image takes a long time to load, it won't be skipped
onLoading = {
viewModel.pulseSlideshow(Long.MAX_VALUE)
},
onSuccess = {
viewModel.pulseSlideshow()
},
onError = {
Timber.e(
it.result.throwable,
"Error loading image ${imageState.id}",
)
Toast
.makeText(
context,
"Error loading image: ${it.result.throwable.localizedMessage}",
Toast.LENGTH_LONG,
).show()
viewModel.pulseSlideshow()
}, },
) )
} }
} }
AnimatedVisibility(showFilterDialog) {
ImageFilterDialog(
filter = imageFilter,
showVideoOptions = false,
showSaveGalleryButton = true,
onChange = viewModel::updateImageFilter,
onClickSave = viewModel::saveImageFilter,
onClickSaveGallery = viewModel::saveGalleryFilter,
onDismissRequest = {
showFilterDialog = false
viewModel.unpauseSlideshow()
viewModel.pulseSlideshow()
},
)
}
} }
} }
AnimatedVisibility(
showOverlay,
enter = slideInVertically { it },
exit = slideOutVertically { it },
modifier = Modifier.align(Alignment.BottomStart),
) {
imageState?.let { imageState ->
ImageOverlay(
modifier =
contentModifier
.fillMaxWidth()
.background(AppColors.TransparentBlack50),
onDismiss = { showOverlay = false },
player = player,
slideshowControls = slideshowControls,
slideshowEnabled = slideshowState.enabled,
image = imageState,
position = position,
count = pager?.size ?: -1,
onClickItem = {},
onLongClickItem = {},
onZoom = ::zoom,
onRotate = { rotation += it },
onReset = { reset(true) },
onShowFilterDialogClick = {
showFilterDialog = true
showOverlay = false
viewModel.pauseSlideshow()
},
)
}
}
AnimatedVisibility(showFilterDialog) {
ImageFilterDialog(
filter = imageFilter,
showVideoOptions = false,
showSaveGalleryButton = true,
onChange = viewModel::updateImageFilter,
onClickSave = viewModel::saveImageFilter,
onClickSaveGallery = viewModel::saveGalleryFilter,
onDismissRequest = {
showFilterDialog = false
viewModel.unpauseSlideshow()
viewModel.pulseSlideshow()
},
)
}
} }
} }

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,67 +113,58 @@ 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()
}
} }
player.addListener(this@SlideshowViewModel)
viewModelScope.launchIO { viewModelScope.launchIO {
try { val photoPrefs = userPreferencesService.getCurrent().appPreferences.photoPreferences
val appPreferences = userPreferencesService.getCurrent().appPreferences slideshowDelay =
val playerCreation = photoPrefs.slideshowDuration.takeIf { it >= AppPreference.SlideshowDuration.min }
playerFactory.createVideoPlayer( ?: AppPreference.SlideshowDuration.defaultValue
backend = PlayerBackend.EXO_PLAYER, // val album =
appPreferences.playbackPreferences, // api.userLibraryApi
) // .getItem(
player = playerCreation.player // itemId = slideshowSettings.parentId,
player.addListener(this@SlideshowViewModel) // ).content
// .let { BaseItem(it, false) }
val photoPrefs = appPreferences.photoPreferences // this@SlideshowViewModel.album.setValueOnMain(album)
slideshowDelay = val includeItemTypes =
photoPrefs.slideshowDuration.takeIf { it >= AppPreference.SlideshowDuration.min } if (photoPrefs.slideshowPlayVideos) {
?: AppPreference.SlideshowDuration.defaultValue listOf(BaseItemKind.PHOTO, BaseItemKind.VIDEO)
val includeItemTypes = } else {
if (photoPrefs.slideshowPlayVideos) { listOf(BaseItemKind.PHOTO)
listOf(BaseItemKind.PHOTO, BaseItemKind.VIDEO) }
} else { val request =
listOf(BaseItemKind.PHOTO) slideshowSettings.filter.filter.applyTo(
} GetItemsRequest(
val request = parentId = slideshowSettings.parentId,
slideshowSettings.filter.filter.applyTo( includeItemTypes = includeItemTypes,
GetItemsRequest( fields = PhotoItemFields,
parentId = slideshowSettings.parentId, recursive = true,
includeItemTypes = includeItemTypes, sortBy = listOf(slideshowSettings.sortAndDirection.sort),
fields = PhotoItemFields, sortOrder = listOf(slideshowSettings.sortAndDirection.direction),
recursive = true, ),
sortBy = listOf(slideshowSettings.sortAndDirection.sort), )
sortOrder = listOf(slideshowSettings.sortAndDirection.direction), serverRepository.currentUser.value?.let { user ->
), val filter =
) playbackEffectDao
serverRepository.currentUser.value?.let { user -> .getPlaybackEffect(
val filter = user.rowId,
playbackEffectDao slideshowSettings.parentId,
.getPlaybackEffect( BaseItemKind.PHOTO_ALBUM,
user.rowId, )?.videoFilter
slideshowSettings.parentId, if (filter != null) {
BaseItemKind.PHOTO_ALBUM, Timber.v("Got filter for album %s", slideshowSettings.parentId)
)?.videoFilter albumImageFilter = filter
if (filter != null) {
Timber.v("Got filter for album %s", slideshowSettings.parentId)
albumImageFilter = filter
}
} }
val pager =
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
.init(slideshowSettings.index)
this@SlideshowViewModel._pager.setValueOnMain(pager)
loading.update { LoadingState.Success }
updatePosition(slideshowSettings.index)?.join()
if (slideshowSettings.startSlideshow) onMain { startSlideshow() }
} catch (ex: Exception) {
Timber.e(ex, "Error")
loading.update { LoadingState.Error(ex) }
} }
val pager =
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
.init(slideshowSettings.index)
this@SlideshowViewModel._pager.setValueOnMain(pager)
updatePosition(slideshowSettings.index)?.join()
if (slideshowSettings.startSlideshow) onMain { startSlideshow() }
} }
} }

View file

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="about">حول</string>
<string name="active_recordings">التسجيلات النشطة</string>
<string name="add_favorite">إضافة للمفضلة</string>
<string name="add_server">إضافة خادم</string>
<string name="add_user">إضافة مستخدم</string>
<string name="audio">الصوت</string>
<string name="birthplace">مكان الميلاد</string>
<string name="bitrate">معدل البت</string>
<string name="born">وُلد</string>
<string name="cancel_recording">إلغاء التسجيل</string>
<string name="cancel_series_recording">إلغاء تسجيل المسلسل</string>
<string name="cancel">إلغاء</string>
<string name="chapters">الفصول</string>
<string name="choose_stream">اختيار %1$s</string>
<string name="clear_image_cache">مسح ذاكرة التخزين المؤقت للصور</string>
<string name="collection">مجموعة</string>
<string name="collections">المجموعات</string>
<string name="commercial">تجاري</string>
<string name="community_rating">تقييم المجتمع</string>
<string name="compose_error_message_title">حدث خطأ! اضغط على الزر لإرسال السجلات إلى خادمك.</string>
<string name="confirm">تأكيد</string>
<string name="continue_watching">تابع المشاهدة</string>
<string name="critic_rating">تقييم النقاد</string>
<string name="decimal_seconds">%.2f ثانية</string>
<string name="default_track">الافتراضي</string>
<string name="delete">حذف</string>
<string name="died">تُوفي</string>
<string name="directed_by">إخراج %1$s</string>
<string name="director">المخرج</string>
<string name="disabled">مُعطّل</string>
<string name="discovered_servers">الخوادم المكتشفة</string>
<string name="download_and_update"><![CDATA[تنزيل وتحديث]]></string>
<string name="downloading">تنزيل…</string>
<string name="enabled">مُفعّل</string>
<string name="ends_at">ينتهي عند %1$s</string>
<string name="enter_server_url">أدخل عنوان IP للخادم أو رابط URL</string>
<string name="enter_server_address">أدخل عنوان الخادم</string>
<string name="episodes">الحلقات</string>
<string name="error_loading_collection">خطأ في تحميل المجموعة %1$s</string>
<string name="external_track">خارجي</string>
<string name="favorites">المفضلة</string>
<string name="file_size">الحجم</string>
<string name="forced_track">إجبارية</string>
<string name="genres">الأنواع</string>
<string name="go_to_series">انتقل إلى المسلسل</string>
<string name="go_to">انتقل إلى</string>
<string name="hide_controller_timeout">إخفاء عناصر التحكم في التشغيل</string>
<string name="hide_debug_info">إخفاء معلومات تصحيح الأخطاء</string>
<string name="hide">إخفاء</string>
<string name="home">الرئيسية</string>
<string name="immediate">فوري</string>
<string name="intro">المقدمة</string>
<string name="jump_letters">#ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="library">المكتبة</string>
<string name="license_info">معلومات الترخيص</string>
<string name="live_tv">تلفزيون مباشر</string>
<string name="loading">تحميل…</string>
<string name="mark_entire_series_as_played">هل تريد تعيين المسلسل بأكمله كمُشاهَد؟</string>
<string name="mark_entire_series_as_unplayed">هل تريد تعيين المسلسل بأكمله كغير مشاهد؟</string>
<string name="mark_unwatched">تعيين كغير مشاهد</string>
<string name="mark_watched">تعيين كمشاهد</string>
<string name="max_bitrate">الحد الأقصى لمعدل البت</string>
<string name="more_like_this">المزيد من هذا القبيل</string>
<string name="more">المزيد</string>
<plurals name="movies">
<item quantity="zero">الأفلام</item>
<item quantity="one"></item>
<item quantity="two"></item>
<item quantity="few"></item>
<item quantity="many"></item>
<item quantity="other"></item>
</plurals>
<string name="name">الاسم</string>
<string name="next_up">التالي</string>
<string name="no_data">لا توجد بيانات</string>
<string name="no_results">لا توجد نتائج</string>
<string name="no_servers_found">لم يتم العثور على خوادم</string>
<string name="no_scheduled_recordings">لا توجد تسجيلات مجدولة</string>
<string name="no_update_available">لا يوجد تحديث متاح</string>
<string name="none">لا شيء</string>
<string name="only_forced_subtitles">الترجمات الإجبارية فقط</string>
<string name="outro">خاتمة</string>
<string name="path">المسار</string>
<plurals name="people">
<item quantity="zero">الأشخاص</item>
<item quantity="one"></item>
<item quantity="two"></item>
<item quantity="few"></item>
<item quantity="many"></item>
<item quantity="other"></item>
</plurals>
<string name="play_count">عدد مرات التشغيل</string>
<string name="play_from_here">تشغيل من هنا</string>
<string name="play">تشغيل</string>
<string name="playback_debug_info">إظهار معلومات التشغيل التفصيلية</string>
<string name="playback_speed">سرعة التشغيل</string>
<string name="playback">التشغيل</string>
<string name="playlist">قائمة التشغيل</string>
<string name="playlists">قوائم التشغيل</string>
<string name="preview">معاينة</string>
<string name="profile_specific_settings">إعدادات ملف تعريف المستخدم</string>
<string name="queue">قائمة الانتظار</string>
<string name="recap">ملخص ما سبق</string>
<string name="recently_added_in">أُضيف مؤخرًا في %1$s</string>
<string name="recently_added">أُضيف مؤخرًا</string>
<string name="recently_recorded">التسجيلات الأخيرة</string>
<string name="recently_released">أحدث الإصدارات</string>
<string name="recommended">مقترح</string>
<string name="record_program">تسجيل البرنامج</string>
<string name="record_series">تسجيل المسلسل</string>
<string name="remove_favorite">إزالة من المفضلة</string>
<string name="restart">إعادة تشغيل</string>
<string name="resume">استئناف</string>
<string name="save">حفظ</string>
<string name="search_and_download"><![CDATA[بحث وتنزيل]]></string>
<string name="search">بحث</string>
<string name="searching">جارٍ البحث…</string>
<string name="voice_search">البحث الصوتي</string>
<string name="voice_starting">جارٍ البدء</string>
<string name="voice_search_prompt">تحدث للبحث</string>
<string name="processing">جارٍ المعالجة</string>
<string name="press_back_to_cancel">اضغط رجوع للإلغاء</string>
<string name="voice_error_audio">خطأ في تسجيل الصوت</string>
<string name="voice_error_client">خطأ في التعرف على الصوت</string>
<string name="voice_error_permissions">مطلوب إذن الوصول للميكروفون</string>
<string name="voice_error_network">خطأ في الشبكة</string>
<string name="voice_error_network_timeout">انتهت مهلة الشبكة</string>
<string name="voice_error_no_match">لم يتم التعرف على الكلام</string>
</resources>

View file

@ -199,12 +199,12 @@
<string name="italic_font">Kursivschrift</string> <string name="italic_font">Kursivschrift</string>
<string name="runtime_sort">Laufzeit</string> <string name="runtime_sort">Laufzeit</string>
<plurals name="theme_songs"> <plurals name="theme_songs">
<item quantity="one">Theme Songs</item> <item quantity="one">Titelsong</item>
<item quantity="other"/> <item quantity="other">Titelsongs</item>
</plurals> </plurals>
<plurals name="theme_videos"> <plurals name="theme_videos">
<item quantity="one">Theme Videos</item> <item quantity="one">Titelvideo</item>
<item quantity="other"/> <item quantity="other">Titelvideos</item>
</plurals> </plurals>
<plurals name="clips"> <plurals name="clips">
<item quantity="one">Clip</item> <item quantity="one">Clip</item>
@ -224,11 +224,11 @@
</plurals> </plurals>
<plurals name="shorts"> <plurals name="shorts">
<item quantity="one">Shorts</item> <item quantity="one">Shorts</item>
<item quantity="other"/> <item quantity="other">Kurzvideos</item>
</plurals> </plurals>
<string name="combine_continue_next_summary">Trifft nur auf Serien zu</string> <string name="combine_continue_next_summary">Trifft nur auf Serien zu</string>
<string name="direct_play_ass">Direct play ASS subtitles</string> <string name="direct_play_ass">Libass für ASS Untertitel verwenden</string>
<string name="direct_play_pgs">Direct play PGS subtitles</string> <string name="direct_play_pgs">Direktwiedergabe PGS Untertitel</string>
<string name="downmix_stereo">Immer zu Stereo heruntermischen</string> <string name="downmix_stereo">Immer zu Stereo heruntermischen</string>
<string name="ffmpeg_extension_pref">FFmpeg decoder Modul verwenden</string> <string name="ffmpeg_extension_pref">FFmpeg decoder Modul verwenden</string>
<string name="global_content_scale">Standard Skalierung</string> <string name="global_content_scale">Standard Skalierung</string>
@ -251,8 +251,8 @@
<string name="font_size">Schriftgröße</string> <string name="font_size">Schriftgröße</string>
<string name="font_color">Schriftfarbe</string> <string name="font_color">Schriftfarbe</string>
<string name="font_opacity">Schriftart Deckkraft</string> <string name="font_opacity">Schriftart Deckkraft</string>
<string name="edge_style">Rand Stil</string> <string name="edge_style">Stil der Umrandung</string>
<string name="edge_color">Rand Farbe</string> <string name="edge_color">Farbe der Umrandung</string>
<string name="background_opacity">Hintergrund Deckktraft</string> <string name="background_opacity">Hintergrund Deckktraft</string>
<string name="background_style">Hintergrund Stil</string> <string name="background_style">Hintergrund Stil</string>
<string name="subtitle_style">Untertitel Stil</string> <string name="subtitle_style">Untertitel Stil</string>
@ -329,7 +329,7 @@
<string name="use_server_credentials">Login via Server</string> <string name="use_server_credentials">Login via Server</string>
<string name="press_enter_to_confirm">Drücke die mittlere Taste zur Bestätigung</string> <string name="press_enter_to_confirm">Drücke die mittlere Taste zur Bestätigung</string>
<string name="image_cache_size">Größe des Festplatten-Caches für Bilder (MB)</string> <string name="image_cache_size">Größe des Festplatten-Caches für Bilder (MB)</string>
<string name="cast_and_crew">Besetzung &amp; Mitwirkende</string> <string name="cast_and_crew"><![CDATA[Besetzung & Mitwirkende]]></string>
<string name="container">Container</string> <string name="container">Container</string>
<string name="codec">Codec</string> <string name="codec">Codec</string>
<string name="level">Level</string> <string name="level">Level</string>
@ -432,4 +432,122 @@
<string name="play_videos_during_slideshow">Videos während der Diashow abspielen</string> <string name="play_videos_during_slideshow">Videos während der Diashow abspielen</string>
<string name="max_days_next_up">Maximale Tage in \"Als nächstes\"</string> <string name="max_days_next_up">Maximale Tage in \"Als nächstes\"</string>
<string name="quick_connect">Quick Connect</string> <string name="quick_connect">Quick Connect</string>
<plurals name="minutes">
<item quantity="one">%s Minute</item>
<item quantity="other">%s Minuten</item>
</plurals>
<string name="interlaced">Interlaced</string>
<string name="nal">NAL</string>
<string name="quick_connect_summary">Gewähre anderen Geräten Zugriff auf deinen Account</string>
<string name="quick_connect_code">Eingabe Quick Connect Code</string>
<string name="no_limit">Keine Beschränkung</string>
<string name="add_row">Reihe hinzufügen</string>
<string name="genres_in">Genres in %1$s</string>
<string name="recently_released_in">kürzlich veröffentlicht in %1$s</string>
<string name="height">Höhe</string>
<string name="apply_all_rows">Auf alle Reihen anwenden</string>
<string name="add_row_for">Reihe hinzufügen für %1$s</string>
<string name="overwrite_server_settings">Überschreibe Einstellungen auf dem Server?</string>
<string name="overwrite_local_settings">Überschreibe lokale Einstellungen?</string>
<string name="suggestions_for">Vorschläge für %1$s</string>
<string name="send_media_info_log_to_server">Sende Media Info Log zum Server</string>
<string name="customize_home">Anpassung Homepage</string>
<string name="for_episodes">Für Episoden</string>
<string name="display_presets_description">Vorlagen um schnell alle Reihen zu gestalten</string>
<string name="customize_home_summary">Wähle Reihen und Bilder auf der Homepage</string>
<string name="start_after">Starte anschließend</string>
<string name="display_preset_series_thumb">Serien Vorschaubilder</string>
<string name="display_preset_episode_thumbnails">Episoden Vorschaubilder</string>
<string name="seerr_login">Einloggen zum Seerr Server</string>
<string name="seerr_jellyfin_user">Jellyfin User</string>
<string name="show_media_management">Zeige Media Management Optionen</string>
<string name="decrease_all_cards_size">Größe für alle Karten reduzieren</string>
<string name="settings_saved">Einstellungen gespeichert</string>
<string name="max_age_rating">Maximale Altersfreigabe</string>
<string name="no_max">Keine Begrenzung</string>
<string name="for_all_ages">Ohne Altersbeschränkung</string>
<string name="up_to_age">Ab %s Jahre</string>
<string name="screensaver">Bildschirmschoner</string>
<string name="screensaver_settings">Bildschirmschoner-Einstellungen</string>
<string name="start_screensaver">Bildschirmschoner starten</string>
<string name="duration">Länge</string>
<string name="photos">Fotos</string>
<string name="include_types">Arten einschließen</string>
<string name="include_types_summary">Für welche Arten von Einträgen sollen Bilder angezeigt werden</string>
<string name="content_scale_fit">Angepasst</string>
<string name="content_scale_crop">Zugeschnitten</string>
<string name="content_scale_fill">Ausgefüllt</string>
<string name="skip_ignore">Ignorieren</string>
<string name="skip_automatically">Automatisch überspringen</string>
<string name="skip_ask">Vor dem Überspringen nachfragen</string>
<string name="ffmpeg_fallback">FFmpeg nur verwenden, sofern keine anderen Decoder vorhanden sind</string>
<string name="ffmpeg_prefer">FFmpeg statt der vorhandenen Decoder verwenden</string>
<string name="ffmpeg_never">FFmpeg Decoder nie verwenden</string>
<string name="next_up_playback_end">Am Ende einer Playlist</string>
<string name="next_up_outro">Während der Credits/Outro</string>
<string name="white">Weiß</string>
<string name="light_gray">Hellgrau</string>
<string name="dark_gray">Dunkelgrau</string>
<string name="yellow">Gelb</string>
<string name="cyan">Cyan</string>
<string name="magenta">Magenta</string>
<string name="subtitle_edge_outline">Umrandung</string>
<string name="subtitle_edge_shadow">Schatten</string>
<string name="prefer_mpv">MPV bevorzugen</string>
<string name="player_backend_options_subtitles_prefer_mpv">ExoPlayer für die HDR Wiedergabe verwenden</string>
<string name="aspect_ratios_poster">Poster (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">Quadrat (1:1)</string>
<string name="image_type_primary">Primär</string>
<string name="backdrop_style_dynamic">Bild mit dynamischen Farben</string>
<string name="api_key">API Schlüssel</string>
<string name="seerr_local_user">Lokaler Benutzer</string>
<string name="search_for">Suche %s</string>
<string name="display_presets">Design-Vorlagen</string>
<string name="animate">Animieren</string>
<string name="content_scale_fill_width">Breite ausfüllen</string>
<string name="content_scale_fill_height">Höhe ausfüllen</string>
<string name="display_preset_default">Wholphin Standard</string>
<string name="display_preset_compact">Wholphin Kompakt</string>
<string name="volume_lowest">Niedrigste</string>
<string name="volume_low">Niedrig</string>
<string name="volume_medium">mittel</string>
<string name="volume_high">Hoch</string>
<string name="volume_full">Voll</string>
<string name="purple">Lila</string>
<string name="orange">Orange</string>
<string name="black">Schwarz</string>
<string name="backdrop_style_image">Nur Bild</string>
<string name="series_continueing">Aktuell</string>
<string name="in_app_screensaver">Eingebauten Bildschirmschoner nutzen</string>
<string name="delete_item">Bist du sicher, dass du diesen Eintrag löschen möchtest?</string>
<string name="actor">Schauspieler</string>
<string name="composer">Komponist</string>
<string name="writer">Autor</string>
<string name="guest_star">Nebenrolle</string>
<string name="producer">Produzent</string>
<string name="conductor">Dirigent</string>
<string name="lyricist">Liedtext-Schreiber</string>
<string name="artist">Künstler</string>
<string name="favorite_items">%s als Favorit speichern</string>
<string name="home_rows">Startseite - Reihen</string>
<string name="load_from_server">Vom Server Benutzerprofil laden</string>
<string name="save_to_server">Im Server Benutzerprofil speichern</string>
<string name="load_from_web_client">Vom Webclient laden</string>
<string name="increase_all_cards_size">Größe für alle Karten erhöhen</string>
<string name="use_thumb_images">Vorschaubilder benutzen</string>
<string name="no_subtitles_found">Es wurden keine Untertitel gefunden</string>
<string name="arranger">Arrangeur</string>
<string name="engineer">Ingenieur</string>
<string name="bold_blue">Dunkelblau</string>
<string name="image_subtitle_opacity">Untertitel-Transparenz</string>
<string name="image_type_thumb">Vorschaubild</string>
<string name="use_series">Serien Vorschaubilder verwenden</string>
<string name="enter_url_api_key"><![CDATA[URL & API Key eingeben]]></string>
<string name="search_and_download_subtitles"><![CDATA[Untertitel suchen & herunterladen]]></string>
<string name="creator">Ersteller</string>
<string name="background_style_wrap">Gestreckt</string>
<string name="background_style_boxed">Zentriert</string>
<string name="mixer">Mischer</string>
</resources> </resources>

View file

@ -35,7 +35,7 @@
<string name="name">Nombre</string> <string name="name">Nombre</string>
<string name="no_results">Sin resultados</string> <string name="no_results">Sin resultados</string>
<string name="none">Ninguno</string> <string name="none">Ninguno</string>
<string name="outro">Outro</string> <string name="outro">Créditos finales</string>
<string name="people">Personas</string> <string name="people">Personas</string>
<string name="play">Reproducir</string> <string name="play">Reproducir</string>
<string name="playback">Reproducción</string> <string name="playback">Reproducción</string>
@ -80,7 +80,7 @@
<string name="create_playlist">Crear nueva lista de reproducción</string> <string name="create_playlist">Crear nueva lista de reproducción</string>
<string name="add_to_playlist">Agregar a la lista de reproducción</string> <string name="add_to_playlist">Agregar a la lista de reproducción</string>
<string name="one_click_pause">Pausar con un clic</string> <string name="one_click_pause">Pausar con un clic</string>
<string name="one_click_pause_summary_on">Presiona el centro del D-Pad para pausar/reproducir</string> <string name="one_click_pause_summary_on">Pulsa el botón central de la cruceta para reproducir/pausar</string>
<string name="success">Éxito</string> <string name="success">Éxito</string>
<string name="runtime_sort">Duración</string> <string name="runtime_sort">Duración</string>
<string name="extras">Extras</string> <string name="extras">Extras</string>
@ -264,8 +264,8 @@
<string name="skip_back_preference">Retroceder</string> <string name="skip_back_preference">Retroceder</string>
<string name="skip_commercials_behavior">Comportamiento al saltar anuncios</string> <string name="skip_commercials_behavior">Comportamiento al saltar anuncios</string>
<string name="skip_forward_preference">Avanzar</string> <string name="skip_forward_preference">Avanzar</string>
<string name="skip_intro_behavior">Comportamiento al saltar la intro</string> <string name="skip_intro_behavior">Comportamiento al saltar la cabecera</string>
<string name="skip_outro_behavior">Comportamiento al saltar el outro</string> <string name="skip_outro_behavior">Comportamiento al saltar créditos finales</string>
<string name="skip_previews_behavior">Comportamiento al saltar avances</string> <string name="skip_previews_behavior">Comportamiento al saltar avances</string>
<string name="skip_recap_behavior">Comportamiento al saltar resumen</string> <string name="skip_recap_behavior">Comportamiento al saltar resumen</string>
<string name="update_available">Actualización disponible</string> <string name="update_available">Actualización disponible</string>
@ -291,8 +291,8 @@
<string name="skip_segment_commercial">Saltar anuncios</string> <string name="skip_segment_commercial">Saltar anuncios</string>
<string name="skip_segment_preview">Saltar avance</string> <string name="skip_segment_preview">Saltar avance</string>
<string name="skip_segment_recap">Saltar resumen</string> <string name="skip_segment_recap">Saltar resumen</string>
<string name="skip_segment_outro">Saltar outro</string> <string name="skip_segment_outro">Saltar créditos finales</string>
<string name="skip_segment_intro">Saltar intro</string> <string name="skip_segment_intro">Saltar cabecera</string>
<string name="played">Reproducido</string> <string name="played">Reproducido</string>
<string name="filter">FIltrar</string> <string name="filter">FIltrar</string>
<string name="year">Año</string> <string name="year">Año</string>
@ -485,8 +485,8 @@
<string name="content_scale_fill">Rellenar</string> <string name="content_scale_fill">Rellenar</string>
<string name="content_scale_fill_width">Ajustar al ancho</string> <string name="content_scale_fill_width">Ajustar al ancho</string>
<string name="content_scale_fill_height">Ajustar al alto</string> <string name="content_scale_fill_height">Ajustar al alto</string>
<string name="display_preset_series_thumb">Miniaturas de las series</string> <string name="display_preset_series_thumb">Miniatura de la serie</string>
<string name="display_preset_episode_thumbnails">Miniaturas de los episodios</string> <string name="display_preset_episode_thumbnails">Miniatura del episodio</string>
<string name="volume_lowest">Mínimo</string> <string name="volume_lowest">Mínimo</string>
<string name="volume_low">Bajo</string> <string name="volume_low">Bajo</string>
<string name="volume_medium">Medio</string> <string name="volume_medium">Medio</string>
@ -534,4 +534,38 @@
<string name="search_and_download_subtitles"><![CDATA[Buscar y descargar subtítulos]]></string> <string name="search_and_download_subtitles"><![CDATA[Buscar y descargar subtítulos]]></string>
<string name="no_subtitles_found">No se encontraron subtítulos externos</string> <string name="no_subtitles_found">No se encontraron subtítulos externos</string>
<string name="series_continueing">Actualidad</string> <string name="series_continueing">Actualidad</string>
<plurals name="minutes">
<item quantity="one">%s minuto</item>
<item quantity="many">%s minutos</item>
<item quantity="other">%s minutos</item>
</plurals>
<string name="start_after">Comenzar después de</string>
<string name="animate">Animación</string>
<string name="max_age_rating">Clasificación por edad máxima</string>
<string name="for_all_ages">Para todas las edades</string>
<string name="up_to_age">Menores de %s</string>
<string name="screensaver">Salvapantallas</string>
<string name="screensaver_settings">Configuración de salvapantallas</string>
<string name="start_screensaver">Iniciar salvapantallas</string>
<string name="duration">Duración</string>
<string name="photos">Fotos</string>
<string name="include_types">Incluir tipos</string>
<string name="include_types_summary">Qué tipos de elementos mostrar</string>
<string name="in_app_screensaver">Usar salvapantallas de la aplicación</string>
<string name="no_max">Sin límite</string>
<string name="actor">Intérprete</string>
<string name="composer">Compositor</string>
<string name="writer">Guionista</string>
<string name="guest_star">Estrella invitada</string>
<string name="producer">Productor</string>
<string name="conductor">Director musical</string>
<string name="lyricist">Letrista</string>
<string name="arranger">Arreglista</string>
<string name="engineer">Ingeniero de sonido</string>
<string name="mixer">Ingeniero de mezcla</string>
<string name="creator">Creador</string>
<string name="artist">Artista</string>
<string name="delete_item">¿Estás seguro de que quieres eliminar este elemento?</string>
<string name="show_media_management">Mostrar opciones de gestión de medios</string>
<string name="search_for">Buscar %s</string>
</resources> </resources>

View file

@ -221,8 +221,8 @@
<string name="font_opacity">Opacité de la police</string> <string name="font_opacity">Opacité de la police</string>
<string name="edge_style">Style des contours</string> <string name="edge_style">Style des contours</string>
<string name="edge_color">Couleur des contours</string> <string name="edge_color">Couleur des contours</string>
<string name="background_opacity">Opacité de larrière-plan</string> <string name="background_opacity">Opacité de l\'arrière-plan</string>
<string name="background_style">Style de larrière-plan</string> <string name="background_style">Style de l\'arrière-plan</string>
<string name="subtitle_style">Style des sous-titres</string> <string name="subtitle_style">Style des sous-titres</string>
<string name="background_color">Couleur de fond</string> <string name="background_color">Couleur de fond</string>
<string name="reset">Réinitialiser</string> <string name="reset">Réinitialiser</string>
@ -234,10 +234,10 @@
<string name="exoplayer_options">Options ExoPlayer</string> <string name="exoplayer_options">Options ExoPlayer</string>
<string name="skip_segment_unknown">Passer le segment</string> <string name="skip_segment_unknown">Passer le segment</string>
<string name="skip_segment_commercial">Passer les publicités</string> <string name="skip_segment_commercial">Passer les publicités</string>
<string name="skip_segment_preview">Passer laperçu</string> <string name="skip_segment_preview">Passer l\'aperçu</string>
<string name="skip_segment_recap">Passer le récapitulatif</string> <string name="skip_segment_recap">Passer le récapitulatif</string>
<string name="skip_segment_outro">Passer le générique de fin</string> <string name="skip_segment_outro">Passer le générique de fin</string>
<string name="skip_segment_intro">Passer lintro</string> <string name="skip_segment_intro">Passer l\'intro</string>
<string name="played">Lu</string> <string name="played">Lu</string>
<string name="filter">Filtre</string> <string name="filter">Filtre</string>
<string name="year">Année</string> <string name="year">Année</string>
@ -275,7 +275,7 @@
<string name="play_theme_music">Lire la musique de thème</string> <string name="play_theme_music">Lire la musique de thème</string>
<string name="skip_back_on_resume_preference">Revenir en arrière à la reprise de la lecture</string> <string name="skip_back_on_resume_preference">Revenir en arrière à la reprise de la lecture</string>
<string name="skip_commercials_behavior">Comportement de saut des publicités</string> <string name="skip_commercials_behavior">Comportement de saut des publicités</string>
<string name="skip_intro_behavior">Comportement de saut de lintro</string> <string name="skip_intro_behavior">Comportement de saut de l\'intro</string>
<string name="skip_outro_behavior">Comportement de saut du générique de fin</string> <string name="skip_outro_behavior">Comportement de saut du générique de fin</string>
<string name="skip_previews_behavior">Comportement de saut des aperçus</string> <string name="skip_previews_behavior">Comportement de saut des aperçus</string>
<string name="skip_recap_behavior">Comportement de saut du récapitulatif</string> <string name="skip_recap_behavior">Comportement de saut du récapitulatif</string>
@ -289,7 +289,7 @@
<item quantity="many"/> <item quantity="many"/>
<item quantity="other"/> <item quantity="other"/>
</plurals> </plurals>
<string name="combine_continue_next"><![CDATA[Combiner "Continuer à regarder" & "À suivre"]]></string> <string name="combine_continue_next"><![CDATA[Combiner \"Continuer à regarder\" & \"À suivre\"]]></string>
<string name="download_and_update"><![CDATA[Télécharger & Mettre à jour]]></string> <string name="download_and_update"><![CDATA[Télécharger & Mettre à jour]]></string>
<string name="search_and_download"><![CDATA[Rechercher & Télécharger]]></string> <string name="search_and_download"><![CDATA[Rechercher & Télécharger]]></string>
<string name="tv_dvr_schedule">Calendrier du DVR</string> <string name="tv_dvr_schedule">Calendrier du DVR</string>
@ -480,4 +480,89 @@
<string name="display_presets_description">Préréglages intégrés pour styliser rapidement toutes les lignes</string> <string name="display_presets_description">Préréglages intégrés pour styliser rapidement toutes les lignes</string>
<string name="customize_home_summary">Choisissez des lignes et des images sur la page d\'accueil</string> <string name="customize_home_summary">Choisissez des lignes et des images sur la page d\'accueil</string>
<string name="favorite_items">Favori %s</string> <string name="favorite_items">Favori %s</string>
<plurals name="minutes">
<item quantity="one">%s minute</item>
<item quantity="many">%s minutes</item>
<item quantity="other">%s minute</item>
</plurals>
<string name="start_after">Démarrer après</string>
<string name="animate">Animer</string>
<string name="max_age_rating">Classification par âge maximale</string>
<string name="no_max">Pas de maximum</string>
<string name="for_all_ages">Pour tous les âges</string>
<string name="up_to_age">Jusqu\'à l\'âge de %s</string>
<string name="screensaver">Écran de veille</string>
<string name="screensaver_settings">Paramètres de l\'économiseur d\'écran</string>
<string name="start_screensaver">Lancer l\'économiseur d\'écran</string>
<string name="duration">Durée</string>
<string name="photos">Photos</string>
<string name="include_types">Inclure les types</string>
<string name="include_types_summary">Quels types d\'éléments afficher des images pour</string>
<string name="content_scale_fit">Ajustement</string>
<string name="content_scale_crop">Recadrer</string>
<string name="content_scale_fill">Remplir</string>
<string name="content_scale_fill_width">Largeur de remplissage</string>
<string name="content_scale_fill_height">Hauteur de remplissage</string>
<string name="display_preset_default">Wholphin Par défaut</string>
<string name="display_preset_compact">Wholphin Compact</string>
<string name="display_preset_series_thumb">Images miniatures de la série</string>
<string name="display_preset_episode_thumbnails">Images miniatures de l\'épisode</string>
<string name="volume_lowest">Le plus bas</string>
<string name="volume_low">Faible</string>
<string name="volume_medium">Moyen</string>
<string name="volume_high">Haute</string>
<string name="volume_full">Complet</string>
<string name="purple">Violet</string>
<string name="orange">Orange</string>
<string name="bold_blue">Bleu gras</string>
<string name="black">Noir</string>
<string name="skip_ignore">Ignorer</string>
<string name="skip_automatically">Passer automatiquement</string>
<string name="skip_ask">Demander à passer</string>
<string name="ffmpeg_fallback">N\'utilisez FFmpeg que si aucun décodeur intégré n\'existe</string>
<string name="ffmpeg_prefer">Préférez utiliser FFmpeg plutôt que les décodeurs intégrés</string>
<string name="ffmpeg_never">N\'utilisez jamais les décodeurs FFmpeg</string>
<string name="next_up_playback_end">À la fin de la lecture</string>
<string name="next_up_outro">Pendant le générique de fin/outro</string>
<string name="white">Blanc</string>
<string name="light_gray">Gris clair</string>
<string name="dark_gray">Gris foncé</string>
<string name="yellow">Jaune</string>
<string name="cyan">Cyan</string>
<string name="magenta">Magenta</string>
<string name="subtitle_edge_outline">Contour</string>
<string name="subtitle_edge_shadow">Ombre</string>
<string name="background_style_wrap">Envelopper</string>
<string name="background_style_boxed">Encadré</string>
<string name="prefer_mpv">Préférer MPV</string>
<string name="player_backend_options_subtitles_prefer_mpv">Utiliser ExoPlayer pour la lecture HDR</string>
<string name="aspect_ratios_poster">Affiche (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">Carré (1:1)</string>
<string name="image_type_primary">Primaire</string>
<string name="image_type_thumb">Miniature</string>
<string name="backdrop_style_dynamic">Image avec couleur dynamique</string>
<string name="backdrop_style_image">Image uniquement</string>
<string name="enter_url_api_key">Entrez l\'URL et la clé API</string>
<string name="api_key">Clé API</string>
<string name="seerr_login">Se connecter au serveur Seerr</string>
<string name="seerr_jellyfin_user">Utilisateur Jellyfin</string>
<string name="seerr_local_user">Utilisateur local</string>
<string name="search_and_download_subtitles">Rechercher et télécharger des sous-titres</string>
<string name="no_subtitles_found">Aucun sous-titre distant n\'a été trouvé</string>
<string name="series_continueing">Présent</string>
<string name="in_app_screensaver">Utiliser l\'économiseur d\'écran intégré</string>
<string name="actor">Acteur</string>
<string name="composer">Compositeur</string>
<string name="writer">Écrivain</string>
<string name="guest_star">Invité spécial</string>
<string name="producer">Producteur</string>
<string name="conductor">Conducteur</string>
<string name="lyricist">Parolier</string>
<string name="arranger">Arrangeur</string>
<string name="engineer">Ingénieur</string>
<string name="mixer">Mélangeur</string>
<string name="creator">Créateur</string>
<string name="artist">Artiste</string>
</resources> </resources>

View file

@ -242,8 +242,8 @@
<string name="font_size">Ukuran font</string> <string name="font_size">Ukuran font</string>
<string name="font_color">Warna font</string> <string name="font_color">Warna font</string>
<string name="font_opacity">Transparansi font</string> <string name="font_opacity">Transparansi font</string>
<string name="edge_style">Jenis garis tepi</string> <string name="edge_style">Jenis pinggiran</string>
<string name="edge_color">Warna garis tepi</string> <string name="edge_color">Warna pinggiran</string>
<string name="background_opacity">Transparansi latar belakang</string> <string name="background_opacity">Transparansi latar belakang</string>
<string name="background_style">Gaya latar belakang</string> <string name="background_style">Gaya latar belakang</string>
<string name="subtitle_style">Gaya subtitel</string> <string name="subtitle_style">Gaya subtitel</string>
@ -318,7 +318,7 @@
<string name="require_pin_code">Membutuhkan PIN untuk profil</string> <string name="require_pin_code">Membutuhkan PIN untuk profil</string>
<string name="will_remove_pin">PIN akan dihapus</string> <string name="will_remove_pin">PIN akan dihapus</string>
<string name="view_options">Opsi tampilan</string> <string name="view_options">Opsi tampilan</string>
<string name="edge_size">Ukuran garis tepi</string> <string name="edge_size">Ukuran pinggiran</string>
<string name="automatic">Otomatis</string> <string name="automatic">Otomatis</string>
<string name="username_or_password">Gunakan nama pengguna/kata sandi</string> <string name="username_or_password">Gunakan nama pengguna/kata sandi</string>
<string name="subtitle_margin">Batas tepi</string> <string name="subtitle_margin">Batas tepi</string>
@ -446,4 +446,79 @@
<string name="display_presets_description">Preset bawaan untuk kustomisasi cepat semua baris</string> <string name="display_presets_description">Preset bawaan untuk kustomisasi cepat semua baris</string>
<string name="customize_home_summary">Pilih baris dan gambar di halaman beranda</string> <string name="customize_home_summary">Pilih baris dan gambar di halaman beranda</string>
<string name="favorite_items">Favoritkan %s</string> <string name="favorite_items">Favoritkan %s</string>
<string name="artist">Artis</string>
<plurals name="minutes">
<item quantity="other">%s menit</item>
</plurals>
<string name="start_after">Mulai setelah</string>
<string name="animate">Animasi</string>
<string name="max_age_rating">Rating usia maks</string>
<string name="no_max">Tidak ada maks</string>
<string name="for_all_ages">Untuk semua umur</string>
<string name="up_to_age">Maks. usia %s</string>
<string name="screensaver">Screensaver</string>
<string name="screensaver_settings">Pengaturan screensaver</string>
<string name="start_screensaver">Mulai screensaver</string>
<string name="duration">Durasi</string>
<string name="photos">Foto</string>
<string name="include_types">Sertakan jenis</string>
<string name="include_types_summary">Tampilkan gambar untuk jenis item</string>
<string name="display_preset_default">Wholphin Bawaan</string>
<string name="display_preset_compact">Wholphin Ringkas</string>
<string name="display_preset_series_thumb">Gambar Thumbnail Serial</string>
<string name="display_preset_episode_thumbnails">Gambar Thumbnail Episode</string>
<string name="volume_lowest">Terendah</string>
<string name="volume_low">Rendah</string>
<string name="volume_medium">Sedang</string>
<string name="volume_high">Tinggi</string>
<string name="volume_full">Penuh</string>
<string name="purple">Ungu</string>
<string name="orange">Oranye</string>
<string name="bold_blue">Biru Tegas</string>
<string name="black">Hitam</string>
<string name="skip_ignore">Abaikan</string>
<string name="skip_automatically">Lewati otomatis</string>
<string name="skip_ask">Tanya sebelum melewati</string>
<string name="ffmpeg_fallback">Gunakan FFmpeg hanya jika dekoder bawaan tidak ada</string>
<string name="ffmpeg_prefer">Utamakan FFmpeg daripada dekoder bawaan</string>
<string name="ffmpeg_never">Jangan pernah gunakan dekoder FFmpeg</string>
<string name="next_up_playback_end">Di akhir pemutaran</string>
<string name="next_up_outro">Selama kredit akhir/outro</string>
<string name="white">Putih</string>
<string name="light_gray">Abu-abu muda</string>
<string name="dark_gray">Abu-abu tua</string>
<string name="yellow">Kuning</string>
<string name="cyan">Sian</string>
<string name="magenta">Magenta</string>
<string name="subtitle_edge_outline">Garis luar</string>
<string name="prefer_mpv">Utamakan MPV</string>
<string name="player_backend_options_subtitles_prefer_mpv">Gunakan ExoPlayer untuk pemutaran HDR</string>
<string name="aspect_ratios_poster">Poster (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">Persegi (1:1)</string>
<string name="image_type_primary">Utama</string>
<string name="image_type_thumb">Thumbnail</string>
<string name="backdrop_style_dynamic">Gambar dengan warna dinamis</string>
<string name="backdrop_style_image">Hanya gambar</string>
<string name="enter_url_api_key"><![CDATA[Enter URL & API Key]]></string>
<string name="api_key">API Key</string>
<string name="seerr_login">Masuk ke server Seerr</string>
<string name="seerr_jellyfin_user">Pengguna Jellyfin</string>
<string name="seerr_local_user">Pengguna lokal</string>
<string name="search_and_download_subtitles"><![CDATA[Search & download subtitles]]></string>
<string name="no_subtitles_found">Subtitel remote tidak ditemukan</string>
<string name="series_continueing">Tayang</string>
<string name="in_app_screensaver">Gunakan screensaver dalam aplikasi</string>
<string name="actor">Aktor</string>
<string name="composer">Komposer</string>
<string name="writer">Penulis</string>
<string name="guest_star">Bintang tamu</string>
<string name="producer">Produser</string>
<string name="conductor">Konduktor</string>
<string name="lyricist">Penulis lirik</string>
<string name="arranger">Aransemen</string>
<string name="engineer">Teknisi</string>
<string name="mixer">Mixer</string>
<string name="creator">Kreator</string>
</resources> </resources>

View file

@ -480,4 +480,92 @@
<string name="use_series">Usa immagine delle serie</string> <string name="use_series">Usa immagine delle serie</string>
<string name="customize_home_summary">Scegli righe e immagini nella home page</string> <string name="customize_home_summary">Scegli righe e immagini nella home page</string>
<string name="favorite_items">Preferito %s</string> <string name="favorite_items">Preferito %s</string>
<string name="content_scale_fit">Adatta</string>
<string name="content_scale_crop">Ritaglia</string>
<string name="content_scale_fill">Riempi</string>
<string name="content_scale_fill_width">Riempi larghezza</string>
<string name="content_scale_fill_height">Riempi altezza</string>
<string name="display_preset_default">Wholphin predefinito</string>
<string name="display_preset_compact">Wholphin Compatto</string>
<string name="display_preset_series_thumb">Immagini miniatura serie</string>
<string name="display_preset_episode_thumbnails">Immagini miniatura episodio</string>
<string name="volume_lowest">Minimo</string>
<string name="volume_low">Basso</string>
<string name="volume_medium">Medio</string>
<string name="volume_high">Alto</string>
<string name="volume_full">Massimo</string>
<string name="purple">Viola</string>
<string name="orange">Arancione</string>
<string name="bold_blue">Blu intenso</string>
<string name="black">Nero</string>
<string name="skip_ignore">Ignora</string>
<string name="skip_automatically">Salta automaticamente</string>
<string name="skip_ask">Chiedi se saltare</string>
<string name="ffmpeg_fallback">Usa FFmpeg solo se non esiste un decodificatore integrato</string>
<string name="ffmpeg_prefer">Preferisci FFmpeg ai decoder integrati</string>
<string name="ffmpeg_never">Non usare mai i decoder FFmpeg</string>
<string name="next_up_playback_end">Alla fine della riproduzione</string>
<string name="next_up_outro">Durante i titoli di coda</string>
<string name="white">Bianco</string>
<string name="light_gray">Grigio chiaro</string>
<string name="dark_gray">Grigio scuro</string>
<string name="yellow">Giallo</string>
<string name="cyan">Ciano</string>
<string name="magenta">Magenta</string>
<string name="subtitle_edge_outline">Contorno</string>
<string name="subtitle_edge_shadow">Ombra</string>
<string name="background_style_wrap">Avvolgi</string>
<string name="background_style_boxed">Con riquadro</string>
<string name="prefer_mpv">Preferisci MVP</string>
<string name="player_backend_options_subtitles_prefer_mpv">Usa ExoPlayer per la riproduzione HDR</string>
<string name="aspect_ratios_poster">Copertina (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">Quadrato (1:1)</string>
<string name="image_type_primary">Principale</string>
<string name="image_type_thumb">Miniatura</string>
<string name="backdrop_style_dynamic">Immagine con colore dinamico</string>
<string name="backdrop_style_image">Solo immagine</string>
<string name="enter_url_api_key"><![CDATA[Inserisci URL e chiave API]]></string>
<string name="api_key">Chiave API</string>
<string name="seerr_login">Accedi al server Seerr</string>
<string name="seerr_jellyfin_user">Utente Jellyfin</string>
<string name="seerr_local_user">Utente locale</string>
<string name="search_and_download_subtitles"><![CDATA[Cerca e scarica sottotitoli]]></string>
<string name="no_subtitles_found">Nessun sottotitolo remoto trovato</string>
<string name="series_continueing">In corso</string>
<plurals name="minutes">
<item quantity="one">%s minuto</item>
<item quantity="many">%s minuti</item>
<item quantity="other">%s minuti</item>
</plurals>
<string name="start_after">Inizia dopo</string>
<string name="animate">Anima</string>
<string name="max_age_rating">Età massima</string>
<string name="for_all_ages">Per tutte le età</string>
<string name="up_to_age">Fino a %s anni</string>
<string name="screensaver">Salvaschermo</string>
<string name="screensaver_settings">Impostazioni salvaschermo</string>
<string name="start_screensaver">Avvia salvaschermo</string>
<string name="duration">Durata</string>
<string name="photos">Foto</string>
<string name="include_types">Includi tipi</string>
<string name="include_types_summary">Quali tipi di elementi mostrare nelle immagini</string>
<string name="no_max">Nessun massimo</string>
<string name="in_app_screensaver">Usa salvaschermo dell\'app</string>
<string name="composer">Compositore</string>
<string name="writer">Sceneggiatore</string>
<string name="actor">Attore</string>
<string name="producer">Produttore</string>
<string name="guest_star">Ospite speciale</string>
<string name="conductor">Direttore dorchestra</string>
<string name="lyricist">Paroliere</string>
<string name="arranger">Arrangiatore</string>
<string name="engineer">Ingegnere del suono</string>
<string name="mixer">Tecnico di mixaggio</string>
<string name="artist">Artista</string>
<string name="creator">Creatore</string>
<string name="search_for">Cerca %s</string>
<string name="delete_item">Sei sicuro di voler cancellare questo elemento?</string>
<string name="show_media_management">Mostra opzioni di gestione dei media</string>
</resources> </resources>

View file

@ -65,8 +65,8 @@
<string name="more">Mais</string> <string name="more">Mais</string>
<plurals name="movies"> <plurals name="movies">
<item quantity="one">Filmes</item> <item quantity="one">Filmes</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="name">Nome</string> <string name="name">Nome</string>
<string name="next_up">A Seguir</string> <string name="next_up">A Seguir</string>
@ -81,8 +81,8 @@
<string name="path">Caminho</string> <string name="path">Caminho</string>
<plurals name="people"> <plurals name="people">
<item quantity="one">Pessoas</item> <item quantity="one">Pessoas</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="play_count">Contagem de Reproduções</string> <string name="play_count">Contagem de Reproduções</string>
<string name="play_from_here">Reproduzir a partir daqui</string> <string name="play_from_here">Reproduzir a partir daqui</string>
@ -152,8 +152,8 @@
<string name="trailer">Trailer</string> <string name="trailer">Trailer</string>
<plurals name="trailers"> <plurals name="trailers">
<item quantity="one">Trailers</item> <item quantity="one">Trailers</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="tv_dvr_schedule">Agenda do DVR</string> <string name="tv_dvr_schedule">Agenda do DVR</string>
<string name="tv_guide">Guia</string> <string name="tv_guide">Guia</string>
@ -161,8 +161,8 @@
<string name="tv_seasons">Temporadas</string> <string name="tv_seasons">Temporadas</string>
<plurals name="tv_shows"> <plurals name="tv_shows">
<item quantity="one">Programas de TV</item> <item quantity="one">Programas de TV</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="ui_interface">Interface</string> <string name="ui_interface">Interface</string>
<string name="unknown">Desconhecido</string> <string name="unknown">Desconhecido</string>
@ -190,53 +190,53 @@
<string name="extras">Extras</string> <string name="extras">Extras</string>
<plurals name="other_extras"> <plurals name="other_extras">
<item quantity="one">Outro</item> <item quantity="one">Outro</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="behind_the_scenes"> <plurals name="behind_the_scenes">
<item quantity="one">Por Trás das Cenas</item> <item quantity="one">Por Trás das Cenas</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="theme_songs"> <plurals name="theme_songs">
<item quantity="one">Músicas Tema</item> <item quantity="one">Músicas Tema</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="theme_videos"> <plurals name="theme_videos">
<item quantity="one">Vídeos Tema</item> <item quantity="one">Vídeos Tema</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="clips"> <plurals name="clips">
<item quantity="one">Clipes</item> <item quantity="one">Clipes</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="deleted_scenes"> <plurals name="deleted_scenes">
<item quantity="one">Cenas Deletadas</item> <item quantity="one">Cenas Deletadas</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="interviews"> <plurals name="interviews">
<item quantity="one">Entrevistas</item> <item quantity="one">Entrevistas</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="scenes"> <plurals name="scenes">
<item quantity="one">Cenas</item> <item quantity="one">Cenas</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="samples"> <plurals name="samples">
<item quantity="one">Amostras</item> <item quantity="one">Amostras</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="shorts"> <plurals name="shorts">
<item quantity="one">Curtas</item> <item quantity="one">Curtas</item>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="favorite_items">Favoritar %s</string> <string name="favorite_items">Favoritar %s</string>
<plurals name="downloads"> <plurals name="downloads">

View file

@ -121,7 +121,7 @@
<string name="top_unwatched">Mais Votados Não Assistidos</string> <string name="top_unwatched">Mais Votados Não Assistidos</string>
<string name="trailer">Trailer</string> <string name="trailer">Trailer</string>
<plurals name="trailers"> <plurals name="trailers">
<item quantity="one">Trailers</item> <item quantity="one">Trailer</item>
<item quantity="many">Trailers</item> <item quantity="many">Trailers</item>
<item quantity="other">Trailers</item> <item quantity="other">Trailers</item>
</plurals> </plurals>
@ -463,8 +463,8 @@
<string name="apply_all_rows">Aplicar a todas as linhas</string> <string name="apply_all_rows">Aplicar a todas as linhas</string>
<string name="customize_home">Personalizar página inicial</string> <string name="customize_home">Personalizar página inicial</string>
<string name="home_rows">Linhas da página inicial</string> <string name="home_rows">Linhas da página inicial</string>
<string name="load_from_server">Carregar do servidor</string> <string name="load_from_server">Carregar do perfil de utilizador do servidor</string>
<string name="save_to_server">Gravar para o servidor</string> <string name="save_to_server">Guardar no perfil de utilizador do servidor</string>
<string name="load_from_web_client">Carregar a partir do cliente web</string> <string name="load_from_web_client">Carregar a partir do cliente web</string>
<string name="use_series">Usar imagem da série</string> <string name="use_series">Usar imagem da série</string>
<string name="add_row_for">Adicionar linha para %1$s</string> <string name="add_row_for">Adicionar linha para %1$s</string>
@ -479,4 +479,90 @@
<string name="display_presets_description">Predefinições embarcadas para definir todas as linhas rapidamente</string> <string name="display_presets_description">Predefinições embarcadas para definir todas as linhas rapidamente</string>
<string name="customize_home_summary">Escolha as linhas e imagens na página inicial</string> <string name="customize_home_summary">Escolha as linhas e imagens na página inicial</string>
<string name="decrease_all_cards_size">Diminuir o tamanho de todos os cartões</string> <string name="decrease_all_cards_size">Diminuir o tamanho de todos os cartões</string>
<string name="favorite_items">Favoritos %s</string>
<plurals name="minutes">
<item quantity="one">%s minuto</item>
<item quantity="many">%s minutos</item>
<item quantity="other">%s minutos</item>
</plurals>
<string name="start_after">Começar depois</string>
<string name="animate">Animado</string>
<string name="max_age_rating">Idade máxima</string>
<string name="no_max">Sem máximo</string>
<string name="for_all_ages">Para todas as idades</string>
<string name="up_to_age">Até a idade %s</string>
<string name="screensaver">Proteção de ecrã</string>
<string name="screensaver_settings">Definições de protecção de ecrã</string>
<string name="start_screensaver">Iniciar protecção de ecrã</string>
<string name="duration">Duração</string>
<string name="photos">Fotos</string>
<string name="include_types">Incluir tipos</string>
<string name="include_types_summary">Que tipos de itens devem mostrar imagens</string>
<string name="content_scale_fit">Ajustar</string>
<string name="content_scale_crop">Recortar</string>
<string name="content_scale_fill">Preencher</string>
<string name="content_scale_fill_width">Preencher largura</string>
<string name="content_scale_fill_height">Preencher altura</string>
<string name="display_preset_default">Padrão do Wholphin</string>
<string name="display_preset_compact">Wholphin Compacto</string>
<string name="display_preset_series_thumb">Imagens em miniatura da série</string>
<string name="display_preset_episode_thumbnails">Imagens em miniatura do episódio</string>
<string name="volume_lowest">Mínimo</string>
<string name="volume_low">Baixo</string>
<string name="volume_medium">Médio</string>
<string name="volume_high">Alto</string>
<string name="volume_full">Máximo</string>
<string name="purple">Roxo</string>
<string name="orange">Laranja</string>
<string name="bold_blue">Azul intenso</string>
<string name="black">Preto</string>
<string name="skip_ignore">Ignorar</string>
<string name="skip_automatically">Saltar automaticamente</string>
<string name="skip_ask">Pedir para saltar</string>
<string name="ffmpeg_fallback">Usa FFmpeg apenas se não houver um descodificador integrado</string>
<string name="ffmpeg_prefer">Prefere o FFmpeg aos descodificadores integrados</string>
<string name="ffmpeg_never">Nunca usar os descodificadores FFmpeg</string>
<string name="next_up_playback_end">No final da reprodução</string>
<string name="next_up_outro">Durante os créditos finais</string>
<string name="white">Branco</string>
<string name="light_gray">Cinzento claro</string>
<string name="dark_gray">Cinzento escuro</string>
<string name="yellow">Amarelo</string>
<string name="cyan">Ciano</string>
<string name="magenta">Magenta</string>
<string name="subtitle_edge_outline">Contorno</string>
<string name="subtitle_edge_shadow">Sombra</string>
<string name="background_style_wrap">Envolver</string>
<string name="background_style_boxed">Com caixa</string>
<string name="prefer_mpv">Preferir MPV</string>
<string name="player_backend_options_subtitles_prefer_mpv">Usar ExoPlayer para reprodução HDR</string>
<string name="aspect_ratios_poster">Poster (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">Quadrado (1:1)</string>
<string name="image_type_primary">Primária</string>
<string name="image_type_thumb">Miniatura</string>
<string name="backdrop_style_dynamic">Imagem com cor dinâmica</string>
<string name="backdrop_style_image">Apenas imagem</string>
<string name="enter_url_api_key"><![CDATA[Inserir URL e Chave API]]></string>
<string name="api_key">Chave API</string>
<string name="seerr_login">Iniciar sessão no servidor Seerr</string>
<string name="seerr_jellyfin_user">Utilizador Jellyfin</string>
<string name="seerr_local_user">Utilizador local</string>
<string name="search_and_download_subtitles"><![CDATA[Procurar e transferir legendas]]></string>
<string name="no_subtitles_found">Não foram encontradas legendas remotas</string>
<string name="series_continueing">Em curso</string>
<string name="in_app_screensaver">Usar protetor de ecrã da aplicação</string>
<string name="actor">Ator</string>
<string name="composer">Compositor</string>
<string name="writer">Argumentista</string>
<string name="guest_star">Convidado especial</string>
<string name="producer">Produtor</string>
<string name="conductor">Director musicar</string>
<string name="lyricist">Letrista</string>
<string name="arranger">Arranjador</string>
<string name="engineer">Engenheiro de som</string>
<string name="mixer">Técnico de mixagem</string>
<string name="creator">Criador</string>
<string name="artist">Artista</string>
</resources> </resources>

View file

@ -146,9 +146,9 @@
<string name="trailer">Trailer</string> <string name="trailer">Trailer</string>
<plurals name="trailers"> <plurals name="trailers">
<item quantity="one">Trailery</item> <item quantity="one">Trailery</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="tv_dvr_schedule">DVR rozvrh</string> <string name="tv_dvr_schedule">DVR rozvrh</string>
<string name="tv_guide">Sprievodca</string> <string name="tv_guide">Sprievodca</string>
@ -156,9 +156,9 @@
<string name="tv_seasons">Série</string> <string name="tv_seasons">Série</string>
<plurals name="tv_shows"> <plurals name="tv_shows">
<item quantity="one">Seriály</item> <item quantity="one">Seriály</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="ui_interface">Rozhranie</string> <string name="ui_interface">Rozhranie</string>
<string name="unknown">Neznámy</string> <string name="unknown">Neznámy</string>
@ -186,69 +186,69 @@
<string name="extras">Bonusový materiál</string> <string name="extras">Bonusový materiál</string>
<plurals name="other_extras"> <plurals name="other_extras">
<item quantity="one">Ostatné</item> <item quantity="one">Ostatné</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="behind_the_scenes"> <plurals name="behind_the_scenes">
<item quantity="one">Zo zákulisia</item> <item quantity="one">Zo zákulisia</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="theme_songs"> <plurals name="theme_songs">
<item quantity="one">Tématické piesne</item> <item quantity="one">Tématické piesne</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="theme_videos"> <plurals name="theme_videos">
<item quantity="one">Tématické videá</item> <item quantity="one">Tématické videá</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="clips"> <plurals name="clips">
<item quantity="one">Klipy</item> <item quantity="one">Klipy</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="deleted_scenes"> <plurals name="deleted_scenes">
<item quantity="one">Vymazané scény</item> <item quantity="one">Vymazané scény</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="interviews"> <plurals name="interviews">
<item quantity="one">Rozhovory</item> <item quantity="one">Rozhovory</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="scenes"> <plurals name="scenes">
<item quantity="one">Scény</item> <item quantity="one">Scény</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="samples"> <plurals name="samples">
<item quantity="one">Vzorky</item> <item quantity="one">Vzorky</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="featurettes"> <plurals name="featurettes">
<item quantity="one">Krátkometrážne filmy</item> <item quantity="one">Krátkometrážne filmy</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<plurals name="shorts"> <plurals name="shorts">
<item quantity="one">Krátke videá</item> <item quantity="one">Krátke videá</item>
<item quantity="few"></item> <item quantity="few"/>
<item quantity="many"></item> <item quantity="many"/>
<item quantity="other"></item> <item quantity="other"/>
</plurals> </plurals>
<string name="favorite_items">Obľúbené %s</string> <string name="favorite_items">Obľúbené %s</string>
<plurals name="downloads"> <plurals name="downloads">

View file

@ -277,4 +277,49 @@
<string name="enter_pin">Skriv in PIN</string> <string name="enter_pin">Skriv in PIN</string>
<string name="sign_in_auto">Logga in automatiskt</string> <string name="sign_in_auto">Logga in automatiskt</string>
<string name="use_server_credentials">Logga in via server</string> <string name="use_server_credentials">Logga in via server</string>
<string name="enter_server_address">Ange server-adress</string>
<string name="voice_error_network">Nätverksfel</string>
<string name="voice_error_unknown">Okänt fel</string>
<plurals name="minutes">
<item quantity="one">%s minut</item>
<item quantity="other">%s minuter</item>
</plurals>
<string name="bold_font">Fet stil</string>
<string name="dolby_atmos">Dolby Atmos</string>
<string name="discard_change">Avbryt ändringar?</string>
<string name="require_pin_code">Kräv PIN för profilen</string>
<string name="resolution">Upplösning</string>
<string name="language">Språk</string>
<string name="yes">Ja</string>
<string name="no">Nej</string>
<string name="live_tv_repeat">Upprepa</string>
<string name="password">Lösenord</string>
<string name="username">Användarnamn</string>
<string name="creator">Skapare</string>
<string name="engineer">Ingenjör</string>
<string name="actor">Skådespelare</string>
<string name="composer">Kompositör</string>
<string name="writer">Författare</string>
<string name="producer">Producent</string>
<string name="backdrop_style_image">Endast bild</string>
<string name="white">Vit</string>
<string name="light_gray">Ljusgrå</string>
<string name="dark_gray">Mörkgrå</string>
<string name="yellow">Gul</string>
<string name="purple">Lila</string>
<string name="black">Svart</string>
<string name="for_all_ages">För alla åldrar</string>
<string name="up_to_age">Upp till %s år</string>
<string name="screensaver">Skärmsläckare</string>
<string name="screensaver_settings">Inställningar Skärmsläckare</string>
<string name="start_screensaver">Starta skärmsläckare</string>
<string name="photos">Bilder</string>
<string name="settings_saved">Inställningar sparade</string>
<string name="rotate_left">Rotera vänster</string>
<string name="rotate_right">Rotera höger</string>
<string name="zoom_in">Zooma in</string>
<string name="zoom_out">Zooma ut</string>
<string name="no_limit">Ingen gräns</string>
<string name="add_row">Lägg till rad</string>
<string name="press_back_to_cancel">Tryck bakåt för att avbryta</string>
</resources> </resources>

View file

@ -256,7 +256,7 @@
<string name="check_for_updates">Güncellemeleri kontrol et</string> <string name="check_for_updates">Güncellemeleri kontrol et</string>
<string name="combine_continue_next_summary">Yalnızca diziler için geçerlidir</string> <string name="combine_continue_next_summary">Yalnızca diziler için geçerlidir</string>
<string name="combine_continue_next"><![CDATA[İzlemeye Devam Et ve Sıradaki Bölümleri Birleştir]]></string> <string name="combine_continue_next"><![CDATA[İzlemeye Devam Et ve Sıradaki Bölümleri Birleştir]]></string>
<string name="direct_play_ass">ASS altyazıları doğrudan oynat</string> <string name="direct_play_ass">ASS altyazıları için libass kullan</string>
<string name="direct_play_pgs">PGS altyazıları doğrudan oynat</string> <string name="direct_play_pgs">PGS altyazıları doğrudan oynat</string>
<string name="downmix_stereo">Her zaman Stereo\'ya dönüştür</string> <string name="downmix_stereo">Her zaman Stereo\'ya dönüştür</string>
<string name="ffmpeg_extension_pref">FFmpeg kod çözücü kullan</string> <string name="ffmpeg_extension_pref">FFmpeg kod çözücü kullan</string>
@ -472,4 +472,91 @@
<string name="display_presets_description">Tüm satırları hızlıca stilize etmek için hazır ayarlar</string> <string name="display_presets_description">Tüm satırları hızlıca stilize etmek için hazır ayarlar</string>
<string name="favorite_items">Favori %s</string> <string name="favorite_items">Favori %s</string>
<string name="customize_home_summary">Ana sayfadaki satırları ve görselleri seçin</string> <string name="customize_home_summary">Ana sayfadaki satırları ve görselleri seçin</string>
<string name="content_scale_fit">Sığdır</string>
<string name="content_scale_crop">Kırp</string>
<string name="content_scale_fill">Doldur</string>
<string name="content_scale_fill_width">Genişliği Doldur</string>
<string name="content_scale_fill_height">Yüksekliği Doldur</string>
<string name="display_preset_default">Wholphin Varsayılan</string>
<string name="display_preset_compact">Wholphin Kompakt</string>
<string name="display_preset_series_thumb">Dizi Küçük Resimleri</string>
<string name="display_preset_episode_thumbnails">Bölüm Küçük Resimleri</string>
<string name="volume_lowest">En Düşük</string>
<string name="volume_low">Düşük</string>
<string name="volume_medium">Orta</string>
<string name="volume_high">Yüksek</string>
<string name="volume_full">Tam</string>
<string name="purple">Mor</string>
<string name="orange">Turuncu</string>
<string name="bold_blue">Koyu Mavi</string>
<string name="black">Siyah</string>
<string name="skip_ignore">Yoksay</string>
<string name="skip_automatically">Otomatik Atla</string>
<string name="skip_ask">Atlamak için sor</string>
<string name="ffmpeg_fallback">Dahili çözücü yoksa FFmpeg kullan</string>
<string name="ffmpeg_prefer">Dahili çözücüler yerine FFmpeg\'i tercih et</string>
<string name="ffmpeg_never">FFmpeg çözücülerini asla kullanma</string>
<string name="next_up_playback_end">Oynatma sonunda</string>
<string name="next_up_outro">Kapanış bölümünde / Jenerik sırasında</string>
<string name="white">Beyaz</string>
<string name="light_gray">ık Gri</string>
<string name="dark_gray">Koyu Gri</string>
<string name="yellow">Sarı</string>
<string name="cyan">Camgöbeği</string>
<string name="magenta">Macenta</string>
<string name="subtitle_edge_outline">Kenarlık</string>
<string name="subtitle_edge_shadow">Gölge</string>
<string name="background_style_wrap">Kapla</string>
<string name="background_style_boxed">Kutu içinde</string>
<string name="prefer_mpv">MPV\'yi tercih et</string>
<string name="player_backend_options_subtitles_prefer_mpv">HDR oynatma için ExoPlayer kullan</string>
<string name="aspect_ratios_poster">Poster (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">Kare (1:1)</string>
<string name="image_type_primary">Birincil</string>
<string name="image_type_thumb">Küçük Resim</string>
<string name="backdrop_style_dynamic">Dinamik renkli görsel</string>
<string name="backdrop_style_image">Sadece görsel</string>
<string name="enter_url_api_key"><![CDATA[URL ve API Anahtarını Girin]]></string>
<string name="api_key">API Anahtarı</string>
<string name="seerr_login">Seerr sunucusuna giriş yap</string>
<string name="seerr_jellyfin_user">Jellyfin kullanıcısı</string>
<string name="seerr_local_user">Yerel kullanıcı</string>
<string name="search_and_download_subtitles"><![CDATA[Altyazı ara ve indir]]></string>
<string name="no_subtitles_found">Uzak altyazı bulunamadı</string>
<string name="series_continueing">Mevcut</string>
<plurals name="minutes">
<item quantity="one">%s dakika</item>
<item quantity="other">%s dakika</item>
</plurals>
<string name="start_after">Başlama süresi</string>
<string name="animate">Oynatma Süresi</string>
<string name="max_age_rating">Maksimum yaş sınırı</string>
<string name="no_max">Sınır yok</string>
<string name="for_all_ages">Genel İzleyici</string>
<string name="up_to_age">%s yaşa kadar</string>
<string name="screensaver">Ekran koruyucu</string>
<string name="screensaver_settings">Ekran koruyucu ayarları</string>
<string name="start_screensaver">Ekran koruyucuyu başlat</string>
<string name="duration">Süre</string>
<string name="photos">Fotoğraflar</string>
<string name="include_types">İçerik türleri</string>
<string name="include_types_summary">Görsel gösterilecek öğe türleri</string>
<string name="in_app_screensaver">Uygulama içi ekran koruyucuyu kullan</string>
<string name="actor">Aktör</string>
<string name="composer">Besteci</string>
<string name="writer">Yazar</string>
<string name="guest_star">Konuk sanatçı</string>
<string name="producer">Yapımcı</string>
<string name="conductor">Orkestra şefi</string>
<string name="lyricist">Söz yazarı</string>
<string name="arranger">Aranjör</string>
<string name="engineer">Mühendis</string>
<string name="mixer">Karıştırıcı</string>
<string name="creator">Yaratıcı</string>
<string name="artist">Sanatçı</string>
<string name="delete_item">Bu öğeyi silmek istediğinizden emin misiniz?</string>
<string name="show_media_management">Medya yönetim seçeneklerini göster</string>
<string name="search_for">Ara %s</string>
</resources> </resources>

View file

@ -150,7 +150,7 @@
<string name="font">字体</string> <string name="font">字体</string>
<string name="background">背景</string> <string name="background">背景</string>
<string name="success">成功</string> <string name="success">成功</string>
<string name="official_rating">年龄分级</string> <string name="official_rating">内容分级</string>
<string name="runtime_sort">播放时长</string> <string name="runtime_sort">播放时长</string>
<string name="extras">附加内容</string> <string name="extras">附加内容</string>
<plurals name="other_extras"> <plurals name="other_extras">
@ -208,7 +208,7 @@
<string name="check_for_updates">检查更新</string> <string name="check_for_updates">检查更新</string>
<string name="combine_continue_next_summary">仅适用于电视剧</string> <string name="combine_continue_next_summary">仅适用于电视剧</string>
<string name="combine_continue_next"><![CDATA[合并继续观看与即将播放]]></string> <string name="combine_continue_next"><![CDATA[合并继续观看与即将播放]]></string>
<string name="direct_play_ass">直接播放 ASS 字幕</string> <string name="direct_play_ass">使用 libass 处理 ASS 字幕</string>
<string name="direct_play_pgs">直接播放 PGS 字幕</string> <string name="direct_play_pgs">直接播放 PGS 字幕</string>
<string name="downmix_stereo">始终降混为立体声</string> <string name="downmix_stereo">始终降混为立体声</string>
<string name="ffmpeg_extension_pref">使用 FFmpeg 解码模块</string> <string name="ffmpeg_extension_pref">使用 FFmpeg 解码模块</string>
@ -446,4 +446,90 @@
<string name="display_presets_description">可快速设置所有行样式的内置预设</string> <string name="display_presets_description">可快速设置所有行样式的内置预设</string>
<string name="customize_home_summary">选择首页上的行和图片</string> <string name="customize_home_summary">选择首页上的行和图片</string>
<string name="favorite_items">收藏的 %s</string> <string name="favorite_items">收藏的 %s</string>
<string name="content_scale_fit">适应</string>
<string name="content_scale_crop">裁剪</string>
<string name="content_scale_fill">填充</string>
<string name="content_scale_fill_width">填充宽度</string>
<string name="content_scale_fill_height">填充高度</string>
<string name="display_preset_default">Wholphin 默认</string>
<string name="display_preset_compact">Wholphin 紧凑</string>
<string name="display_preset_series_thumb">剧集缩略图</string>
<string name="display_preset_episode_thumbnails">分集缩略图</string>
<string name="purple">紫色</string>
<string name="orange">橙色</string>
<string name="bold_blue">深蓝色</string>
<string name="black">黑色</string>
<string name="skip_ignore">忽略</string>
<string name="skip_automatically">自动跳过</string>
<string name="skip_ask">询问后跳过</string>
<string name="ffmpeg_fallback">仅在无内置解码器时使用 FFmpeg</string>
<string name="ffmpeg_prefer">优先使用 FFmpeg 解码器而非内置解码器</string>
<string name="ffmpeg_never">从不使用 FFmpeg 解码器</string>
<string name="next_up_playback_end">播放结束时</string>
<string name="next_up_outro">播到片尾时</string>
<string name="white">白色</string>
<string name="light_gray">浅灰色</string>
<string name="dark_gray">深灰色</string>
<string name="yellow">黄色</string>
<string name="cyan">青色</string>
<string name="magenta">紫红色</string>
<string name="subtitle_edge_outline">轮廓</string>
<string name="subtitle_edge_shadow">阴影</string>
<string name="prefer_mpv">优先使用 mpv</string>
<string name="player_backend_options_subtitles_prefer_mpv">HDR 播放使用 ExoPlayer</string>
<string name="aspect_ratios_poster">海报 (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">正方形 (1:1)</string>
<string name="image_type_primary">主图</string>
<string name="image_type_thumb">缩略图</string>
<string name="backdrop_style_dynamic">动态配色图片</string>
<string name="backdrop_style_image">仅图片</string>
<string name="enter_url_api_key"><![CDATA[输入 URL 与 API 密钥]]></string>
<string name="api_key">API 密钥</string>
<string name="seerr_login">登录 Seerr 服务器</string>
<string name="seerr_jellyfin_user">Jellyfin 用户</string>
<string name="seerr_local_user">本地用户</string>
<string name="search_and_download_subtitles"><![CDATA[搜索并下载字幕]]></string>
<string name="no_subtitles_found">未找到远程字幕</string>
<string name="volume_lowest">最低音量</string>
<string name="volume_low">低音量</string>
<string name="volume_medium">中等音量</string>
<string name="volume_high">高音量</string>
<string name="volume_full">最大音量</string>
<string name="background_style_wrap">环绕式</string>
<string name="background_style_boxed">框式</string>
<string name="series_continueing">当前</string>
<plurals name="minutes">
<item quantity="other">%s 分钟</item>
</plurals>
<string name="animate">动画</string>
<string name="start_after">启动时间</string>
<string name="max_age_rating">最高年龄分级</string>
<string name="no_max">无上限</string>
<string name="for_all_ages">所有年龄</string>
<string name="up_to_age">最高至 %s 岁</string>
<string name="screensaver">屏幕保护程序</string>
<string name="screensaver_settings">屏幕保护程序设置</string>
<string name="start_screensaver">启动屏幕保护程序</string>
<string name="duration">时长</string>
<string name="photos">照片</string>
<string name="include_types">包含类型</string>
<string name="include_types_summary">要显示图片的项目类型</string>
<string name="in_app_screensaver">使用应用内屏幕保护程序</string>
<string name="actor">演员</string>
<string name="composer">作曲</string>
<string name="writer">编剧</string>
<string name="guest_star">客串</string>
<string name="producer">制片</string>
<string name="conductor">指挥</string>
<string name="lyricist">作词</string>
<string name="arranger">编曲</string>
<string name="engineer">录音</string>
<string name="mixer">混音</string>
<string name="creator">主创</string>
<string name="artist">艺人</string>
<string name="show_media_management">显示媒体管理选项</string>
<string name="delete_item">是否确定要删除此项目?</string>
<string name="search_for">搜索 %s</string>
</resources> </resources>

View file

@ -59,7 +59,7 @@
<string name="tv_guide">指南</string> <string name="tv_guide">指南</string>
<string name="updates">更新</string> <string name="updates">更新</string>
<string name="version">版本</string> <string name="version">版本</string>
<string name="video_scale">畫面比例</string> <string name="video_scale">畫面縮放比例</string>
<string name="video">影片</string> <string name="video">影片</string>
<string name="videos">影片</string> <string name="videos">影片</string>
<string name="watch_live">觀看直播</string> <string name="watch_live">觀看直播</string>
@ -104,7 +104,7 @@
<string name="direct_play_ass">直接播放 ASS 字幕</string> <string name="direct_play_ass">直接播放 ASS 字幕</string>
<string name="direct_play_pgs">直接播放 PGS 字幕</string> <string name="direct_play_pgs">直接播放 PGS 字幕</string>
<string name="ffmpeg_extension_pref">使用 FFmpeg 解碼模組</string> <string name="ffmpeg_extension_pref">使用 FFmpeg 解碼模組</string>
<string name="global_content_scale">預設畫面比例</string> <string name="global_content_scale">預設縮放比例</string>
<string name="install_update">安裝更新</string> <string name="install_update">安裝更新</string>
<string name="installed_version">安裝版本</string> <string name="installed_version">安裝版本</string>
<string name="max_homepage_items">首頁每列項目上限</string> <string name="max_homepage_items">首頁每列項目上限</string>
@ -362,7 +362,7 @@
<string name="remove_seerr_server">移除 Seerr 伺服器</string> <string name="remove_seerr_server">移除 Seerr 伺服器</string>
<string name="seerr_server_added">已新增 Seerr 伺服器</string> <string name="seerr_server_added">已新增 Seerr 伺服器</string>
<string name="password">密碼</string> <string name="password">密碼</string>
<string name="username">使用者名稱</string> <string name="username">帳號</string>
<string name="url">URL</string> <string name="url">URL</string>
<string name="trending">當前趨勢</string> <string name="trending">當前趨勢</string>
<string name="upcoming_movies">即將上映的電影</string> <string name="upcoming_movies">即將上映的電影</string>
@ -427,18 +427,18 @@
<string name="suggestions_for">推薦的 %1$s</string> <string name="suggestions_for">推薦的 %1$s</string>
<string name="increase_all_cards_size">放大所有海報尺寸</string> <string name="increase_all_cards_size">放大所有海報尺寸</string>
<string name="decrease_all_cards_size">縮小所有海報尺寸</string> <string name="decrease_all_cards_size">縮小所有海報尺寸</string>
<string name="use_thumb_images">使用橫縮圖</string> <string name="use_thumb_images">使用橫縮圖</string>
<string name="add_row">新增一列</string> <string name="add_row">新增一列</string>
<string name="apply_all_rows">套用到所有列</string> <string name="apply_all_rows">套用到所有列</string>
<string name="home_rows">首頁各列</string> <string name="home_rows">首頁各列</string>
<string name="add_row_for">為 %1$s 新增列項目</string> <string name="add_row_for">為 %1$s 新增列項目</string>
<string name="display_presets_description">快速設定所有項目為內建預設樣式</string> <string name="display_presets_description">快速設定所有項目為內建預設樣式</string>
<string name="customize_home_summary">選擇首頁要顯示的項目及圖片樣式</string> <string name="customize_home_summary">選擇首頁要顯示的項目及圖片樣式</string>
<string name="height">高度</string> <string name="height">高度</string>
<string name="customize_home">自訂首頁</string> <string name="customize_home">自訂首頁</string>
<string name="load_from_server">從伺服器載入設定</string> <string name="load_from_server">從伺服器載入設定</string>
<string name="save_to_server">保存設定到伺服器</string> <string name="save_to_server">保存設定到伺服器</string>
<string name="load_from_web_client">從網頁版載入</string> <string name="load_from_web_client">從網頁版載入設定</string>
<string name="use_series">使用劇集圖片</string> <string name="use_series">使用劇集圖片</string>
<string name="overwrite_server_settings">覆蓋伺服器上的設定?</string> <string name="overwrite_server_settings">覆蓋伺服器上的設定?</string>
<string name="overwrite_local_settings">覆蓋本地設定?</string> <string name="overwrite_local_settings">覆蓋本地設定?</string>
@ -450,11 +450,11 @@
<string name="content_scale_fill">拉伸填滿</string> <string name="content_scale_fill">拉伸填滿</string>
<string name="content_scale_fill_width">填滿寬度</string> <string name="content_scale_fill_width">填滿寬度</string>
<string name="content_scale_fill_height">填滿高度</string> <string name="content_scale_fill_height">填滿高度</string>
<string name="volume_lowest">最低</string> <string name="volume_lowest">最低音量</string>
<string name="volume_low"></string> <string name="volume_low">音量</string>
<string name="volume_medium">中等</string> <string name="volume_medium">中等音量</string>
<string name="volume_high"></string> <string name="volume_high">音量</string>
<string name="volume_full">最大</string> <string name="volume_full">最大音量</string>
<string name="purple">紫色</string> <string name="purple">紫色</string>
<string name="orange">橘色</string> <string name="orange">橘色</string>
<string name="bold_blue">深藍色</string> <string name="bold_blue">深藍色</string>
@ -472,10 +472,64 @@
<string name="dark_gray">深灰色</string> <string name="dark_gray">深灰色</string>
<string name="yellow">黃色</string> <string name="yellow">黃色</string>
<string name="cyan">青色</string> <string name="cyan">青色</string>
<string name="magenta">紅色</string> <string name="magenta">紅色</string>
<string name="subtitle_edge_outline">外框</string> <string name="subtitle_edge_outline">外框</string>
<string name="subtitle_edge_shadow">陰影</string> <string name="subtitle_edge_shadow">陰影</string>
<string name="background_style_wrap">貼合文字</string> <string name="background_style_wrap">貼合文字</string>
<string name="background_style_boxed">長方底框</string> <string name="background_style_boxed">長方底框</string>
<string name="prefer_mpv">優先使用 MPV</string> <string name="prefer_mpv">優先使用 MPV</string>
<string name="player_backend_options_subtitles_prefer_mpv">僅 HDR 使用 ExoPlayer 播放</string>
<string name="favorite_items">我的最愛的 %s</string>
<string name="display_preset_default">Wholphin 預設樣式</string>
<string name="display_preset_compact">Wholphin 緊湊樣式</string>
<string name="display_preset_series_thumb">劇集縮圖樣式</string>
<string name="display_preset_episode_thumbnails">單集縮圖樣式</string>
<string name="aspect_ratios_poster">海報 (2:3)</string>
<string name="aspect_ratios_16_9">16:9</string>
<string name="aspect_ratios_4_3">4:3</string>
<string name="aspect_ratios_square">正方形 (1:1)</string>
<string name="image_type_primary">主封面</string>
<string name="image_type_thumb">橫式縮圖</string>
<string name="backdrop_style_dynamic">動態色彩背景</string>
<string name="backdrop_style_image">僅背景圖</string>
<string name="enter_url_api_key"><![CDATA[輸入 URL 及 API 金鑰]]></string>
<string name="api_key">API 金鑰</string>
<string name="seerr_login">登入 Seerr 伺服器</string>
<string name="seerr_jellyfin_user">Jellyfin 帳號</string>
<string name="seerr_local_user">Seerr 帳號</string>
<string name="search_and_download_subtitles"><![CDATA[搜尋和下載字幕]]></string>
<string name="no_subtitles_found">未找到遠端字幕</string>
<string name="series_continueing">至今</string>
<plurals name="minutes">
<item quantity="other">%s 分鐘</item>
</plurals>
<string name="start_after">啟動時間</string>
<string name="animate">動態效果</string>
<string name="max_age_rating">年齡分級上限</string>
<string name="no_max">不限制</string>
<string name="for_all_ages">適合所有年齡</string>
<string name="up_to_age">最高至 %s 歲</string>
<string name="screensaver">螢幕保護程式</string>
<string name="screensaver_settings">螢幕保護程式設定</string>
<string name="start_screensaver">立即開始</string>
<string name="duration">切換間隔</string>
<string name="photos">照片</string>
<string name="include_types">包含類型</string>
<string name="include_types_summary">要顯示圖片的類型</string>
<string name="in_app_screensaver">使用內建螢幕保護程式</string>
<string name="guest_star">客串演員</string>
<string name="actor">演員</string>
<string name="composer">作曲</string>
<string name="writer">編劇</string>
<string name="producer">製作人</string>
<string name="conductor">指揮</string>
<string name="lyricist">作詞</string>
<string name="arranger">編曲</string>
<string name="engineer">錄音師</string>
<string name="mixer">混音師</string>
<string name="creator">創作者</string>
<string name="artist">演出者</string>
<string name="delete_item">確定要刪除此項目嗎?</string>
<string name="show_media_management">顯示媒體管理選項</string>
<string name="search_for">搜尋 %s</string>
</resources> </resources>

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" }