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..819c8b7c
--- /dev/null
+++ b/app/src/main/java/com/nuvio/tv/ui/screens/player/PlayerAspectScaleUtils.kt
@@ -0,0 +1,112 @@
+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
+
+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) {
+ val targetView = resolveVideoSurfaceView(playerView) ?: playerView
+ playerView.scaleX = 1.0f
+ playerView.scaleY = 1.0f
+
+ when (mode) {
+ AspectMode.ORIGINAL -> {
+ targetView.scaleX = 1.0f
+ targetView.scaleY = 1.0f
+ }
+
+ AspectMode.FULL_SCREEN -> applyCoverAspectScale(playerView, targetView)
+
+ AspectMode.SLIGHT_ZOOM -> {
+ targetView.scaleX = 1.15f
+ targetView.scaleY = 1.15f
+ }
+
+ AspectMode.CINEMA_ZOOM -> {
+ targetView.scaleX = 1.33f
+ targetView.scaleY = 1.33f
+ }
+
+ AspectMode.VERTICAL_STRETCH -> {
+ targetView.scaleX = 1.0f
+ targetView.scaleY = 1.33f
+ }
+
+ AspectMode.HORIZONTAL_STRETCH -> {
+ targetView.scaleX = 1.3333f
+ targetView.scaleY = 1.0f
+ }
+ }
+}
+
+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)) /
+ 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) {
+ targetView.scaleX = 1.0f
+ targetView.scaleY = videoAspect / viewAspect
+ } else {
+ targetView.scaleX = viewAspect / videoAspect
+ targetView.scaleY = 1.0f
+ }
+ } else {
+ 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
+ }
+}
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 95d99353..56e76056 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
@@ -79,7 +79,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 9e665021..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
@@ -813,18 +813,31 @@ 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
+ 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)")
_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 30db9dcf..f07b25dc 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
@@ -491,7 +492,7 @@ fun PlayerScreen(
// Video Player
viewModel.exoPlayer?.let { player ->
val subtitleStyle = uiState.subtitleStyle
- val resizeMode = uiState.resizeMode
+ val aspectMode = uiState.aspectMode
AndroidView(
factory = { context ->
@@ -505,8 +506,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..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
@@ -125,6 +125,8 @@ data class PlayerUiState(
val showDisplayModeInfo: Boolean = false,
// 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 f1301210..4ccea513 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -847,6 +847,9 @@
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
Ses
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3046457c..f35f7708 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -851,6 +851,9 @@
Fit Width
Fit Height
Crop
+ Unavailable during tunneled playback
+ Slight Zoom
+ Cinema Zoom
Audio