Skip to content

Commit ab5bcb5

Browse files
address ios bugs
1 parent 0892033 commit ab5bcb5

File tree

1 file changed

+35
-13
lines changed

1 file changed

+35
-13
lines changed

ios/SoundPlayer.swift

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,19 @@ class SoundPlayer: SoundPlayerManaging {
523523
if !self.isAudioEngineIsSetup {
524524
try ensureAudioEngineIsSetup()
525525
}
526+
527+
// Ensure playback session configuration to avoid VPIO constraints at non-48k sample rates
528+
let session = AVAudioSession.sharedInstance()
529+
do {
530+
if session.category != .playback {
531+
try session.setActive(false, options: .notifyOthersOnDeactivation)
532+
try session.setCategory(.playback, mode: .default, options: [.defaultToSpeaker])
533+
try session.setActive(true)
534+
Logger.debug("[SoundPlayer] Switched AVAudioSession to .playback for output")
535+
}
536+
} catch {
537+
Logger.debug("[SoundPlayer] Failed to switch AVAudioSession to playback: \(error)")
538+
}
526539

527540
guard let buffer = try processAudioChunk(base64String, commonFormat: commonFormat) else {
528541
Logger.debug("[SoundPlayer] Failed to process audio chunk")
@@ -579,11 +592,8 @@ class SoundPlayer: SoundPlayerManaging {
579592
/// 3. Scheduling the next audio buffer for playback
580593
/// 4. Handling completion callbacks and recursively playing the next chunk
581594
private func playNextInQueue() {
582-
// Start the audio player node if it's not already playing
583-
if !self.audioPlayerNode.isPlaying {
584-
Logger.debug("[SoundPlayer] Starting Player")
585-
self.audioPlayerNode.play()
586-
}
595+
// Start the player only when scheduling the first buffer to avoid underruns
596+
// We'll call play() right after scheduling below if needed.
587597

588598
// Use a dedicated queue for buffer access to avoid blocking the main thread
589599
self.bufferAccessQueue.async {
@@ -600,22 +610,29 @@ class SoundPlayer: SoundPlayerManaging {
600610
// Remove the buffer from the queue immediately to avoid playing it twice
601611
self.audioQueue.removeFirst()
602612

603-
// If the buffer sample rate doesn't match the hardware output, resample for reliable playback
604-
var buffer = originalBuffer
613+
// Use AVAudioEngine's built-in format converter between player->mixer->output.
614+
// Just log if there is a mismatch so we can diagnose without altering the buffer.
615+
let buffer = originalBuffer
605616
let outputFormat = self.audioEngine.outputNode.outputFormat(forBus: 0)
606617
let inputSR = buffer.format.sampleRate
607618
let outputSR = outputFormat.sampleRate
608619
if abs(inputSR - outputSR) > 0.5 {
609-
Logger.debug("[SoundPlayer] Resampling buffer: inputSR=\(inputSR) -> outputSR=\(outputSR)")
610-
if let resampled = AudioUtils.resampleAudioBuffer(buffer, from: inputSR, to: outputSR) {
611-
buffer = resampled
612-
} else {
613-
Logger.debug("[SoundPlayer] Resampling failed; proceeding with original buffer (may sound off)")
620+
Logger.debug("[SoundPlayer] Format SR mismatch (engine will convert): bufferSR=\(inputSR) -> outputSR=\(outputSR)")
621+
}
622+
623+
// Optional: apply tiny fade-in ramp on first 64 frames to avoid clicks
624+
var scheduledBuffer = buffer
625+
if self.segmentsLeftToPlay == 0, let channelData = buffer.floatChannelData {
626+
let rampFrames = min(Int(buffer.frameLength), 64)
627+
for i in 0..<rampFrames {
628+
let gain = Float(i) / Float(rampFrames)
629+
channelData.pointee[i] *= gain
614630
}
631+
scheduledBuffer = buffer
615632
}
616633

617634
// Schedule the buffer for playback with a completion handler
618-
self.audioPlayerNode.scheduleBuffer(buffer) { [weak self] in
635+
self.audioPlayerNode.scheduleBuffer(scheduledBuffer) { [weak self] in
619636
// ✅ Move to main queue to avoid blocking Core Audio's realtime thread
620637
DispatchQueue.main.async {
621638
guard let self = self else {
@@ -660,6 +677,11 @@ class SoundPlayer: SoundPlayerManaging {
660677
}
661678
}
662679
}
680+
681+
// Start the player after scheduling if it isn't already playing
682+
if !self.audioPlayerNode.isPlaying {
683+
self.audioPlayerNode.play()
684+
}
663685
}
664686
}
665687
}

0 commit comments

Comments
 (0)