mirror of
https://github.com/yattee/yattee.git
synced 2026-11-04 10:57:18 +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-time", format: MPV_FORMAT_DOUBLE)
|
||||||
observeProperty("demuxer-cache-state", format: MPV_FORMAT_NODE)
|
observeProperty("demuxer-cache-state", format: MPV_FORMAT_NODE)
|
||||||
observeProperty("speed", format: MPV_FORMAT_DOUBLE)
|
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("core-idle", format: MPV_FORMAT_FLAG)
|
||||||
observeProperty("seeking", format: MPV_FORMAT_FLAG)
|
observeProperty("seeking", format: MPV_FORMAT_FLAG)
|
||||||
// Cache-related properties for debugging playback start issues
|
// 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).
|
/// Internal property setter (must be called on mpvQueue).
|
||||||
private func setPropertyUnsafe(_ name: String, _ value: Any) {
|
private func setPropertyUnsafe(_ name: String, _ value: Any) {
|
||||||
guard let mpv, !isDestroyed else { return }
|
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:
|
case MPV_EVENT_LOG_MESSAGE:
|
||||||
if let data = event.data {
|
if let data = event.data {
|
||||||
let msg = data.assumingMemoryBound(to: mpv_event_log_message.self).pointee
|
let msg = data.assumingMemoryBound(to: mpv_event_log_message.self).pointee
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
get { _volume }
|
get { _volume }
|
||||||
set {
|
set {
|
||||||
_volume = newValue
|
_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 }
|
get { _isMuted }
|
||||||
set {
|
set {
|
||||||
_isMuted = newValue
|
_isMuted = newValue
|
||||||
mpvClient?.setProperty("mute", newValue)
|
mpvClient?.setVolume(Double(_volume * 100), muted: newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1566,16 +1566,6 @@ extension MPVBackend: MPVClientDelegate {
|
|||||||
_rate = Float(speed)
|
_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":
|
case "core-idle":
|
||||||
// core-idle indicates MPV is processing, but doesn't mean frames are ready
|
// core-idle indicates MPV is processing, but doesn't mean frames are ready
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user