From 406ef99cfc2a7cba567cfafdef1d667656361c51 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:48:35 +0530 Subject: [PATCH] feat(MPV): Spatial Audio for ios --- MPVKit | 2 +- .../player/PlayerSettingsStorage.android.kt | 11 ++++ .../composeResources/values/strings.xml | 7 +++ .../nuvio/app/features/player/PlayerModels.kt | 9 +++ .../player/PlayerSettingsRepository.kt | 14 +++++ .../features/player/PlayerSettingsStorage.kt | 2 + .../features/settings/PlaybackSettingsPage.kt | 60 +++++++++++++++++-- .../app/features/player/NuvioPlayerBridge.kt | 1 + .../app/features/player/PlayerEngine.ios.kt | 1 + .../player/PlayerSettingsStorage.ios.kt | 11 ++++ iosApp/iosApp/Player/MPVPlayerBridge.swift | 27 ++++++++- 11 files changed, 138 insertions(+), 7 deletions(-) diff --git a/MPVKit b/MPVKit index 97923266..2e81303b 160000 --- a/MPVKit +++ b/MPVKit @@ -1 +1 @@ -Subproject commit 97923266e43d52bbca33a911fd6a7f9a1bcf35cb +Subproject commit 2e81303b74762424f4187c905d532a716c8e1ff5 diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt index 3dc73ce1..2a98502d 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt @@ -70,6 +70,7 @@ actual object PlayerSettingsStorage { private const val iosTargetPrimariesKey = "ios_target_primaries" private const val iosTargetTransferKey = "ios_target_transfer" private const val iosHardwareDecoderModeKey = "ios_hardware_decoder_mode" + private const val iosAudioOutputModeKey = "ios_audio_output_mode" private const val iosExtendedDynamicRangeEnabledKey = "ios_extended_dynamic_range_enabled" private const val iosTargetColorspaceHintEnabledKey = "ios_target_colorspace_hint_enabled" private const val iosHdrComputePeakEnabledKey = "ios_hdr_compute_peak_enabled" @@ -129,6 +130,7 @@ actual object PlayerSettingsStorage { iosTargetPrimariesKey, iosTargetTransferKey, iosHardwareDecoderModeKey, + iosAudioOutputModeKey, iosExtendedDynamicRangeEnabledKey, iosTargetColorspaceHintEnabledKey, iosHdrComputePeakEnabledKey, @@ -865,6 +867,13 @@ actual object PlayerSettingsStorage { preferences?.edit()?.putString(ProfileScopedKey.of(iosHardwareDecoderModeKey), mode)?.apply() } + actual fun loadIosAudioOutputMode(): String? = + preferences?.getString(ProfileScopedKey.of(iosAudioOutputModeKey), null) + + actual fun saveIosAudioOutputMode(mode: String) { + preferences?.edit()?.putString(ProfileScopedKey.of(iosAudioOutputModeKey), mode)?.apply() + } + private fun loadIosBoolean(keyBase: String, defaultValue: Boolean): Boolean? = preferences?.let { sharedPreferences -> val key = ProfileScopedKey.of(keyBase) @@ -994,6 +1003,7 @@ actual object PlayerSettingsStorage { loadIosTargetPrimaries()?.let { put(iosTargetPrimariesKey, encodeSyncString(it)) } loadIosTargetTransfer()?.let { put(iosTargetTransferKey, encodeSyncString(it)) } loadIosHardwareDecoderMode()?.let { put(iosHardwareDecoderModeKey, encodeSyncString(it)) } + loadIosAudioOutputMode()?.let { put(iosAudioOutputModeKey, encodeSyncString(it)) } loadIosExtendedDynamicRangeEnabled()?.let { put(iosExtendedDynamicRangeEnabledKey, encodeSyncBoolean(it)) } loadIosTargetColorspaceHintEnabled()?.let { put(iosTargetColorspaceHintEnabledKey, encodeSyncBoolean(it)) } loadIosHdrComputePeakEnabled()?.let { put(iosHdrComputePeakEnabledKey, encodeSyncBoolean(it)) } @@ -1061,6 +1071,7 @@ actual object PlayerSettingsStorage { payload.decodeSyncString(iosTargetPrimariesKey)?.let(::saveIosTargetPrimaries) payload.decodeSyncString(iosTargetTransferKey)?.let(::saveIosTargetTransfer) payload.decodeSyncString(iosHardwareDecoderModeKey)?.let(::saveIosHardwareDecoderMode) + payload.decodeSyncString(iosAudioOutputModeKey)?.let(::saveIosAudioOutputMode) payload.decodeSyncBoolean(iosExtendedDynamicRangeEnabledKey)?.let(::saveIosExtendedDynamicRangeEnabled) payload.decodeSyncBoolean(iosTargetColorspaceHintEnabledKey)?.let(::saveIosTargetColorspaceHintEnabled) payload.decodeSyncBoolean(iosHdrComputePeakEnabledKey)?.let(::saveIosHdrComputePeakEnabled) diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index fd8226bf..0c34f27f 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1560,6 +1560,12 @@ Let mpv target the active display color space by default. Target primaries Target transfer + + iOS audio output + Audio output + Try AVFoundation first, then fall back to AudioUnit. + Experimental support for Spatial Audio and multichannel output. + Use the legacy AudioUnit output. Result Management Max results @@ -1660,6 +1666,7 @@ Capture Hardware decoder + Audio output Target primaries Target transfer diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt index 94afea88..8205b616 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt @@ -146,6 +146,15 @@ enum class IosHardwareDecoderMode( Off("no", "Off"), } +enum class IosAudioOutputMode( + val mpvValue: String, + val label: String, +) { + Auto("avfoundation,audiounit,", "Auto"), + AvFoundation("avfoundation", "AVFoundation"), + AudioUnit("audiounit", "AudioUnit"), +} + @Composable fun IosVideoOutputPreset.localizedLabel(): String = when (this) { IosVideoOutputPreset.NativeEdr -> stringResource(Res.string.player_ios_preset_native_edr_label) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt index 66020e8e..a4c94820 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt @@ -74,6 +74,7 @@ data class PlayerSettingsUiState( val iosTargetPrimaries: IosTargetPrimaries = IosTargetPrimaries.Auto, val iosTargetTransfer: IosTargetTransfer = IosTargetTransfer.Auto, val iosHardwareDecoderMode: IosHardwareDecoderMode = IosHardwareDecoderMode.VideoToolbox, + val iosAudioOutputMode: IosAudioOutputMode = IosAudioOutputMode.Auto, val iosExtendedDynamicRangeEnabled: Boolean = true, val iosTargetColorspaceHintEnabled: Boolean = true, val iosHdrComputePeakEnabled: Boolean = true, @@ -132,6 +133,7 @@ object PlayerSettingsRepository { private var iosTargetPrimaries = IosTargetPrimaries.Auto private var iosTargetTransfer = IosTargetTransfer.Auto private var iosHardwareDecoderMode = IosHardwareDecoderMode.VideoToolbox + private var iosAudioOutputMode = IosAudioOutputMode.Auto private var iosExtendedDynamicRangeEnabled = true private var iosTargetColorspaceHintEnabled = true private var iosHdrComputePeakEnabled = true @@ -195,6 +197,7 @@ object PlayerSettingsRepository { iosTargetPrimaries = IosTargetPrimaries.Auto iosTargetTransfer = IosTargetTransfer.Auto iosHardwareDecoderMode = IosHardwareDecoderMode.VideoToolbox + iosAudioOutputMode = IosAudioOutputMode.Auto iosExtendedDynamicRangeEnabled = true iosTargetColorspaceHintEnabled = true iosHdrComputePeakEnabled = true @@ -318,6 +321,9 @@ object PlayerSettingsRepository { iosHardwareDecoderMode = PlayerSettingsStorage.loadIosHardwareDecoderMode() ?.let { runCatching { IosHardwareDecoderMode.valueOf(it) }.getOrNull() } ?: IosHardwareDecoderMode.VideoToolbox + iosAudioOutputMode = PlayerSettingsStorage.loadIosAudioOutputMode() + ?.let { runCatching { IosAudioOutputMode.valueOf(it) }.getOrNull() } + ?: IosAudioOutputMode.Auto iosExtendedDynamicRangeEnabled = PlayerSettingsStorage.loadIosExtendedDynamicRangeEnabled() ?: true iosTargetColorspaceHintEnabled = PlayerSettingsStorage.loadIosTargetColorspaceHintEnabled() ?: true iosHdrComputePeakEnabled = PlayerSettingsStorage.loadIosHdrComputePeakEnabled() ?: true @@ -716,6 +722,13 @@ object PlayerSettingsRepository { PlayerSettingsStorage.saveIosHardwareDecoderMode(mode.name) } + fun setIosAudioOutputMode(mode: IosAudioOutputMode) { + ensureLoaded() + iosAudioOutputMode = mode + publish() + PlayerSettingsStorage.saveIosAudioOutputMode(mode.name) + } + fun setIosExtendedDynamicRangeEnabled(enabled: Boolean) { ensureLoaded() iosVideoOutputPreset = IosVideoOutputPreset.Custom @@ -853,6 +866,7 @@ object PlayerSettingsRepository { iosTargetPrimaries = iosTargetPrimaries, iosTargetTransfer = iosTargetTransfer, iosHardwareDecoderMode = iosHardwareDecoderMode, + iosAudioOutputMode = iosAudioOutputMode, iosExtendedDynamicRangeEnabled = iosExtendedDynamicRangeEnabled, iosTargetColorspaceHintEnabled = iosTargetColorspaceHintEnabled, iosHdrComputePeakEnabled = iosHdrComputePeakEnabled, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt index e8da2d41..0196d338 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt @@ -106,6 +106,8 @@ internal expect object PlayerSettingsStorage { fun saveIosTargetTransfer(transfer: String) fun loadIosHardwareDecoderMode(): String? fun saveIosHardwareDecoderMode(mode: String) + fun loadIosAudioOutputMode(): String? + fun saveIosAudioOutputMode(mode: String) fun loadIosExtendedDynamicRangeEnabled(): Boolean? fun saveIosExtendedDynamicRangeEnabled(enabled: Boolean) fun loadIosTargetColorspaceHintEnabled(): Boolean? diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt index 78591459..ed5ce9da 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt @@ -59,6 +59,7 @@ import com.nuvio.app.features.player.AudioLanguageOption import com.nuvio.app.features.player.AvailableLanguageOptions import com.nuvio.app.features.player.ExternalPlayerApp import com.nuvio.app.features.player.ExternalPlayerPlatform +import com.nuvio.app.features.player.IosAudioOutputMode import com.nuvio.app.features.player.IosHardwareDecoderMode import com.nuvio.app.features.player.localizedLabel import com.nuvio.app.features.player.IosTargetPrimaries @@ -267,6 +268,7 @@ private fun PlaybackSettingsSection( var showReuseCacheDurationDialog by remember { mutableStateOf(false) } var showDecoderPriorityDialog by remember { mutableStateOf(false) } var showHoldToSpeedValueDialog by remember { mutableStateOf(false) } + var showIosAudioOutputDialog by remember { mutableStateOf(false) } var showIosHardwareDecoderDialog by remember { mutableStateOf(false) } var showIosTargetPrimariesDialog by remember { mutableStateOf(false) } var showIosTargetTransferDialog by remember { mutableStateOf(false) } @@ -782,6 +784,20 @@ private fun PlaybackSettingsSection( } if (isIos) { + SettingsSection( + title = stringResource(Res.string.settings_playback_ios_audio_output_section), + isTablet = isTablet, + ) { + SettingsGroup(isTablet = isTablet) { + SettingsNavigationRow( + title = stringResource(Res.string.settings_playback_ios_audio_output), + description = autoPlayPlayerSettings.iosAudioOutputMode.label, + isTablet = isTablet, + onClick = { showIosAudioOutputDialog = true }, + ) + } + } + SettingsSection( title = stringResource(Res.string.settings_playback_ios_video_output), isTablet = isTablet, @@ -1263,6 +1279,27 @@ private fun PlaybackSettingsSection( ) } + if (showIosAudioOutputDialog) { + IosEnumSelectionDialog( + title = stringResource(Res.string.settings_playback_ios_audio_output_dialog), + options = IosAudioOutputMode.entries, + selected = autoPlayPlayerSettings.iosAudioOutputMode, + label = { it.label }, + description = { + when (it) { + IosAudioOutputMode.Auto -> stringResource(Res.string.settings_playback_ios_audio_output_auto_desc) + IosAudioOutputMode.AvFoundation -> stringResource(Res.string.settings_playback_ios_audio_output_avfoundation_desc) + IosAudioOutputMode.AudioUnit -> stringResource(Res.string.settings_playback_ios_audio_output_audiounit_desc) + } + }, + onSelect = { + PlayerSettingsRepository.setIosAudioOutputMode(it) + showIosAudioOutputDialog = false + }, + onDismiss = { showIosAudioOutputDialog = false }, + ) + } + if (showIosTargetPrimariesDialog) { IosEnumSelectionDialog( title = stringResource(Res.string.settings_playback_ios_target_primaries_dialog), @@ -1890,6 +1927,7 @@ private fun IosEnumSelectionDialog( options: List, selected: T, label: (T) -> String, + description: @Composable (T) -> String? = { null }, onSelect: (T) -> Unit, onDismiss: () -> Unit, ) { @@ -1918,6 +1956,7 @@ private fun IosEnumSelectionDialog( ) { options.forEach { option -> val isSelected = option == selected + val optionDescription = description(option) val containerColor = if (isSelected) { MaterialTheme.colorScheme.primary.copy(alpha = 0.14f) } else { @@ -1937,12 +1976,23 @@ private fun IosEnumSelectionDialog( .padding(horizontal = 14.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically, ) { - Text( - text = label(option), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, + Column( modifier = Modifier.weight(1f), - ) + verticalArrangement = Arrangement.spacedBy(3.dp), + ) { + Text( + text = label(option), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + if (optionDescription != null) { + Text( + text = optionDescription, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } Box( modifier = Modifier.size(24.dp), contentAlignment = Alignment.Center, diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/NuvioPlayerBridge.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/NuvioPlayerBridge.kt index 20f1f6fa..1db7a6c6 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/NuvioPlayerBridge.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/NuvioPlayerBridge.kt @@ -30,6 +30,7 @@ interface NuvioPlayerBridge { saturation: Int, gamma: Int, ) + fun configureAudioOutput(audioOutput: String) fun setPlaybackSpeed(speed: Float) fun setMuted(muted: Boolean) fun setResizeMode(mode: Int) // 0=Fit, 1=Fill, 2=Zoom diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerEngine.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerEngine.ios.kt index 5fa4ffe7..adfa0126 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerEngine.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerEngine.ios.kt @@ -309,6 +309,7 @@ actual fun PlatformPlayerSurface( } private fun NuvioPlayerBridge.applyIosVideoOutputSettings(settings: PlayerSettingsUiState) { + configureAudioOutput(audioOutput = settings.iosAudioOutputMode.mpvValue) configureVideoOutput( hardwareDecoder = settings.iosHardwareDecoderMode.mpvValue, targetColorspaceHint = settings.iosTargetColorspaceHintEnabled, diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt index d1e7a6ef..6da10aff 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt @@ -68,6 +68,7 @@ actual object PlayerSettingsStorage { private const val iosTargetPrimariesKey = "ios_target_primaries" private const val iosTargetTransferKey = "ios_target_transfer" private const val iosHardwareDecoderModeKey = "ios_hardware_decoder_mode" + private const val iosAudioOutputModeKey = "ios_audio_output_mode" private const val iosExtendedDynamicRangeEnabledKey = "ios_extended_dynamic_range_enabled" private const val iosTargetColorspaceHintEnabledKey = "ios_target_colorspace_hint_enabled" private const val iosHdrComputePeakEnabledKey = "ios_hdr_compute_peak_enabled" @@ -127,6 +128,7 @@ actual object PlayerSettingsStorage { iosTargetPrimariesKey, iosTargetTransferKey, iosHardwareDecoderModeKey, + iosAudioOutputModeKey, iosExtendedDynamicRangeEnabledKey, iosTargetColorspaceHintEnabledKey, iosHdrComputePeakEnabledKey, @@ -735,6 +737,13 @@ actual object PlayerSettingsStorage { NSUserDefaults.standardUserDefaults.setObject(mode, forKey = ProfileScopedKey.of(iosHardwareDecoderModeKey)) } + actual fun loadIosAudioOutputMode(): String? = + NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(iosAudioOutputModeKey)) + + actual fun saveIosAudioOutputMode(mode: String) { + NSUserDefaults.standardUserDefaults.setObject(mode, forKey = ProfileScopedKey.of(iosAudioOutputModeKey)) + } + actual fun loadIosExtendedDynamicRangeEnabled(): Boolean? = loadBoolean(iosExtendedDynamicRangeEnabledKey) @@ -844,6 +853,7 @@ actual object PlayerSettingsStorage { loadIosTargetPrimaries()?.let { put(iosTargetPrimariesKey, encodeSyncString(it)) } loadIosTargetTransfer()?.let { put(iosTargetTransferKey, encodeSyncString(it)) } loadIosHardwareDecoderMode()?.let { put(iosHardwareDecoderModeKey, encodeSyncString(it)) } + loadIosAudioOutputMode()?.let { put(iosAudioOutputModeKey, encodeSyncString(it)) } loadIosExtendedDynamicRangeEnabled()?.let { put(iosExtendedDynamicRangeEnabledKey, encodeSyncBoolean(it)) } loadIosTargetColorspaceHintEnabled()?.let { put(iosTargetColorspaceHintEnabledKey, encodeSyncBoolean(it)) } loadIosHdrComputePeakEnabled()?.let { put(iosHdrComputePeakEnabledKey, encodeSyncBoolean(it)) } @@ -910,6 +920,7 @@ actual object PlayerSettingsStorage { payload.decodeSyncString(iosTargetPrimariesKey)?.let(::saveIosTargetPrimaries) payload.decodeSyncString(iosTargetTransferKey)?.let(::saveIosTargetTransfer) payload.decodeSyncString(iosHardwareDecoderModeKey)?.let(::saveIosHardwareDecoderMode) + payload.decodeSyncString(iosAudioOutputModeKey)?.let(::saveIosAudioOutputMode) payload.decodeSyncBoolean(iosExtendedDynamicRangeEnabledKey)?.let(::saveIosExtendedDynamicRangeEnabled) payload.decodeSyncBoolean(iosTargetColorspaceHintEnabledKey)?.let(::saveIosTargetColorspaceHintEnabled) payload.decodeSyncBoolean(iosHdrComputePeakEnabledKey)?.let(::saveIosHdrComputePeakEnabled) diff --git a/iosApp/iosApp/Player/MPVPlayerBridge.swift b/iosApp/iosApp/Player/MPVPlayerBridge.swift index 7021090a..37111147 100644 --- a/iosApp/iosApp/Player/MPVPlayerBridge.swift +++ b/iosApp/iosApp/Player/MPVPlayerBridge.swift @@ -59,6 +59,9 @@ final class MPVPlayerBridgeImpl: NSObject, NuvioPlayerBridge { gamma: Int(gamma) ) } + func configureAudioOutput(audioOutput: String) { + playerVC?.configureAudioOutput(audioOutput: audioOutput) + } func setPlaybackSpeed(speed: Float) { playerVC?.setSpeed(speed) } func setMuted(muted: Bool) { playerVC?.setMuted(muted) } func setResizeMode(mode: Int32) { playerVC?.setResize(Int(mode)) } @@ -192,6 +195,8 @@ private struct PendingLoadRequest { final class MPVPlayerViewController: UIViewController { + private static let defaultAudioOutput = "avfoundation,audiounit," + private let errorStateLock = NSLock() private var metalLayer = MetalLayer() private var lastAppliedDrawableSize: CGSize = .zero @@ -318,7 +323,8 @@ final class MPVPlayerViewController: UIViewController { checkError(mpv_set_option_string(mpv, "gpu-api", "vulkan")) checkError(mpv_set_option_string(mpv, "gpu-context", "moltenvk")) checkError(mpv_set_option_string(mpv, "hwdec", "videotoolbox")) - checkError(mpv_set_option_string(mpv, "audio-channels", "stereo")) + checkError(mpv_set_option_string(mpv, "ao", Self.defaultAudioOutput)) + checkError(mpv_set_option_string(mpv, "audio-channels", "auto")) checkError(mpv_set_option_string(mpv, "audio-fallback-to-null", "yes")) checkError(mpv_set_option_string(mpv, "vulkan-swap-mode", "fifo")) checkError(mpv_set_option_string(mpv, "vulkan-queue-count", "1")) @@ -512,6 +518,11 @@ final class MPVPlayerViewController: UIViewController { setVideoEqualizer("gamma", gamma) } + func configureAudioOutput(audioOutput: String) { + guard mpv != nil else { return } + setStringProperty("ao", audioOutput) + } + func setSpeed(_ speed: Float) { guard mpv != nil else { return } var s = Double(speed) @@ -853,6 +864,7 @@ final class MPVPlayerViewController: UIViewController { self.clearPlaybackError() self.isPlayerLoading = false self.updateState() + self.logCurrentAudioOutput() } case MPV_EVENT_END_FILE: if let data = eventPtr.pointee.data { @@ -943,6 +955,19 @@ final class MPVPlayerViewController: UIViewController { return Int(data) } + private func logCurrentAudioOutput() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + guard let self, self.mpv != nil else { return } + let currentAo = self.getString("current-ao") ?? "unknown" + let channels = self.getString("audio-out-params/hr-channels") + ?? self.getString("audio-params/hr-channels") + ?? "unknown" + let channelCount = self.getInt("audio-out-params/channel-count") + let codec = self.getString("audio-codec-name") ?? "unknown" + print("[MPV] Audio output: ao=\(currentAo), channels=\(channels), channelCount=\(channelCount), codec=\(codec)") + } + } + private func checkError(_ status: CInt) { if status < 0 { print("[MPV] API error: \(String(cString: mpv_error_string(status)))")