From da1b83c3b7e942f32c05632c19a0176a39d5d35b Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Sat, 18 Apr 2026 01:06:28 +0530 Subject: [PATCH] feat(macos): player functionality with new callbacks --- MPVKit | 2 +- .../nuvio/app/features/player/PlayerEngine.kt | 30 +++ .../nuvio/app/features/player/PlayerScreen.kt | 141 ++++++++++++ .../app/features/player/NativePlayerBridge.kt | 42 ++++ .../features/player/PlayerDesktop.desktop.kt | 206 ++++++++++++++++-- 5 files changed, 407 insertions(+), 14 deletions(-) diff --git a/MPVKit b/MPVKit index 61e0e868..a633ca46 160000 --- a/MPVKit +++ b/MPVKit @@ -1 +1 @@ -Subproject commit 61e0e8683f0558fe3ff2299b1fbfd97799a2474d +Subproject commit a633ca46a3d9713e6edc885ef0e17448054997f7 diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt index 4cf9671e..1d4e9038 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt @@ -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, 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, + groups: List, + loading: Boolean, + selectedFilter: String?, + currentStreamUrl: String?, + ) {} + fun pushEpisodes(episodes: List) {} + fun pushEpisodeStreamsData( + streams: List, + groups: List, + 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?): Map { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt index 133c3dd7..41245547 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt @@ -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 diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/NativePlayerBridge.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/NativePlayerBridge.kt index 09ad572f..099b99f3 100644 --- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/NativePlayerBridge.kt +++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/NativePlayerBridge.kt @@ -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?) } diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/PlayerDesktop.desktop.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/PlayerDesktop.desktop.kt index df9eb8ba..c743cd3e 100644 --- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/PlayerDesktop.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/PlayerDesktop.desktop.kt @@ -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, 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, + groups: List, + 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) { + 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, + groups: List, + 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)