mirror of
https://github.com/NuvioMedia/NuvioMobile.git
synced 2026-08-17 12:43:37 +00:00
feat: Add 200% in-player volume boost for Android and iOS
Implemented in-player volume boost support up to 200% across Android and iOS playback engines. Changes include: - Added player-level volume control APIs to PlayerEngineController. - Routed volume gestures through the active player engine instead of system volume when available. - Added Android support by mapping boosted volume to ExoPlayer volume from 0.0 to 2.0. - Added iOS support by exposing volume control through NuvioPlayerBridge. - Configured MPV volume-max to 200 and mapped shared volume values to MPV volume 0–200. - Updated player volume feedback to display boosted levels up to 200%. - Preserved fallback behavior to system volume when the player controller is unavailable. This keeps brightness and system volume behavior intact while allowing playback-specific volume amplification during video playback.
This commit is contained in:
parent
17168387d8
commit
123549a1a5
6 changed files with 69 additions and 5 deletions
|
|
@ -393,6 +393,23 @@ actual fun PlatformPlayerSurface(
|
|||
exoPlayer.setPlaybackSpeed(speed)
|
||||
}
|
||||
|
||||
override fun currentPlayerVolume(): PlayerAudioLevel {
|
||||
val current = exoPlayer.volume.coerceIn(0f, 2f)
|
||||
return PlayerAudioLevel(
|
||||
fraction = current,
|
||||
isMuted = current <= 0.001f,
|
||||
)
|
||||
}
|
||||
|
||||
override fun setPlayerVolume(level: Float): PlayerAudioLevel {
|
||||
val target = level.coerceIn(0f, 2f)
|
||||
exoPlayer.volume = target
|
||||
return PlayerAudioLevel(
|
||||
fraction = target,
|
||||
isMuted = target <= 0.001f,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAudioTracks(): List<AudioTrack> =
|
||||
exoPlayer.extractAudioTracks(context)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ interface PlayerEngineController {
|
|||
fun seekBy(offsetMs: Long)
|
||||
fun retry()
|
||||
fun setPlaybackSpeed(speed: Float)
|
||||
fun currentPlayerVolume(): PlayerAudioLevel = PlayerAudioLevel(
|
||||
fraction = 1f,
|
||||
isMuted = false,
|
||||
)
|
||||
fun setPlayerVolume(level: Float): PlayerAudioLevel = PlayerAudioLevel(
|
||||
fraction = 1f,
|
||||
isMuted = false,
|
||||
)
|
||||
fun getAudioTracks(): List<AudioTrack>
|
||||
fun getSubtitleTracks(): List<SubtitleTrack>
|
||||
fun selectAudioTrack(index: Int)
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ private const val PlayerLockedOverlayDurationMs = 2_000L
|
|||
private const val PlayerLeftGestureBoundary = 0.4f
|
||||
private const val PlayerRightGestureBoundary = 0.6f
|
||||
private const val PlayerVerticalGestureSensitivity = 1f
|
||||
private const val PlayerMaxVolumeBoost = 2f
|
||||
private const val PlayerSeekProgressSyncDebounceMs = 700L
|
||||
private const val P2pInitialPreloadTargetBytes = 5_242_880L
|
||||
/** Hard ceiling for next-episode stream search to prevent hanging forever. */
|
||||
|
|
@ -993,7 +994,7 @@ fun PlayerScreen(
|
|||
}
|
||||
|
||||
fun showVolumeFeedback(level: PlayerAudioLevel) {
|
||||
val percentage = (level.fraction.coerceIn(0f, 1f) * 100f).roundToInt()
|
||||
val percentage = (level.fraction.coerceIn(0f, PlayerMaxVolumeBoost) * 100f).roundToInt()
|
||||
showGestureFeedback(
|
||||
GestureFeedbackState(
|
||||
messageRes = if (level.isMuted) {
|
||||
|
|
@ -2344,7 +2345,7 @@ fun PlayerScreen(
|
|||
},
|
||||
)
|
||||
}
|
||||
.pointerInput(gestureController, layoutSize) {
|
||||
.pointerInput(gestureController, playerController, layoutSize) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
if (playerControlsLockedState.value) {
|
||||
|
|
@ -2371,7 +2372,7 @@ fun PlayerScreen(
|
|||
null
|
||||
}
|
||||
val initialVolume = if (region == PlayerSideGesture.Volume) {
|
||||
controller?.currentVolume()
|
||||
playerController?.currentPlayerVolume() ?: controller?.currentVolume()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -2456,8 +2457,11 @@ fun PlayerScreen(
|
|||
PlayerGestureMode.Volume -> {
|
||||
val gestureDeltaFraction =
|
||||
(-totalDy / height) * PlayerVerticalGestureSensitivity
|
||||
controller?.setVolume((initialVolume?.fraction ?: 0f) + gestureDeltaFraction)
|
||||
?.let(showVolumeFeedbackState.value)
|
||||
val target = ((initialVolume?.fraction ?: 1f) + gestureDeltaFraction)
|
||||
.coerceIn(0f, PlayerMaxVolumeBoost)
|
||||
val level = playerController?.setPlayerVolume(target)
|
||||
?: controller?.setVolume(target.coerceIn(0f, 1f))
|
||||
level?.let(showVolumeFeedbackState.value)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ interface NuvioPlayerBridge {
|
|||
gamma: Int,
|
||||
)
|
||||
fun setPlaybackSpeed(speed: Float)
|
||||
fun getVolume(): Float
|
||||
fun setVolume(volume: Float)
|
||||
fun setResizeMode(mode: Int) // 0=Fit, 1=Fill, 2=Zoom
|
||||
fun getAudioTrackCount(): Int
|
||||
fun getAudioTrackIndex(at: Int): Int
|
||||
|
|
|
|||
|
|
@ -87,6 +87,23 @@ actual fun PlatformPlayerSurface(
|
|||
bridge.setPlaybackSpeed(speed)
|
||||
}
|
||||
|
||||
override fun currentPlayerVolume(): PlayerAudioLevel {
|
||||
val current = bridge.getVolume().coerceIn(0f, 2f)
|
||||
return PlayerAudioLevel(
|
||||
fraction = current,
|
||||
isMuted = current <= 0.001f,
|
||||
)
|
||||
}
|
||||
|
||||
override fun setPlayerVolume(level: Float): PlayerAudioLevel {
|
||||
val target = level.coerceIn(0f, 2f)
|
||||
bridge.setVolume(target)
|
||||
return PlayerAudioLevel(
|
||||
fraction = target,
|
||||
isMuted = target <= 0.001f,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAudioTracks(): List<AudioTrack> {
|
||||
val count = bridge.getAudioTrackCount()
|
||||
return (0 until count).map { i ->
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ final class MPVPlayerBridgeImpl: NSObject, NuvioPlayerBridge {
|
|||
)
|
||||
}
|
||||
func setPlaybackSpeed(speed: Float) { playerVC?.setSpeed(speed) }
|
||||
func getVolume() -> Float { playerVC?.getVolume() ?? 1.0 }
|
||||
func setVolume(volume: Float) { playerVC?.setVolume(volume) }
|
||||
func setResizeMode(mode: Int32) { playerVC?.setResize(Int(mode)) }
|
||||
|
||||
// Audio tracks
|
||||
|
|
@ -319,6 +321,8 @@ final class MPVPlayerViewController: UIViewController {
|
|||
checkError(mpv_set_option_string(mpv, "hwdec", "auto"))
|
||||
checkError(mpv_set_option_string(mpv, "audio-channels", "stereo"))
|
||||
checkError(mpv_set_option_string(mpv, "audio-fallback-to-null", "yes"))
|
||||
checkError(mpv_set_option_string(mpv, "volume-max", "200"))
|
||||
checkError(mpv_set_option_string(mpv, "volume", "100"))
|
||||
checkError(mpv_set_option_string(mpv, "vulkan-swap-mode", "fifo"))
|
||||
checkError(mpv_set_option_string(mpv, "vulkan-queue-count", "1"))
|
||||
checkError(mpv_set_option_string(mpv, "vulkan-async-compute", "no"))
|
||||
|
|
@ -517,6 +521,18 @@ final class MPVPlayerViewController: UIViewController {
|
|||
mpv_set_property(mpv, "speed", MPV_FORMAT_DOUBLE, &s)
|
||||
}
|
||||
|
||||
func getVolume() -> Float {
|
||||
guard mpv != nil else { return 1.0 }
|
||||
let volume = getDouble("volume") / 100.0
|
||||
return Float(max(0.0, min(2.0, volume)))
|
||||
}
|
||||
|
||||
func setVolume(_ volume: Float) {
|
||||
guard mpv != nil else { return }
|
||||
var value = Double(max(0.0, min(2.0, volume)) * 100.0)
|
||||
checkError(mpv_set_property(mpv, "volume", MPV_FORMAT_DOUBLE, &value))
|
||||
}
|
||||
|
||||
func setResize(_ mode: Int) {
|
||||
guard mpv != nil else { return }
|
||||
switch mode {
|
||||
|
|
|
|||
Loading…
Reference in a new issue