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