feat(macos): player functionality with new callbacks

This commit is contained in:
tapframe 2026-04-18 01:06:28 +05:30
parent 859027f3fd
commit da1b83c3b7
5 changed files with 407 additions and 14 deletions

2
MPVKit

@ -1 +1 @@
Subproject commit 61e0e8683f0558fe3ff2299b1fbfd97799a2474d
Subproject commit a633ca46a3d9713e6edc885ef0e17448054997f7

View file

@ -28,6 +28,7 @@ interface PlayerEngineController {
artwork: String? = null,
logo: String? = null,
) {}
fun setPlayerFlags(hasVideoId: Boolean, isSeries: Boolean) {}
fun showSkipButton(type: String, endTimeMs: Long) {}
fun hideSkipButton() {}
fun showNextEpisode(
@ -39,6 +40,35 @@ interface PlayerEngineController {
) {}
fun hideNextEpisode() {}
fun setOnCloseCallback(callback: () -> Unit) {}
fun setOnAddonSubtitlesFetchCallback(callback: () -> Unit) {}
fun pushAddonSubtitles(subtitles: List<AddonSubtitle>, isLoading: Boolean) {}
fun setOnSourcesRequestedCallback(callback: () -> Unit) {}
fun setOnSourceStreamSelectedCallback(callback: (String) -> Unit) {}
fun setOnSourceFilterChangedCallback(callback: (String?) -> Unit) {}
fun setOnSourceReloadCallback(callback: () -> Unit) {}
fun setOnEpisodesRequestedCallback(callback: () -> Unit) {}
fun setOnEpisodeSelectedCallback(callback: (String) -> Unit) {}
fun setOnEpisodeStreamSelectedCallback(callback: (String) -> Unit) {}
fun setOnEpisodeFilterChangedCallback(callback: (String?) -> Unit) {}
fun setOnEpisodeReloadCallback(callback: () -> Unit) {}
fun setOnEpisodeBackCallback(callback: () -> Unit) {}
fun pushSourceData(
streams: List<com.nuvio.app.features.streams.StreamItem>,
groups: List<com.nuvio.app.features.streams.AddonStreamGroup>,
loading: Boolean,
selectedFilter: String?,
currentStreamUrl: String?,
) {}
fun pushEpisodes(episodes: List<com.nuvio.app.features.details.MetaVideo>) {}
fun pushEpisodeStreamsData(
streams: List<com.nuvio.app.features.streams.StreamItem>,
groups: List<com.nuvio.app.features.streams.AddonStreamGroup>,
loading: Boolean,
selectedFilter: String?,
currentStreamUrl: String?,
) {}
fun showEpisodeStreamsView(season: Int?, episode: Int?, title: String?) {}
fun switchSource(url: String, audioUrl: String?, headersJson: String?) {}
}
internal fun sanitizePlaybackHeaders(headers: Map<String, String>?): Map<String, String> {

View file

@ -1023,6 +1023,30 @@ fun PlayerScreen(
playerController?.applySubtitleStyle(subtitleStyle)
}
LaunchedEffect(playerController, addonSubtitles, isLoadingAddonSubtitles) {
playerController?.pushAddonSubtitles(addonSubtitles, isLoadingAddonSubtitles)
}
LaunchedEffect(playerController, sourceStreamsState) {
playerController?.pushSourceData(
streams = sourceStreamsState.allStreams,
groups = sourceStreamsState.groups,
loading = sourceStreamsState.isAnyLoading,
selectedFilter = sourceStreamsState.selectedFilter,
currentStreamUrl = activeSourceUrl,
)
}
LaunchedEffect(playerController, episodeStreamsRepoState) {
playerController?.pushEpisodeStreamsData(
streams = episodeStreamsRepoState.allStreams,
groups = episodeStreamsRepoState.groups,
loading = episodeStreamsRepoState.isAnyLoading,
selectedFilter = episodeStreamsRepoState.selectedFilter,
currentStreamUrl = activeSourceUrl,
)
}
LaunchedEffect(playbackSnapshot.isLoading, playerController) {
if (!playbackSnapshot.isLoading && playerController != null) {
refreshTracks()
@ -1441,7 +1465,124 @@ fun PlayerScreen(
artwork = backdropArtwork,
logo = logo,
)
controller.setPlayerFlags(
hasVideoId = activeVideoId != null,
isSeries = parentMetaType == "series",
)
controller.setOnCloseCallback { onBackWithProgress() }
controller.setOnAddonSubtitlesFetchCallback {
if (contentType != null && activeVideoId != null) {
SubtitleRepository.fetchAddonSubtitles(contentType, activeVideoId!!)
}
}
controller.setOnSourcesRequestedCallback {
val type = contentType ?: parentMetaType
val vid = activeVideoId ?: return@setOnSourcesRequestedCallback
PlayerStreamsRepository.loadSources(
type = type,
videoId = vid,
season = activeSeasonNumber,
episode = activeEpisodeNumber,
)
}
controller.setOnSourceStreamSelectedCallback { url ->
val allStreams = PlayerStreamsRepository.sourceState.value.allStreams
val stream = allStreams.firstOrNull { it.directPlaybackUrl == url }
?: return@setOnSourceStreamSelectedCallback
switchToSource(stream)
val headers = sanitizePlaybackHeaders(stream.behaviorHints.proxyHeaders?.request)
val headersJson = headers.takeIf { it.isNotEmpty() }?.entries
?.joinToString(",", "{", "}") { (k, v) -> "\"${k}\":\"${v}\"" }
controller.switchSource(url, null, headersJson)
controller.setMetadata(
title = title,
streamTitle = activeStreamTitle,
providerName = activeProviderName,
seasonNumber = activeSeasonNumber,
episodeNumber = activeEpisodeNumber,
episodeTitle = activeEpisodeTitle,
artwork = backdropArtwork,
logo = logo,
)
}
controller.setOnSourceFilterChangedCallback { addonId ->
PlayerStreamsRepository.selectSourceFilter(addonId)
}
controller.setOnSourceReloadCallback {
val type = contentType ?: parentMetaType
val vid = activeVideoId ?: return@setOnSourceReloadCallback
PlayerStreamsRepository.loadSources(
type = type,
videoId = vid,
season = activeSeasonNumber,
episode = activeEpisodeNumber,
forceRefresh = true,
)
}
controller.setOnEpisodesRequestedCallback {
scope.launch {
if (playerMetaVideos.isEmpty()) {
playerMetaVideos = MetaDetailsRepository.fetch(parentMetaType, parentMetaId)?.videos ?: emptyList()
}
controller.pushEpisodes(playerMetaVideos)
}
}
controller.setOnEpisodeSelectedCallback { episodeId ->
val episode = playerMetaVideos.firstOrNull { it.id == episodeId }
?: return@setOnEpisodeSelectedCallback
episodeStreamsPanelState = episodeStreamsPanelState.copy(
showStreams = true,
selectedEpisode = episode,
)
val type = contentType ?: parentMetaType
PlayerStreamsRepository.loadEpisodeStreams(
type = type,
videoId = episode.id,
season = episode.season,
episode = episode.episode,
)
controller.showEpisodeStreamsView(episode.season, episode.episode, episode.title)
}
controller.setOnEpisodeStreamSelectedCallback { url ->
val allStreams = PlayerStreamsRepository.episodeStreamsState.value.allStreams
val stream = allStreams.firstOrNull { it.directPlaybackUrl == url }
?: return@setOnEpisodeStreamSelectedCallback
val episode = playerMetaVideos.firstOrNull { it.id == episodeStreamsPanelState.selectedEpisode?.id }
?: return@setOnEpisodeStreamSelectedCallback
switchToEpisodeStream(stream, episode)
val headers = sanitizePlaybackHeaders(stream.behaviorHints.proxyHeaders?.request)
val headersJson = headers.takeIf { it.isNotEmpty() }?.entries
?.joinToString(",", "{", "}") { (k, v) -> "\"${k}\":\"${v}\"" }
controller.switchSource(url, null, headersJson)
controller.setMetadata(
title = title,
streamTitle = activeStreamTitle,
providerName = activeProviderName,
seasonNumber = activeSeasonNumber,
episodeNumber = activeEpisodeNumber,
episodeTitle = activeEpisodeTitle,
artwork = backdropArtwork,
logo = logo,
)
}
controller.setOnEpisodeFilterChangedCallback { addonId ->
PlayerStreamsRepository.selectEpisodeStreamsFilter(addonId)
}
controller.setOnEpisodeReloadCallback {
val episode = episodeStreamsPanelState.selectedEpisode ?: return@setOnEpisodeReloadCallback
val type = contentType ?: parentMetaType
PlayerStreamsRepository.loadEpisodeStreams(
type = type,
videoId = episode.id,
season = episode.season,
episode = episode.episode,
forceRefresh = true,
)
}
controller.setOnEpisodeBackCallback {
episodeStreamsPanelState = EpisodeStreamsPanelState()
PlayerStreamsRepository.clearEpisodeStreams()
}
},
onSnapshot = { snapshot ->
playbackSnapshot = snapshot

View file

@ -52,6 +52,9 @@ internal interface DesktopMPVBridgeLib : Library {
logo: String?,
)
fun nuvio_player_set_has_video_id(player: Pointer, value: Boolean)
fun nuvio_player_set_is_series(player: Pointer, value: Boolean)
fun nuvio_player_load_file(
player: Pointer,
url: String,
@ -117,4 +120,43 @@ internal interface DesktopMPVBridgeLib : Library {
fun nuvio_player_is_closed(player: Pointer): Boolean
fun nuvio_player_pop_next_episode_pressed(player: Pointer): Boolean
fun nuvio_player_is_addon_subtitles_fetch_requested(player: Pointer): Boolean
fun nuvio_player_set_addon_subtitles_loading(player: Pointer, loading: Boolean)
fun nuvio_player_clear_addon_subtitles(player: Pointer)
fun nuvio_player_add_addon_subtitle(player: Pointer, id: String, url: String, language: String, display: String)
fun nuvio_player_pop_subtitle_style_changed(player: Pointer): Boolean
fun nuvio_player_get_subtitle_style_color_index(player: Pointer): Int
fun nuvio_player_get_subtitle_style_font_size(player: Pointer): Int
fun nuvio_player_get_subtitle_style_outline_enabled(player: Pointer): Boolean
fun nuvio_player_get_subtitle_style_bottom_offset(player: Pointer): Int
fun nuvio_player_pop_sources_open_requested(player: Pointer): Boolean
fun nuvio_player_pop_episodes_open_requested(player: Pointer): Boolean
fun nuvio_player_pop_source_stream_selected(player: Pointer): String?
fun nuvio_player_pop_source_filter_changed(player: Pointer): Boolean
fun nuvio_player_get_source_filter_value(player: Pointer): String?
fun nuvio_player_pop_source_reload(player: Pointer): Boolean
fun nuvio_player_pop_episode_selected(player: Pointer): String?
fun nuvio_player_pop_episode_stream_selected(player: Pointer): String?
fun nuvio_player_pop_episode_filter_changed(player: Pointer): Boolean
fun nuvio_player_get_episode_filter_value(player: Pointer): String?
fun nuvio_player_pop_episode_reload(player: Pointer): Boolean
fun nuvio_player_pop_episode_back(player: Pointer): Boolean
fun nuvio_player_set_sources_loading(player: Pointer, loading: Boolean)
fun nuvio_player_clear_source_streams(player: Pointer)
fun nuvio_player_add_source_stream(player: Pointer, id: String, label: String, subtitle: String?, addonName: String, addonId: String, url: String, isCurrent: Boolean)
fun nuvio_player_clear_source_addon_groups(player: Pointer)
fun nuvio_player_add_source_addon_group(player: Pointer, id: String, addonName: String, addonId: String, isLoading: Boolean, hasError: Boolean)
fun nuvio_player_set_source_selected_filter(player: Pointer, addonId: String?)
fun nuvio_player_clear_episodes(player: Pointer)
fun nuvio_player_add_episode(player: Pointer, id: String, title: String, overview: String?, thumbnail: String?, season: Int, episode: Int)
fun nuvio_player_set_episode_streams_loading(player: Pointer, loading: Boolean)
fun nuvio_player_clear_episode_streams(player: Pointer)
fun nuvio_player_add_episode_stream(player: Pointer, id: String, label: String, subtitle: String?, addonName: String, addonId: String, url: String, isCurrent: Boolean)
fun nuvio_player_clear_episode_addon_groups(player: Pointer)
fun nuvio_player_add_episode_addon_group(player: Pointer, id: String, addonName: String, addonId: String, isLoading: Boolean, hasError: Boolean)
fun nuvio_player_set_episode_selected_filter(player: Pointer, addonId: String?)
fun nuvio_player_show_episode_streams(player: Pointer, season: Int, episode: Int, title: String?)
}

View file

@ -25,6 +25,9 @@ import com.nuvio.app.core.sync.encodeSyncInt
import com.nuvio.app.core.sync.encodeSyncString
import com.nuvio.app.core.sync.encodeSyncStringSet
import com.nuvio.app.desktop.DesktopPreferences
import com.nuvio.app.features.details.MetaVideo
import com.nuvio.app.features.streams.AddonStreamGroup
import com.nuvio.app.features.streams.StreamItem
import com.sun.jna.Pointer
import java.util.Locale
import kotlinx.coroutines.delay
@ -50,6 +53,17 @@ actual fun PlatformPlayerSurface(
val bridge = remember { DesktopMPVBridgeLib.INSTANCE }
val playerPtr = remember { bridge.nuvio_player_create() }
var onCloseCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
var onAddonSubtitlesFetchCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
var onSourcesRequestedCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
var onSourceStreamSelectedCallback by remember { mutableStateOf<((String) -> Unit)?>(null) }
var onSourceFilterChangedCallback by remember { mutableStateOf<((String?) -> Unit)?>(null) }
var onSourceReloadCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
var onEpisodesRequestedCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
var onEpisodeSelectedCallback by remember { mutableStateOf<((String) -> Unit)?>(null) }
var onEpisodeStreamSelectedCallback by remember { mutableStateOf<((String) -> Unit)?>(null) }
var onEpisodeFilterChangedCallback by remember { mutableStateOf<((String?) -> Unit)?>(null) }
var onEpisodeReloadCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
var onEpisodeBackCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
DisposableEffect(playerPtr) {
bridge.nuvio_player_show(playerPtr)
@ -151,8 +165,9 @@ actual fun PlatformPlayerSurface(
override fun applySubtitleStyle(style: SubtitleStyleState) {
val colorHex = style.textColor.toMpvColorString()
val outline = if (style.outlineEnabled) 2.0f else 0.0f
val subPos = 100 - style.bottomOffset
bridge.nuvio_player_apply_subtitle_style(
playerPtr, colorHex, outline, style.fontSizeSp.toFloat(), style.bottomOffset,
playerPtr, colorHex, outline, style.fontSizeSp.toFloat(), subPos,
)
}
@ -173,6 +188,11 @@ actual fun PlatformPlayerSurface(
)
}
override fun setPlayerFlags(hasVideoId: Boolean, isSeries: Boolean) {
bridge.nuvio_player_set_has_video_id(playerPtr, hasVideoId)
bridge.nuvio_player_set_is_series(playerPtr, isSeries)
}
override fun showSkipButton(type: String, endTimeMs: Long) {
bridge.nuvio_player_show_skip_button(playerPtr, type, endTimeMs)
}
@ -198,6 +218,130 @@ actual fun PlatformPlayerSurface(
override fun setOnCloseCallback(callback: () -> Unit) {
onCloseCallback = callback
}
override fun setOnAddonSubtitlesFetchCallback(callback: () -> Unit) {
onAddonSubtitlesFetchCallback = callback
}
override fun pushAddonSubtitles(subtitles: List<AddonSubtitle>, isLoading: Boolean) {
bridge.nuvio_player_set_addon_subtitles_loading(playerPtr, isLoading)
if (!isLoading) {
bridge.nuvio_player_clear_addon_subtitles(playerPtr)
subtitles.forEach { addon ->
bridge.nuvio_player_add_addon_subtitle(
playerPtr, addon.id, addon.url, addon.language, addon.display,
)
}
}
}
override fun setOnSourcesRequestedCallback(callback: () -> Unit) {
onSourcesRequestedCallback = callback
}
override fun setOnSourceStreamSelectedCallback(callback: (String) -> Unit) {
onSourceStreamSelectedCallback = callback
}
override fun setOnSourceFilterChangedCallback(callback: (String?) -> Unit) {
onSourceFilterChangedCallback = callback
}
override fun setOnSourceReloadCallback(callback: () -> Unit) {
onSourceReloadCallback = callback
}
override fun setOnEpisodesRequestedCallback(callback: () -> Unit) {
onEpisodesRequestedCallback = callback
}
override fun setOnEpisodeSelectedCallback(callback: (String) -> Unit) {
onEpisodeSelectedCallback = callback
}
override fun setOnEpisodeStreamSelectedCallback(callback: (String) -> Unit) {
onEpisodeStreamSelectedCallback = callback
}
override fun setOnEpisodeFilterChangedCallback(callback: (String?) -> Unit) {
onEpisodeFilterChangedCallback = callback
}
override fun setOnEpisodeReloadCallback(callback: () -> Unit) {
onEpisodeReloadCallback = callback
}
override fun setOnEpisodeBackCallback(callback: () -> Unit) {
onEpisodeBackCallback = callback
}
override fun pushSourceData(
streams: List<StreamItem>,
groups: List<AddonStreamGroup>,
loading: Boolean,
selectedFilter: String?,
currentStreamUrl: String?,
) {
bridge.nuvio_player_set_sources_loading(playerPtr, loading)
bridge.nuvio_player_set_source_selected_filter(playerPtr, selectedFilter)
bridge.nuvio_player_clear_source_addon_groups(playerPtr)
groups.forEach { g ->
bridge.nuvio_player_add_source_addon_group(
playerPtr, g.addonId, g.addonName, g.addonId, g.isLoading, g.error != null,
)
}
bridge.nuvio_player_clear_source_streams(playerPtr)
streams.forEach { s ->
bridge.nuvio_player_add_source_stream(
playerPtr, s.addonId + "_" + (s.url ?: s.infoHash ?: ""),
s.streamLabel, s.streamSubtitle, s.addonName, s.addonId,
s.directPlaybackUrl ?: "", s.directPlaybackUrl == currentStreamUrl,
)
}
}
override fun pushEpisodes(episodes: List<MetaVideo>) {
bridge.nuvio_player_clear_episodes(playerPtr)
episodes.forEach { ep ->
bridge.nuvio_player_add_episode(
playerPtr, ep.id, ep.title, ep.overview, ep.thumbnail,
ep.season ?: 0, ep.episode ?: 0,
)
}
}
override fun pushEpisodeStreamsData(
streams: List<StreamItem>,
groups: List<AddonStreamGroup>,
loading: Boolean,
selectedFilter: String?,
currentStreamUrl: String?,
) {
bridge.nuvio_player_set_episode_streams_loading(playerPtr, loading)
bridge.nuvio_player_set_episode_selected_filter(playerPtr, selectedFilter)
bridge.nuvio_player_clear_episode_addon_groups(playerPtr)
groups.forEach { g ->
bridge.nuvio_player_add_episode_addon_group(
playerPtr, g.addonId, g.addonName, g.addonId, g.isLoading, g.error != null,
)
}
bridge.nuvio_player_clear_episode_streams(playerPtr)
streams.forEach { s ->
bridge.nuvio_player_add_episode_stream(
playerPtr, s.addonId + "_" + (s.url ?: s.infoHash ?: ""),
s.streamLabel, s.streamSubtitle, s.addonName, s.addonId,
s.directPlaybackUrl ?: "", s.directPlaybackUrl == currentStreamUrl,
)
}
}
override fun showEpisodeStreamsView(season: Int?, episode: Int?, title: String?) {
bridge.nuvio_player_show_episode_streams(playerPtr, season ?: 0, episode ?: 0, title)
}
override fun switchSource(url: String, audioUrl: String?, headersJson: String?) {
bridge.nuvio_player_load_file(playerPtr, url, audioUrl, headersJson)
}
}
}
@ -225,6 +369,54 @@ actual fun PlatformPlayerSurface(
onSnapshot(snapshot)
val error = bridge.nuvio_player_get_error(playerPtr)
onError(error)
if (bridge.nuvio_player_is_addon_subtitles_fetch_requested(playerPtr)) {
onAddonSubtitlesFetchCallback?.invoke()
}
if (bridge.nuvio_player_pop_subtitle_style_changed(playerPtr)) {
val colorIndex = bridge.nuvio_player_get_subtitle_style_color_index(playerPtr)
.coerceIn(0, SubtitleColorSwatches.lastIndex)
val style = SubtitleStyleState(
textColor = SubtitleColorSwatches[colorIndex],
outlineEnabled = bridge.nuvio_player_get_subtitle_style_outline_enabled(playerPtr),
fontSizeSp = bridge.nuvio_player_get_subtitle_style_font_size(playerPtr),
bottomOffset = bridge.nuvio_player_get_subtitle_style_bottom_offset(playerPtr),
)
PlayerSettingsRepository.setSubtitleStyle(style)
}
if (bridge.nuvio_player_pop_next_episode_pressed(playerPtr)) {
}
if (bridge.nuvio_player_pop_sources_open_requested(playerPtr)) {
onSourcesRequestedCallback?.invoke()
}
if (bridge.nuvio_player_pop_episodes_open_requested(playerPtr)) {
onEpisodesRequestedCallback?.invoke()
}
bridge.nuvio_player_pop_source_stream_selected(playerPtr)?.let { url ->
onSourceStreamSelectedCallback?.invoke(url)
}
if (bridge.nuvio_player_pop_source_filter_changed(playerPtr)) {
val filterValue = bridge.nuvio_player_get_source_filter_value(playerPtr)
onSourceFilterChangedCallback?.invoke(filterValue)
}
if (bridge.nuvio_player_pop_source_reload(playerPtr)) {
onSourceReloadCallback?.invoke()
}
bridge.nuvio_player_pop_episode_selected(playerPtr)?.let { episodeId ->
onEpisodeSelectedCallback?.invoke(episodeId)
}
bridge.nuvio_player_pop_episode_stream_selected(playerPtr)?.let { url ->
onEpisodeStreamSelectedCallback?.invoke(url)
}
if (bridge.nuvio_player_pop_episode_filter_changed(playerPtr)) {
val filterValue = bridge.nuvio_player_get_episode_filter_value(playerPtr)
onEpisodeFilterChangedCallback?.invoke(filterValue)
}
if (bridge.nuvio_player_pop_episode_reload(playerPtr)) {
onEpisodeReloadCallback?.invoke()
}
if (bridge.nuvio_player_pop_episode_back(playerPtr)) {
onEpisodeBackCallback?.invoke()
}
}
}
@ -290,10 +482,6 @@ internal actual object PlayerSettingsStorage {
secondaryPreferredAudioLanguageKey,
preferredSubtitleLanguageKey,
secondaryPreferredSubtitleLanguageKey,
subtitleTextColorKey,
subtitleOutlineEnabledKey,
subtitleFontSizeSpKey,
subtitleBottomOffsetKey,
streamReuseLastLinkEnabledKey,
streamReuseLastLinkCacheHoursKey,
decoderPriorityKey,
@ -524,10 +712,6 @@ internal actual object PlayerSettingsStorage {
loadSecondaryPreferredAudioLanguage()?.let { put(secondaryPreferredAudioLanguageKey, encodeSyncString(it)) }
loadPreferredSubtitleLanguage()?.let { put(preferredSubtitleLanguageKey, encodeSyncString(it)) }
loadSecondaryPreferredSubtitleLanguage()?.let { put(secondaryPreferredSubtitleLanguageKey, encodeSyncString(it)) }
loadSubtitleTextColor()?.let { put(subtitleTextColorKey, encodeSyncString(it)) }
loadSubtitleOutlineEnabled()?.let { put(subtitleOutlineEnabledKey, encodeSyncBoolean(it)) }
loadSubtitleFontSizeSp()?.let { put(subtitleFontSizeSpKey, encodeSyncInt(it)) }
loadSubtitleBottomOffset()?.let { put(subtitleBottomOffsetKey, encodeSyncInt(it)) }
loadStreamReuseLastLinkEnabled()?.let { put(streamReuseLastLinkEnabledKey, encodeSyncBoolean(it)) }
loadStreamReuseLastLinkCacheHours()?.let { put(streamReuseLastLinkCacheHoursKey, encodeSyncInt(it)) }
loadDecoderPriority()?.let { put(decoderPriorityKey, encodeSyncInt(it)) }
@ -562,10 +746,6 @@ internal actual object PlayerSettingsStorage {
payload.decodeSyncString(secondaryPreferredAudioLanguageKey)?.let(::saveSecondaryPreferredAudioLanguage)
payload.decodeSyncString(preferredSubtitleLanguageKey)?.let(::savePreferredSubtitleLanguage)
payload.decodeSyncString(secondaryPreferredSubtitleLanguageKey)?.let(::saveSecondaryPreferredSubtitleLanguage)
payload.decodeSyncString(subtitleTextColorKey)?.let(::saveSubtitleTextColor)
payload.decodeSyncBoolean(subtitleOutlineEnabledKey)?.let(::saveSubtitleOutlineEnabled)
payload.decodeSyncInt(subtitleFontSizeSpKey)?.let(::saveSubtitleFontSizeSp)
payload.decodeSyncInt(subtitleBottomOffsetKey)?.let(::saveSubtitleBottomOffset)
payload.decodeSyncBoolean(streamReuseLastLinkEnabledKey)?.let(::saveStreamReuseLastLinkEnabled)
payload.decodeSyncInt(streamReuseLastLinkCacheHoursKey)?.let(::saveStreamReuseLastLinkCacheHours)
payload.decodeSyncInt(decoderPriorityKey)?.let(::saveDecoderPriority)