mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-16 23:12:12 +00:00
Merge branch 'NuvioMedia:cmp-rewrite' into introdb
This commit is contained in:
commit
b0f2767925
7 changed files with 153 additions and 134 deletions
|
|
@ -223,7 +223,6 @@ fun PlayerScreen(
|
|||
activeEpisodeNumber,
|
||||
) { mutableStateOf(false) }
|
||||
var hasSentCompletionScrobbleForCurrentItem by remember(
|
||||
activeSourceUrl,
|
||||
activeVideoId,
|
||||
activeSeasonNumber,
|
||||
activeEpisodeNumber,
|
||||
|
|
@ -383,7 +382,6 @@ fun PlayerScreen(
|
|||
val progressPercent = currentPlaybackProgressPercent()
|
||||
if (progressPercent >= 1f && progressPercent < 80f) {
|
||||
emitTraktScrobbleStop(progressPercent)
|
||||
hasSentCompletionScrobbleForCurrentItem = false
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1199,15 +1197,20 @@ fun PlayerScreen(
|
|||
pausedOverlayVisible = true
|
||||
}
|
||||
|
||||
LaunchedEffect(playbackSnapshot.positionMs, playbackSnapshot.isPlaying, playbackSnapshot.isEnded, playbackSnapshot.durationMs) {
|
||||
LaunchedEffect(
|
||||
playbackSnapshot.positionMs,
|
||||
playbackSnapshot.isPlaying,
|
||||
playbackSnapshot.isLoading,
|
||||
playbackSnapshot.isEnded,
|
||||
playbackSnapshot.durationMs,
|
||||
) {
|
||||
if (playbackSnapshot.isEnded) {
|
||||
hasSentCompletionScrobbleForCurrentItem = false
|
||||
flushWatchProgress()
|
||||
previousIsPlaying = false
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
if (previousIsPlaying && !playbackSnapshot.isPlaying) {
|
||||
if (previousIsPlaying && !playbackSnapshot.isPlaying && !playbackSnapshot.isLoading) {
|
||||
flushWatchProgress()
|
||||
}
|
||||
|
||||
|
|
@ -1215,7 +1218,9 @@ fun PlayerScreen(
|
|||
emitTraktScrobbleStart()
|
||||
}
|
||||
|
||||
previousIsPlaying = playbackSnapshot.isPlaying
|
||||
if (!playbackSnapshot.isLoading) {
|
||||
previousIsPlaying = playbackSnapshot.isPlaying
|
||||
}
|
||||
|
||||
if (!playbackSnapshot.isPlaying) {
|
||||
return@LaunchedEffect
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import nuvio.composeapp.generated.resources.Res
|
|||
import nuvio.composeapp.generated.resources.lang_english
|
||||
import nuvio.composeapp.generated.resources.lang_french
|
||||
import nuvio.composeapp.generated.resources.lang_spanish
|
||||
import nuvio.composeapp.generated.resources.lang_portuguese
|
||||
import nuvio.composeapp.generated.resources.lang_portuguese_portugal
|
||||
import nuvio.composeapp.generated.resources.lang_turkish
|
||||
import nuvio.composeapp.generated.resources.lang_italian
|
||||
import nuvio.composeapp.generated.resources.lang_greek
|
||||
|
|
@ -16,8 +16,9 @@ enum class AppLanguage(
|
|||
val labelRes: StringResource,
|
||||
) {
|
||||
ENGLISH("en", Res.string.lang_english),
|
||||
FRENCH("fr", Res.string.lang_french),
|
||||
FRENCH("fr", Res.string.lang_french),
|
||||
SPANISH("es", Res.string.lang_spanish),
|
||||
PORTUGUESE("pt", Res.string.lang_portuguese_portugal),
|
||||
TURKISH("tr", Res.string.lang_turkish),
|
||||
ITALIAN("it", Res.string.lang_italian),
|
||||
GREEK("el", Res.string.lang_greek),
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ object ThemeSettingsRepository {
|
|||
_selectedTheme.value = theme
|
||||
_amoledEnabled.value = ThemeSettingsStorage.loadAmoledEnabled() ?: false
|
||||
val appLanguage = AppLanguage.fromCode(ThemeSettingsStorage.loadSelectedAppLanguage())
|
||||
_selectedAppLanguage.value = appLanguage
|
||||
ThemeSettingsStorage.applySelectedAppLanguage(appLanguage.code)
|
||||
_selectedAppLanguage.value = appLanguage
|
||||
}
|
||||
|
||||
fun setTheme(theme: AppTheme) {
|
||||
|
|
@ -69,8 +69,8 @@ object ThemeSettingsRepository {
|
|||
fun setAppLanguage(language: AppLanguage) {
|
||||
ensureLoaded()
|
||||
if (_selectedAppLanguage.value == language) return
|
||||
_selectedAppLanguage.value = language
|
||||
ThemeSettingsStorage.saveSelectedAppLanguage(language.code)
|
||||
ThemeSettingsStorage.applySelectedAppLanguage(language.code)
|
||||
_selectedAppLanguage.value = language
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,180 +34,182 @@ actual fun PlatformPlayerSurface(
|
|||
onError: (String?) -> Unit,
|
||||
) {
|
||||
sanitizePlaybackResponseHeaders(sourceResponseHeaders)
|
||||
val latestOnControllerReady = rememberUpdatedState(onControllerReady)
|
||||
val latestOnSnapshot = rememberUpdatedState(onSnapshot)
|
||||
val latestOnError = rememberUpdatedState(onError)
|
||||
|
||||
val bridge = remember(sourceUrl) {
|
||||
val bridge = remember {
|
||||
NuvioPlayerBridgeFactory.create()
|
||||
}
|
||||
|
||||
if (bridge == null) {
|
||||
LaunchedEffect(Unit) {
|
||||
onError("MPV player engine not available. Please rebuild the app.")
|
||||
latestOnError.value("MPV player engine not available. Please rebuild the app.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Create controller
|
||||
LaunchedEffect(bridge) {
|
||||
onControllerReady(
|
||||
object : PlayerEngineController {
|
||||
override fun play() {
|
||||
bridge.play()
|
||||
}
|
||||
val controller = remember(bridge) {
|
||||
object : PlayerEngineController {
|
||||
override fun play() {
|
||||
bridge.play()
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
bridge.pause()
|
||||
}
|
||||
override fun pause() {
|
||||
bridge.pause()
|
||||
}
|
||||
|
||||
override fun seekTo(positionMs: Long) {
|
||||
bridge.seekTo(positionMs)
|
||||
}
|
||||
override fun seekTo(positionMs: Long) {
|
||||
bridge.seekTo(positionMs)
|
||||
}
|
||||
|
||||
override fun seekBy(offsetMs: Long) {
|
||||
bridge.seekBy(offsetMs)
|
||||
}
|
||||
override fun seekBy(offsetMs: Long) {
|
||||
bridge.seekBy(offsetMs)
|
||||
}
|
||||
|
||||
override fun retry() {
|
||||
bridge.retry()
|
||||
}
|
||||
override fun retry() {
|
||||
bridge.retry()
|
||||
}
|
||||
|
||||
override fun setPlaybackSpeed(speed: Float) {
|
||||
bridge.setPlaybackSpeed(speed)
|
||||
}
|
||||
override fun setPlaybackSpeed(speed: Float) {
|
||||
bridge.setPlaybackSpeed(speed)
|
||||
}
|
||||
|
||||
override fun getAudioTracks(): List<AudioTrack> {
|
||||
val count = bridge.getAudioTrackCount()
|
||||
return (0 until count).map { i ->
|
||||
AudioTrack(
|
||||
index = bridge.getAudioTrackIndex(i),
|
||||
id = bridge.getAudioTrackId(i),
|
||||
label = bridge.getAudioTrackLabel(i),
|
||||
language = bridge.getAudioTrackLang(i),
|
||||
isSelected = bridge.isAudioTrackSelected(i),
|
||||
)
|
||||
}
|
||||
override fun getAudioTracks(): List<AudioTrack> {
|
||||
val count = bridge.getAudioTrackCount()
|
||||
return (0 until count).map { i ->
|
||||
AudioTrack(
|
||||
index = bridge.getAudioTrackIndex(i),
|
||||
id = bridge.getAudioTrackId(i),
|
||||
label = bridge.getAudioTrackLabel(i),
|
||||
language = bridge.getAudioTrackLang(i),
|
||||
isSelected = bridge.isAudioTrackSelected(i),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSubtitleTracks(): List<SubtitleTrack> {
|
||||
val count = bridge.getSubtitleTrackCount()
|
||||
val tracks = (0 until count).map { i ->
|
||||
val trackId = bridge.getSubtitleTrackId(i)
|
||||
val trackLabel = bridge.getSubtitleTrackLabel(i)
|
||||
val trackLanguage = bridge.getSubtitleTrackLang(i)
|
||||
SubtitleTrack(
|
||||
index = bridge.getSubtitleTrackIndex(i),
|
||||
id = trackId,
|
||||
override fun getSubtitleTracks(): List<SubtitleTrack> {
|
||||
val count = bridge.getSubtitleTrackCount()
|
||||
val tracks = (0 until count).map { i ->
|
||||
val trackId = bridge.getSubtitleTrackId(i)
|
||||
val trackLabel = bridge.getSubtitleTrackLabel(i)
|
||||
val trackLanguage = bridge.getSubtitleTrackLang(i)
|
||||
SubtitleTrack(
|
||||
index = bridge.getSubtitleTrackIndex(i),
|
||||
id = trackId,
|
||||
label = trackLabel,
|
||||
language = trackLanguage,
|
||||
isSelected = bridge.isSubtitleTrackSelected(i),
|
||||
isForced = inferForcedSubtitleTrack(
|
||||
label = trackLabel,
|
||||
language = trackLanguage,
|
||||
isSelected = bridge.isSubtitleTrackSelected(i),
|
||||
isForced = inferForcedSubtitleTrack(
|
||||
label = trackLabel,
|
||||
language = trackLanguage,
|
||||
trackId = trackId,
|
||||
),
|
||||
)
|
||||
}
|
||||
Logger.d(TAG) { "getSubtitleTracks: found ${tracks.size} tracks" }
|
||||
return tracks
|
||||
trackId = trackId,
|
||||
),
|
||||
)
|
||||
}
|
||||
Logger.d(TAG) { "getSubtitleTracks: found ${tracks.size} tracks" }
|
||||
return tracks
|
||||
}
|
||||
|
||||
override fun selectAudioTrack(index: Int) {
|
||||
// Convert from logical track index to mpv track id
|
||||
val count = bridge.getAudioTrackCount()
|
||||
override fun selectAudioTrack(index: Int) {
|
||||
// Convert from logical track index to mpv track id
|
||||
val count = bridge.getAudioTrackCount()
|
||||
if (count <= 0) return
|
||||
|
||||
val trackId = (0 until count)
|
||||
.firstNotNullOfOrNull { at ->
|
||||
if (bridge.getAudioTrackIndex(at) == index) {
|
||||
bridge.getAudioTrackId(at).toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: if (index in 0 until count) {
|
||||
bridge.getAudioTrackId(index).toIntOrNull() ?: (index + 1)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (trackId != null) {
|
||||
bridge.selectAudioTrack(trackId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun selectSubtitleTrack(index: Int) {
|
||||
if (index < 0) {
|
||||
bridge.selectSubtitleTrack(-1) // disable
|
||||
} else {
|
||||
val count = bridge.getSubtitleTrackCount()
|
||||
if (count <= 0) return
|
||||
|
||||
val trackId = (0 until count)
|
||||
.firstNotNullOfOrNull { at ->
|
||||
if (bridge.getAudioTrackIndex(at) == index) {
|
||||
bridge.getAudioTrackId(at).toIntOrNull()
|
||||
if (bridge.getSubtitleTrackIndex(at) == index) {
|
||||
bridge.getSubtitleTrackId(at).toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: if (index in 0 until count) {
|
||||
bridge.getAudioTrackId(index).toIntOrNull() ?: (index + 1)
|
||||
bridge.getSubtitleTrackId(index).toIntOrNull() ?: (index + 1)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (trackId != null) {
|
||||
bridge.selectAudioTrack(trackId)
|
||||
bridge.selectSubtitleTrack(trackId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun selectSubtitleTrack(index: Int) {
|
||||
if (index < 0) {
|
||||
bridge.selectSubtitleTrack(-1) // disable
|
||||
override fun setSubtitleUri(url: String) {
|
||||
Logger.d(TAG) { "setSubtitleUri: $url" }
|
||||
bridge.setSubtitleUrl(url)
|
||||
}
|
||||
|
||||
override fun clearExternalSubtitle() {
|
||||
bridge.clearExternalSubtitle()
|
||||
}
|
||||
|
||||
override fun clearExternalSubtitleAndSelect(trackIndex: Int) {
|
||||
val trackId = if (trackIndex < 0) {
|
||||
-1
|
||||
} else {
|
||||
val count = bridge.getSubtitleTrackCount()
|
||||
if (count <= 0) {
|
||||
trackIndex + 1
|
||||
} else {
|
||||
val count = bridge.getSubtitleTrackCount()
|
||||
if (count <= 0) return
|
||||
|
||||
val trackId = (0 until count)
|
||||
(0 until count)
|
||||
.firstNotNullOfOrNull { at ->
|
||||
if (bridge.getSubtitleTrackIndex(at) == index) {
|
||||
if (bridge.getSubtitleTrackIndex(at) == trackIndex) {
|
||||
bridge.getSubtitleTrackId(at).toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: if (index in 0 until count) {
|
||||
bridge.getSubtitleTrackId(index).toIntOrNull() ?: (index + 1)
|
||||
?: if (trackIndex in 0 until count) {
|
||||
bridge.getSubtitleTrackId(trackIndex).toIntOrNull() ?: (trackIndex + 1)
|
||||
} else {
|
||||
null
|
||||
trackIndex + 1
|
||||
}
|
||||
|
||||
if (trackId != null) {
|
||||
bridge.selectSubtitleTrack(trackId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setSubtitleUri(url: String) {
|
||||
Logger.d(TAG) { "setSubtitleUri: $url" }
|
||||
bridge.setSubtitleUrl(url)
|
||||
}
|
||||
|
||||
override fun clearExternalSubtitle() {
|
||||
bridge.clearExternalSubtitle()
|
||||
}
|
||||
|
||||
override fun clearExternalSubtitleAndSelect(trackIndex: Int) {
|
||||
val trackId = if (trackIndex < 0) {
|
||||
-1
|
||||
} else {
|
||||
val count = bridge.getSubtitleTrackCount()
|
||||
if (count <= 0) {
|
||||
trackIndex + 1
|
||||
} else {
|
||||
(0 until count)
|
||||
.firstNotNullOfOrNull { at ->
|
||||
if (bridge.getSubtitleTrackIndex(at) == trackIndex) {
|
||||
bridge.getSubtitleTrackId(at).toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: if (trackIndex in 0 until count) {
|
||||
bridge.getSubtitleTrackId(trackIndex).toIntOrNull() ?: (trackIndex + 1)
|
||||
} else {
|
||||
trackIndex + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
bridge.clearExternalSubtitleAndSelect(trackId)
|
||||
}
|
||||
|
||||
override fun applySubtitleStyle(style: SubtitleStyleState) {
|
||||
bridge.applySubtitleStyle(
|
||||
textColor = style.textColor.toMpvColorString(),
|
||||
outlineSize = if (style.outlineEnabled) 1.65f else 0f,
|
||||
fontSize = style.toMpvSubtitleFontSize(),
|
||||
subPos = style.toMpvSubtitlePosition(),
|
||||
)
|
||||
}
|
||||
bridge.clearExternalSubtitleAndSelect(trackId)
|
||||
}
|
||||
)
|
||||
|
||||
override fun applySubtitleStyle(style: SubtitleStyleState) {
|
||||
bridge.applySubtitleStyle(
|
||||
textColor = style.textColor.toMpvColorString(),
|
||||
outlineSize = if (style.outlineEnabled) 1.65f else 0f,
|
||||
fontSize = style.toMpvSubtitleFontSize(),
|
||||
subPos = style.toMpvSubtitlePosition(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(controller, sourceUrl, sourceAudioUrl, sourceHeaders, sourceResponseHeaders) {
|
||||
latestOnControllerReady.value(controller)
|
||||
}
|
||||
|
||||
// Load file and set initial state
|
||||
|
|
|
|||
|
|
@ -50,7 +50,17 @@ actual object ThemeSettingsStorage {
|
|||
NSUserDefaults.standardUserDefaults.setObject(languageCode, forKey = selectedAppLanguageKey)
|
||||
}
|
||||
|
||||
actual fun applySelectedAppLanguage(languageCode: String) = Unit
|
||||
actual fun applySelectedAppLanguage(languageCode: String) {
|
||||
val normalizedCode = languageCode
|
||||
.trim()
|
||||
.takeIf { it.isNotBlank() }
|
||||
?: AppLanguage.ENGLISH.code
|
||||
NSUserDefaults.standardUserDefaults.setObject(
|
||||
listOf(normalizedCode),
|
||||
forKey = "AppleLanguages",
|
||||
)
|
||||
NSUserDefaults.standardUserDefaults.synchronize()
|
||||
}
|
||||
|
||||
actual fun exportToSyncPayload(): JsonObject = buildJsonObject {
|
||||
loadSelectedTheme()?.let { put(selectedThemeKey, encodeSyncString(it)) }
|
||||
|
|
@ -69,5 +79,6 @@ actual object ThemeSettingsStorage {
|
|||
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)
|
||||
payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled)
|
||||
payload.decodeSyncString(selectedAppLanguageKey)?.let(::saveSelectedAppLanguage)
|
||||
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.ENGLISH.code)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
CURRENT_PROJECT_VERSION=49
|
||||
MARKETING_VERSION=0.1.0
|
||||
CURRENT_PROJECT_VERSION=50
|
||||
MARKETING_VERSION=0.1.13
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue