Search, favourites, person page and settings
This commit is contained in:
@@ -2,7 +2,9 @@ package ru.stenfredd.movies.playback.core
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -12,6 +14,7 @@ import kotlinx.coroutines.flow.map
|
||||
import org.json.JSONObject
|
||||
import ru.stenfredd.movies.core.jellyfin.JellyfinSession
|
||||
import ru.stenfredd.movies.playback.api.SubtitleStyle
|
||||
import ru.stenfredd.movies.playback.api.VideoPathSwitchable
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
@@ -32,12 +35,22 @@ data class TrackPick(val language: String?, val name: String?, val codec: String
|
||||
?.first
|
||||
}
|
||||
|
||||
/**
|
||||
* Playback defaults from the settings screen, for everyone on this TV (they depend on the TV and the network).
|
||||
* [maxBitrate] null = the original file; [dolbyVision] = decode Dolby Vision files with the DV decoder.
|
||||
*/
|
||||
data class PlaybackDefaults(
|
||||
val maxBitrate: Int? = null,
|
||||
val videoPath: VideoPathSwitchable.Mode = VideoPathSwitchable.Mode.AUTO,
|
||||
val dolbyVision: Boolean = false,
|
||||
)
|
||||
|
||||
/** What the user picked last for a movie or a series. [subtitle] null with [subtitleOff] = subtitles off. */
|
||||
data class RememberedTracks(val audio: TrackPick?, val subtitle: TrackPick?, val subtitleOff: Boolean)
|
||||
|
||||
/**
|
||||
* Player settings kept on the device: the look of text subtitles (for everyone on this TV) and the tracks chosen
|
||||
* per movie or series (per account: family members watch with different tracks).
|
||||
* Player settings kept on the device: the look of text subtitles and the playback defaults (for everyone on this TV),
|
||||
* and the tracks chosen per movie or series (per account: family members watch with different tracks).
|
||||
*/
|
||||
@Singleton
|
||||
class PlaybackPreferences @Inject constructor(
|
||||
@@ -52,6 +65,24 @@ class PlaybackPreferences @Inject constructor(
|
||||
context.playbackStore.edit { it[KEY_SUBTITLE_STYLE] = "${style.size}|${style.color}|${style.background}" }
|
||||
}
|
||||
|
||||
val defaults: Flow<PlaybackDefaults> = context.playbackStore.data.map { prefs ->
|
||||
PlaybackDefaults(
|
||||
maxBitrate = prefs[KEY_MAX_BITRATE]?.takeIf { it > 0 },
|
||||
videoPath = prefs[KEY_VIDEO_PATH]?.let { runCatching { VideoPathSwitchable.Mode.valueOf(it) }.getOrNull() } ?: VideoPathSwitchable.Mode.AUTO,
|
||||
dolbyVision = prefs[KEY_DOLBY_VISION] ?: false,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun defaults(): PlaybackDefaults = defaults.first()
|
||||
|
||||
suspend fun setDefaults(defaults: PlaybackDefaults) {
|
||||
context.playbackStore.edit {
|
||||
it[KEY_MAX_BITRATE] = defaults.maxBitrate ?: 0
|
||||
it[KEY_VIDEO_PATH] = defaults.videoPath.name
|
||||
it[KEY_DOLBY_VISION] = defaults.dolbyVision
|
||||
}
|
||||
}
|
||||
|
||||
/** [key]: the series of an episode, else the item itself. */
|
||||
suspend fun tracks(key: UUID): RememberedTracks? {
|
||||
val raw = context.playbackStore.data.first()[tracksKey(key) ?: return null] ?: return null
|
||||
@@ -104,5 +135,8 @@ class PlaybackPreferences @Inject constructor(
|
||||
|
||||
private companion object {
|
||||
val KEY_SUBTITLE_STYLE = stringPreferencesKey("subtitle_style")
|
||||
val KEY_MAX_BITRATE = intPreferencesKey("max_bitrate")
|
||||
val KEY_VIDEO_PATH = stringPreferencesKey("video_path")
|
||||
val KEY_DOLBY_VISION = booleanPreferencesKey("dolby_vision")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,12 +127,16 @@ class PlaybackSession(
|
||||
private var transcodeTried = false
|
||||
private var nextVideoPath: VideoPathSwitchable.Mode? = null
|
||||
private var nextDolbyVision: Boolean? = null
|
||||
/** Set by [switchToAuto]: the next engine keeps its own default decoder choice instead of the old engine's. */
|
||||
/** Set by [switchToAuto]: the next engine gets the default decoder choice instead of the old engine's. */
|
||||
private var defaultDolbyVision = false
|
||||
/** Settings screen defaults, read at [start]. */
|
||||
private var defaults = PlaybackDefaults()
|
||||
/** The user picked the engine: the stall watchdog only logs instead of switching away from their choice. */
|
||||
private var manualEngine = false
|
||||
private var lastSeekAt = 0L
|
||||
private var maxBitrate: Int? = null
|
||||
/** [setMaxBitrate] was called before [start] (quality kept from the previous episode): the default doesn't apply. */
|
||||
private var maxBitrateSet = false
|
||||
private var subtitleStyle = SubtitleStyle()
|
||||
private var subtitleLift = 0f
|
||||
|
||||
@@ -160,6 +164,11 @@ class PlaybackSession(
|
||||
item = session.call { libraryApi.getItem(itemId, user.userId).content }
|
||||
source = item.mediaSources?.firstOrNull() ?: error("У элемента нет медиаисточников")
|
||||
subtitleStyle = prefs.subtitleStyle()
|
||||
defaults = prefs.defaults()
|
||||
if (!maxBitrateSet) {
|
||||
maxBitrate = defaults.maxBitrate
|
||||
_state.update { it.copy(maxBitrate = maxBitrate) }
|
||||
}
|
||||
selection = streams ?: rememberedSelection() ?: StreamSelection(source.defaultAudioStreamIndex, source.defaultSubtitleStreamIndex)
|
||||
val startMs = startFraction?.let { ((item.runTimeTicks ?: 0) / TICKS_PER_MS * it).toLong() } ?: startPositionMs
|
||||
ranking = selector.rank(source, selection, device)
|
||||
@@ -250,6 +259,7 @@ class PlaybackSession(
|
||||
|
||||
/** Quality: null = original (direct play where possible), else the server transcodes down to [bitrate]. */
|
||||
fun setMaxBitrate(bitrate: Int?) {
|
||||
maxBitrateSet = true
|
||||
if (bitrate == maxBitrate) return
|
||||
maxBitrate = bitrate
|
||||
_state.update { it.copy(maxBitrate = bitrate) }
|
||||
@@ -317,7 +327,7 @@ class PlaybackSession(
|
||||
scope.launch {
|
||||
tried.clear()
|
||||
transcodeTried = false
|
||||
nextVideoPath = VideoPathSwitchable.Mode.AUTO
|
||||
nextVideoPath = defaults.videoPath
|
||||
nextDolbyVision = null
|
||||
defaultDolbyVision = true
|
||||
manualEngine = false
|
||||
@@ -423,9 +433,9 @@ class PlaybackSession(
|
||||
.map { ExternalSubtitle(it.index, session.authorizedUrl(it.deliveryUrl!!), it.codec, it.language, it.displayTitle) }
|
||||
|
||||
val newEngine = provider.create(device)
|
||||
nextVideoPath?.let { (newEngine as? VideoPathSwitchable)?.setVideoPathMode(it) }
|
||||
(newEngine as? VideoPathSwitchable)?.setVideoPathMode(nextVideoPath ?: defaults.videoPath)
|
||||
nextVideoPath = null
|
||||
nextDolbyVision?.let { (newEngine as? DolbyVisionSwitchable)?.useDolbyVisionDecoder = it }
|
||||
(newEngine as? DolbyVisionSwitchable)?.useDolbyVisionDecoder = nextDolbyVision ?: defaults.dolbyVision
|
||||
(newEngine as? SubtitleStyleable)?.let {
|
||||
it.setSubtitleStyle(subtitleStyle)
|
||||
it.setSubtitleLift(subtitleLift)
|
||||
|
||||
Reference in New Issue
Block a user