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)))")