From fbfab7cc390078aa8155a3a1ea164edbdb26bd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?HAL=C4=B0L=20=C4=B0BRAH=C4=B0M=20=C3=96ZDEM=C4=B0R?= <94225244+halibiram@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:59:40 +0300 Subject: [PATCH 1/4] Fix pt-BR string resource markup --- app/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3746118c..b8858498 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -991,7 +991,7 @@ Isso removerá a lista e todos os seus itens do Trakt. Excluir LOCAL - string name="library_source_nuvio">NUVIO + NUVIO TRAKT Sincronizando biblioteca… Todos From b2ab419b1adeb7ce2428b7d3ab75ddb6cbfb816c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?HAL=C4=B0L=20=C4=B0BRAH=C4=B0M=20=C3=96ZDEM=C4=B0R?= <94225244+halibiram@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:59:58 +0300 Subject: [PATCH 2/4] Replace main aspect ratio with typed aspect modes --- .../screens/player/PlayerAspectScaleUtils.kt | 83 +++++++++++++++++++ .../PlayerRuntimeControllerPlaybackEvents.kt | 13 ++- .../tv/ui/screens/player/PlayerScreen.kt | 7 +- .../tv/ui/screens/player/PlayerUiState.kt | 1 + app/src/main/res/values-tr/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 6 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt new file mode 100644 index 00000000..6cb64f0f --- /dev/null +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt @@ -0,0 +1,83 @@ +package com.nuvio.tv.ui.screens.player + +import androidx.annotation.StringRes +import androidx.media3.ui.PlayerView +import com.nuvio.tv.R + +enum class AspectMode(@StringRes val labelResId: Int) { + ORIGINAL(R.string.player_aspect_fit), + FULL_SCREEN(R.string.player_aspect_crop), + SLIGHT_ZOOM(R.string.player_aspect_mode_slight_zoom), + CINEMA_ZOOM(R.string.player_aspect_mode_cinema_zoom), + VERTICAL_STRETCH(R.string.player_aspect_fit_height), + HORIZONTAL_STRETCH(R.string.player_aspect_fit_width) +} + +internal fun nextAspectMode(current: AspectMode): AspectMode { + val modes = AspectMode.entries + val nextIndex = (modes.indexOf(current) + 1) % modes.size + return modes[nextIndex] +} + +internal fun aspectModeLabel(mode: AspectMode, getString: (Int) -> String): String = + getString(mode.labelResId) + +internal fun applyAspectMode(playerView: PlayerView, mode: AspectMode) { + when (mode) { + AspectMode.ORIGINAL -> { + playerView.scaleX = 1.0f + playerView.scaleY = 1.0f + } + + AspectMode.FULL_SCREEN -> applyCoverAspectScale(playerView) + + AspectMode.SLIGHT_ZOOM -> { + playerView.scaleX = 1.15f + playerView.scaleY = 1.15f + } + + AspectMode.CINEMA_ZOOM -> { + playerView.scaleX = 1.33f + playerView.scaleY = 1.33f + } + + AspectMode.VERTICAL_STRETCH -> { + playerView.scaleX = 1.0f + playerView.scaleY = 1.33f + } + + AspectMode.HORIZONTAL_STRETCH -> { + playerView.scaleX = 1.3333f + playerView.scaleY = 1.0f + } + } +} + +private fun applyCoverAspectScale(playerView: PlayerView) { + val videoSize = playerView.player?.videoSize + val videoAspect = if ((videoSize?.height ?: 0) > 0) { + ((videoSize?.width ?: 0).toFloat() * (videoSize?.pixelWidthHeightRatio ?: 1f)) / + videoSize!!.height.toFloat() + } else { + 0f + } + + val viewAspect = if (playerView.width > 0 && playerView.height > 0) { + playerView.width.toFloat() / playerView.height.toFloat() + } else { + 0f + } + + if (videoAspect > 0f && viewAspect > 0f) { + if (videoAspect > viewAspect) { + playerView.scaleX = 1.0f + playerView.scaleY = videoAspect / viewAspect + } else { + playerView.scaleX = viewAspect / videoAspect + playerView.scaleY = 1.0f + } + } else { + playerView.scaleX = 1.0f + playerView.scaleY = 1.0f + } +} diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt index 9e665021..439c321a 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt @@ -813,18 +813,17 @@ fun PlayerRuntimeController.onEvent(event: PlayerEvent) { } } PlayerEvent.OnToggleAspectRatio -> { - val currentMode = _uiState.value.resizeMode - val newMode = PlayerDisplayModeUtils.nextResizeMode(currentMode) - val modeText = PlayerDisplayModeUtils.resizeModeLabel(newMode, context) - Log.d("PlayerViewModel", "Aspect ratio toggled: $currentMode -> $newMode") + val state = _uiState.value + val newMode = nextAspectMode(state.aspectMode) + val label = aspectModeLabel(newMode, context::getString) + Log.d("PlayerViewModel", "Aspect mode toggled: ${state.aspectMode} -> $newMode ($label)") _uiState.update { it.copy( - resizeMode = newMode, + aspectMode = newMode, showAspectRatioIndicator = true, - aspectRatioIndicatorText = modeText + aspectRatioIndicatorText = label ) } - scope.launch { playerSettingsDataStore.setResizeMode(newMode) } hideAspectRatioIndicatorJob?.cancel() hideAspectRatioIndicatorJob = scope.launch { delay(1500) diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerScreen.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerScreen.kt index 3f736694..5020dd69 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerScreen.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerScreen.kt @@ -87,6 +87,7 @@ import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -489,7 +490,7 @@ fun PlayerScreen( // Video Player viewModel.exoPlayer?.let { player -> val subtitleStyle = uiState.subtitleStyle - val resizeMode = uiState.resizeMode + val aspectMode = uiState.aspectMode AndroidView( factory = { context -> @@ -503,8 +504,8 @@ fun PlayerScreen( update = { playerView -> // Keep device awake only while playback is active (or buffering), not when paused. playerView.keepScreenOn = uiState.isPlaying || uiState.isBuffering - Log.d("PlayerScreen", "Applying resizeMode: $resizeMode") - playerView.resizeMode = resizeMode + playerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT + applyAspectMode(playerView, aspectMode) playerView.subtitleView?.apply { // Calculate font size based on percentage (100% = 24sp base) val baseFontSize = 24f diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt index ab5d2ba1..6312957b 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt @@ -125,6 +125,7 @@ data class PlayerUiState( val showDisplayModeInfo: Boolean = false, // Aspect ratio / resize mode val resizeMode: Int = AspectRatioFrameLayout.RESIZE_MODE_FIT, + val aspectMode: AspectMode = AspectMode.ORIGINAL, val showAspectRatioIndicator: Boolean = false, val aspectRatioIndicatorText: String = "", // Stream info overlay diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f1301210..fc4a5ccb 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -847,6 +847,8 @@ Genişliğe Sığdır Yüksekliğe Sığdır Kırp + Hafif Yakınlaştırma + Sinema Yakınlaştırma Ses diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ea4fcc5..19c0099a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -850,6 +850,8 @@ Fit Width Fit Height Crop + Slight Zoom + Cinema Zoom Audio From 32102b30e5265160565b25f900552050e2cd1068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?HAL=C4=B0L=20=C4=B0BRAH=C4=B0M=20=C3=96ZDEM=C4=B0R?= <94225244+halibiram@users.noreply.github.com> Date: Tue, 31 Mar 2026 00:14:32 +0300 Subject: [PATCH 3/4] Keep subtitles unaffected by aspect scaling --- .../screens/player/PlayerAspectScaleUtils.kt | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt index 6cb64f0f..819c8b7c 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt @@ -1,5 +1,9 @@ package com.nuvio.tv.ui.screens.player +import android.view.SurfaceView +import android.view.TextureView +import android.view.View +import android.view.ViewGroup import androidx.annotation.StringRes import androidx.media3.ui.PlayerView import com.nuvio.tv.R @@ -23,37 +27,41 @@ internal fun aspectModeLabel(mode: AspectMode, getString: (Int) -> String): Stri getString(mode.labelResId) internal fun applyAspectMode(playerView: PlayerView, mode: AspectMode) { + val targetView = resolveVideoSurfaceView(playerView) ?: playerView + playerView.scaleX = 1.0f + playerView.scaleY = 1.0f + when (mode) { AspectMode.ORIGINAL -> { - playerView.scaleX = 1.0f - playerView.scaleY = 1.0f + targetView.scaleX = 1.0f + targetView.scaleY = 1.0f } - AspectMode.FULL_SCREEN -> applyCoverAspectScale(playerView) + AspectMode.FULL_SCREEN -> applyCoverAspectScale(playerView, targetView) AspectMode.SLIGHT_ZOOM -> { - playerView.scaleX = 1.15f - playerView.scaleY = 1.15f + targetView.scaleX = 1.15f + targetView.scaleY = 1.15f } AspectMode.CINEMA_ZOOM -> { - playerView.scaleX = 1.33f - playerView.scaleY = 1.33f + targetView.scaleX = 1.33f + targetView.scaleY = 1.33f } AspectMode.VERTICAL_STRETCH -> { - playerView.scaleX = 1.0f - playerView.scaleY = 1.33f + targetView.scaleX = 1.0f + targetView.scaleY = 1.33f } AspectMode.HORIZONTAL_STRETCH -> { - playerView.scaleX = 1.3333f - playerView.scaleY = 1.0f + targetView.scaleX = 1.3333f + targetView.scaleY = 1.0f } } } -private fun applyCoverAspectScale(playerView: PlayerView) { +private fun applyCoverAspectScale(playerView: PlayerView, targetView: View) { val videoSize = playerView.player?.videoSize val videoAspect = if ((videoSize?.height ?: 0) > 0) { ((videoSize?.width ?: 0).toFloat() * (videoSize?.pixelWidthHeightRatio ?: 1f)) / @@ -70,14 +78,35 @@ private fun applyCoverAspectScale(playerView: PlayerView) { if (videoAspect > 0f && viewAspect > 0f) { if (videoAspect > viewAspect) { - playerView.scaleX = 1.0f - playerView.scaleY = videoAspect / viewAspect + targetView.scaleX = 1.0f + targetView.scaleY = videoAspect / viewAspect } else { - playerView.scaleX = viewAspect / videoAspect - playerView.scaleY = 1.0f + targetView.scaleX = viewAspect / videoAspect + targetView.scaleY = 1.0f } } else { - playerView.scaleX = 1.0f - playerView.scaleY = 1.0f + targetView.scaleX = 1.0f + targetView.scaleY = 1.0f + } +} + +private fun resolveVideoSurfaceView(playerView: PlayerView): View? { + return findVideoSurfaceView(playerView) +} + +private fun findVideoSurfaceView(view: View): View? { + return when (view) { + is SurfaceView, is TextureView -> view + is ViewGroup -> { + for (index in 0 until view.childCount) { + val child = findVideoSurfaceView(view.getChildAt(index)) + if (child != null) { + return child + } + } + null + } + + else -> null } } From 59dbc6a705f656aeecff74b69cafca53f615f758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?HAL=C4=B0L=20=C4=B0BRAH=C4=B0M=20=C3=96ZDEM=C4=B0R?= <94225244+halibiram@users.noreply.github.com> Date: Tue, 31 Mar 2026 00:19:50 +0300 Subject: [PATCH 4/4] Warn when aspect ratio is unavailable during tunneled playback --- .../PlayerRuntimeControllerInitialization.kt | 3 ++- .../player/PlayerRuntimeControllerObservers.kt | 1 + .../PlayerRuntimeControllerPlaybackEvents.kt | 14 ++++++++++++++ .../nuvio/tv/ui/screens/player/PlayerUiState.kt | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerInitialization.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerInitialization.kt index 15be3503..f73eac01 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerInitialization.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerInitialization.kt @@ -78,7 +78,8 @@ internal fun PlayerRuntimeController.initializePlayer(url: String, headers: Map< _uiState.update { it.copy( frameRateMatchingMode = playerSettings.frameRateMatchingMode, - resizeMode = playerSettings.resizeMode + resizeMode = playerSettings.resizeMode, + tunnelingEnabled = playerSettings.tunnelingEnabled ) } val afrJob = async { diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerObservers.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerObservers.kt index 1f100917..34ac1147 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerObservers.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerObservers.kt @@ -191,6 +191,7 @@ internal fun PlayerRuntimeController.observeSubtitleSettings() { pauseOverlayEnabled = settings.pauseOverlayEnabled, osdClockEnabled = settings.osdClockEnabled, frameRateMatchingMode = settings.frameRateMatchingMode, + tunnelingEnabled = settings.tunnelingEnabled, persistAudioAmplification = settings.persistAudioAmplification, audioAmplificationDb = resolvedAudioAmplificationDb ) diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt index 439c321a..a15388d0 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerRuntimeControllerPlaybackEvents.kt @@ -814,6 +814,20 @@ fun PlayerRuntimeController.onEvent(event: PlayerEvent) { } PlayerEvent.OnToggleAspectRatio -> { val state = _uiState.value + if (state.tunnelingEnabled) { + _uiState.update { + it.copy( + showAspectRatioIndicator = true, + aspectRatioIndicatorText = context.getString(R.string.player_aspect_tunneling_unavailable) + ) + } + hideAspectRatioIndicatorJob?.cancel() + hideAspectRatioIndicatorJob = scope.launch { + delay(1500) + _uiState.update { it.copy(showAspectRatioIndicator = false) } + } + return + } val newMode = nextAspectMode(state.aspectMode) val label = aspectModeLabel(newMode, context::getString) Log.d("PlayerViewModel", "Aspect mode toggled: ${state.aspectMode} -> $newMode ($label)") diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt index 6312957b..db2ccaca 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerUiState.kt @@ -126,6 +126,7 @@ data class PlayerUiState( // Aspect ratio / resize mode val resizeMode: Int = AspectRatioFrameLayout.RESIZE_MODE_FIT, val aspectMode: AspectMode = AspectMode.ORIGINAL, + val tunnelingEnabled: Boolean = false, val showAspectRatioIndicator: Boolean = false, val aspectRatioIndicatorText: String = "", // Stream info overlay diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fc4a5ccb..4ccea513 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -847,6 +847,7 @@ Genişliğe Sığdır Yüksekliğe Sığdır Kırp + Tünellenmiş oynatım sırasında kullanılamaz Hafif Yakınlaştırma Sinema Yakınlaştırma diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 19c0099a..f3293796 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -850,6 +850,7 @@ Fit Width Fit Height Crop + Unavailable during tunneled playback Slight Zoom Cinema Zoom