Skip to content
Open
2 changes: 1 addition & 1 deletion app/src/main/java/org/jellyfin/androidtv/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ val appModule = module {
viewModel { ServerAddViewModel(get()) }
viewModel { NextUpViewModel(get(), get(), get()) }
viewModel { StillWatchingViewModel(get(), get(), get(), get()) }
viewModel { PhotoPlayerViewModel(get()) }
viewModel { PhotoPlayerViewModel(get(), get()) }
viewModel { SearchViewModel(get()) }
viewModel { DreamViewModel(get(), get(), get(), get(), get()) }
viewModel { SettingsViewModel() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
* Enable the use of software-based codecs.
*/
var softwareCodecsEnabled = booleanPreference("software_codecs_enabled", true)

/**
* Stores the interval for the photo player.
*/
var photoPlayerPresentationDelay = longPreference("photo_player_presentation_delay", 8000)
}

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.data.repository.ItemRepository
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
Expand All @@ -19,9 +20,11 @@ import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
import java.util.UUID
import kotlin.time.Duration.Companion.seconds

class PhotoPlayerViewModel(private val api: ApiClient) : ViewModel() {
class PhotoPlayerViewModel(
private val api: ApiClient,
private val userPreferences: UserPreferences
) : ViewModel() {
private var album: List<BaseItemDto> = emptyList()
private var albumIndex = -1

Expand Down Expand Up @@ -82,11 +85,10 @@ class PhotoPlayerViewModel(private val api: ApiClient) : ViewModel() {
private var presentationJob: Job? = null
private val _presentationActive = MutableStateFlow(false)
val presentationActive = _presentationActive.asStateFlow()
var presentationDelay = 8.seconds

fun createPresentationJob() = viewModelScope.launch(Dispatchers.IO) {
while (isActive) {
delay(presentationDelay)
delay(userPreferences[UserPreferences.photoPlayerPresentationDelay])
showNext()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackHEVCLe
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackInactivityPromptScreen
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackMaxBitrateScreen
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackPlayerScreen
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackPhotoPlayerScreen
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackPrerollsScreen
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackRefreshRateSwitchingBehaviorScreen
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackResumeSubtractDurationScreen
Expand Down Expand Up @@ -87,6 +88,7 @@ object Routes {
const val LIVETV_GUIDE_CHANNEL_ORDER = "/livetv/guide/channel-order"
const val PLAYBACK = "/playback"
const val PLAYBACK_PLAYER = "/playback/player"
const val PLAYBACK_PHOTO_PLAYER = "/playback/photo-player"
const val PLAYBACK_NEXT_UP = "/playback/next-up"
const val PLAYBACK_NEXT_UP_BEHAVIOR = "/playback/next-up/behavior"
const val PLAYBACK_INACTIVITY_PROMPT = "/playback/inactivity-prompt"
Expand Down Expand Up @@ -215,6 +217,9 @@ val routes = mapOf<String, RouteComposable>(
Routes.PLAYBACK_PLAYER to {
SettingsPlaybackPlayerScreen()
},
Routes.PLAYBACK_PHOTO_PLAYER to {
SettingsPlaybackPhotoPlayerScreen()
},
Routes.PLAYBACK_NEXT_UP to {
SettingsPlaybackNextUpScreen()
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.jellyfin.androidtv.ui.settings.screen.playback

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.ui.base.Text
import org.jellyfin.androidtv.ui.base.form.RangeControl
import org.jellyfin.androidtv.ui.base.list.ListControl
import org.jellyfin.androidtv.ui.base.list.ListSection
import org.jellyfin.androidtv.ui.navigation.focus.focusKey
import org.jellyfin.androidtv.ui.settings.compat.rememberPreference
import org.jellyfin.androidtv.ui.settings.composable.SettingsColumn
import org.jellyfin.design.Tokens
import org.koin.compose.koinInject

@Composable
fun SettingsPlaybackPhotoPlayerScreen() {
val userPreferences = koinInject<UserPreferences>()

SettingsColumn {
item {
ListSection(
overlineContent = { Text(stringResource(R.string.pref_playback).uppercase()) },
headingContent = { Text(stringResource(R.string.photo_player)) },
)
}

item {
var photoPlayerPresentationDelay by rememberPreference(userPreferences, UserPreferences.photoPlayerPresentationDelay)
val interactionSource = remember { MutableInteractionSource() }

ListControl(
headingContent = { Text(stringResource(R.string.photo_display_duration)) },
captionContent = { Text(stringResource(R.string.photo_display_duration_description)) },
interactionSource = interactionSource,
modifier = Modifier.focusKey("photo_display_duration")
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
RangeControl(
modifier = Modifier
.height(4.dp)
.weight(1f),
interactionSource = interactionSource,
// 4 - 120 seconds with 2 second increment
min = 4_000f,
max = 120_000f,
stepForward = 2_000f,
value = photoPlayerPresentationDelay.toFloat(),
onValueChange = { photoPlayerPresentationDelay = it.toLong() }
)

Spacer(Modifier.width(Tokens.Space.spaceSm))

Box(
modifier = Modifier.sizeIn(minWidth = 32.dp),
contentAlignment = Alignment.CenterEnd
) {
Text("${photoPlayerPresentationDelay / 1000}s")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same implementation of formatting value as seconds is found in SettingsPlaybackAdvancedScreen.kt and SettingsPlaybackNextUpScreen.kt. Should all be updated or this one ignored.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New timeUtils function to convert millis to seconds added to remove usage of magic number for displaying seconds.

}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ fun SettingsPlaybackScreen() {
)
}

item {
ListButton(
leadingContent = { Icon(painterResource(R.drawable.ic_photo), contentDescription = null) },
headingContent = { Text(stringResource(R.string.photo_player)) },
onClick = { router.push(Routes.PLAYBACK_PHOTO_PLAYER) },
modifier = Modifier.focusKey(Routes.PLAYBACK_PHOTO_PLAYER)
)
}

item {
ListButton(
leadingContent = { Icon(painterResource(R.drawable.ic_next_up), contentDescription = null) },
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@
<string name="hlg">HLG</string>
<string name="pref_software_codecs_enabled">Use software codecs</string>
<string name="pref_software_codecs_enabled_description">Some software-based implementations may degrade performance. Disabling this option makes transcoding more likely.</string>
<string name="photo_player">Photo player</string>
<string name="photo_display_duration">Photo display duration</string>
<string name="photo_display_duration_description">How long to wait before showing the next photo</string>
<string name="truehd">TrueHD</string>
<string name="eac3_atmos">DD+ Atmos</string>
<string name="truehd_atmos">TrueHD Atmos</string>
Expand Down