mirror of
https://github.com/yattee/yattee.git
synced 2026-10-23 13:12:53 +00:00
Apply volume and mute on the avfoundation renderer directly
mpv's volume/mute properties are software volume applied before samples reach the AO, and AVSampleBufferAudioRenderer queues ~2s ahead, so slider and mute changes were heard only after the queue drained. With the avfoundation AO active, drive ao-volume/ao-mute (renderer volume, instant) and keep software volume neutral; other AOs keep software volume. Re-apply on every audio reconfig since a new renderer starts at full volume.
This commit is contained in:
@@ -628,8 +628,6 @@ final class MPVClient: @unchecked Sendable {
|
||||
observeProperty("demuxer-cache-time", format: MPV_FORMAT_DOUBLE)
|
||||
observeProperty("demuxer-cache-state", format: MPV_FORMAT_NODE)
|
||||
observeProperty("speed", format: MPV_FORMAT_DOUBLE)
|
||||
observeProperty("volume", format: MPV_FORMAT_DOUBLE)
|
||||
observeProperty("mute", format: MPV_FORMAT_FLAG)
|
||||
observeProperty("core-idle", format: MPV_FORMAT_FLAG)
|
||||
observeProperty("seeking", format: MPV_FORMAT_FLAG)
|
||||
// Cache-related properties for debugging playback start issues
|
||||
@@ -891,6 +889,54 @@ final class MPVClient: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Desired volume (0–100) and mute state; only touched on mpvQueue.
|
||||
private var desiredVolume: Double = 100
|
||||
private var desiredMute = false
|
||||
|
||||
/// Set the playback volume (0–100) and mute state.
|
||||
///
|
||||
/// mpv's `volume`/`mute` properties are software volume applied before
|
||||
/// samples reach the AO, so with avfoundation (AVSampleBufferAudioRenderer
|
||||
/// queues ~2s ahead) changes are heard only after the queue drains. When
|
||||
/// avfoundation is active, drive the renderer via `ao-volume`/`ao-mute`
|
||||
/// instead (instant) and keep software volume neutral. Other AOs
|
||||
/// (audiounit/coreaudio) keep using software volume.
|
||||
func setVolume(_ volume: Double, muted: Bool) {
|
||||
mpvQueue.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.desiredVolume = volume
|
||||
self.desiredMute = muted
|
||||
self.applyVolumeUnsafe()
|
||||
}
|
||||
}
|
||||
|
||||
/// Must be called on mpvQueue. Re-run on every audio reconfig, since a new
|
||||
/// AO instance (new renderer) starts at full volume.
|
||||
private func applyVolumeUnsafe() {
|
||||
guard let mpv, !isDestroyed else { return }
|
||||
|
||||
var currentAo: String?
|
||||
if let cString = mpv_get_property_string(mpv, "current-ao") {
|
||||
currentAo = String(cString: cString)
|
||||
mpv_free(cString)
|
||||
}
|
||||
|
||||
var softVolume = desiredVolume
|
||||
var softMute: Int32 = desiredMute ? 1 : 0
|
||||
if currentAo == "avfoundation" {
|
||||
var aoVolume = desiredVolume
|
||||
var aoMute: Int32 = desiredMute ? 1 : 0
|
||||
let volumeResult = mpv_set_property(mpv, "ao-volume", MPV_FORMAT_DOUBLE, &aoVolume)
|
||||
let muteResult = mpv_set_property(mpv, "ao-mute", MPV_FORMAT_FLAG, &aoMute)
|
||||
if volumeResult >= 0 && muteResult >= 0 {
|
||||
softVolume = 100
|
||||
softMute = 0
|
||||
}
|
||||
}
|
||||
mpv_set_property(mpv, "volume", MPV_FORMAT_DOUBLE, &softVolume)
|
||||
mpv_set_property(mpv, "mute", MPV_FORMAT_FLAG, &softMute)
|
||||
}
|
||||
|
||||
/// Internal property setter (must be called on mpvQueue).
|
||||
private func setPropertyUnsafe(_ name: String, _ value: Any) {
|
||||
guard let mpv, !isDestroyed else { return }
|
||||
@@ -1452,6 +1498,11 @@ final class MPVClient: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
case MPV_EVENT_AUDIO_RECONFIG:
|
||||
mpvQueue.async { [weak self] in
|
||||
self?.applyVolumeUnsafe()
|
||||
}
|
||||
|
||||
case MPV_EVENT_LOG_MESSAGE:
|
||||
if let data = event.data {
|
||||
let msg = data.assumingMemoryBound(to: mpv_event_log_message.self).pointee
|
||||
|
||||
@@ -48,7 +48,7 @@ final class MPVBackend: PlayerBackend {
|
||||
get { _volume }
|
||||
set {
|
||||
_volume = newValue
|
||||
mpvClient?.setProperty("volume", Double(newValue * 100))
|
||||
mpvClient?.setVolume(Double(newValue * 100), muted: _isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ final class MPVBackend: PlayerBackend {
|
||||
get { _isMuted }
|
||||
set {
|
||||
_isMuted = newValue
|
||||
mpvClient?.setProperty("mute", newValue)
|
||||
mpvClient?.setVolume(Double(_volume * 100), muted: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1566,16 +1566,6 @@ extension MPVBackend: MPVClientDelegate {
|
||||
_rate = Float(speed)
|
||||
}
|
||||
|
||||
case "volume":
|
||||
if let vol = value as? Double {
|
||||
_volume = Float(vol / 100)
|
||||
}
|
||||
|
||||
case "mute":
|
||||
if let muted = value as? Bool {
|
||||
_isMuted = muted
|
||||
}
|
||||
|
||||
case "core-idle":
|
||||
// core-idle indicates MPV is processing, but doesn't mean frames are ready
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user