Repository navigation
This release reworks how AudioPlayer manages state. The player's playback state (playing, paused, stopped) is now separate from whether the underlying AVAudioEngine is running. It also fixes several concurrency bugs and makes the player respond faster.
⚠️ Breaking API Changes
AudioPlayer playback control
resume()has been removed. Callplay()to resume a paused player.togglePlayPause()still works as before.pause()now returnsVoid(it used to returnBool).stop()always cancels the current decoder and clears the queue, even if the player is already stopped. Before, calling it on a stopped player did nothing.- The engine is now paused when the player is paused. A paused player no longer uses power for audio I/O and the system no longer sees it as playing.
engineIsRunningis nowfalsewhile the player is paused. PlaybackStatemeans something different. It now describes the playback session, not the engine. The player can stop and restart the engine internally (for example, when the processing graph changes or the hardware configuration changes) without changingplaybackState. If the engine can't be restarted, the player switches to paused, reports the error to the delegate and keeps its decoders.
End-of-audio behavior
- Implementing
audioPlayerEndOfAudio(_:)no longer stops the player from stopping. The player now first does whatever the newendOfAudioActionproperty says, then calls the delegate. The default action is.stop. If your app relied on implementing the delegate method to keep the engine running, setendOfAudioAction = .continue(or.pause).
iOS audio session interruptions
-
The player no longer activates the app's
AVAudioSessionafter an interruption. When an interruption begins, the player now pauses instead of stopping, so the session and decoders are kept. Playback resumes automatically only if all of these are true:- the player was playing when the interruption began
- the player wasn't paused or stopped during the interruption
- the system passes
.shouldResume
To allow automatic resumption, activate the audio session in
audioPlayer(_:audioSessionInterruption:). To prevent it, callpause()there.
Reentrancy restrictions (now documented)
- The
modifyProcessingGraph(_:)block and the delegate's node-reconnection callback must not call any player method other thansourceNode. Doing so may crash or deadlock.
✨ New
AudioPlayer.EndOfAudioAction(.stop,.pause,.continue) and theendOfAudioActionproperty let you choose what happens when all queued audio has been rendered. With.continue, the engine keeps running and audio you enqueue later starts playing without callingplay().togglePlayPause()now checks and changes the playback state in one atomic step.
🐛 Fixes & Improvements
- Fixed a possible use-after-free of decoder state (#1040).
- Fixed a possible self-join when an
AudioPlayeris deallocated (#1015). - The player now outputs silence while decoder cancellations are pending, and handles those cancellations before decoding starts again (#1022, #1023).
- End of audio is now detected and handled in a single critical section (#1049).
setOutputDeviceID()now holds the engine lock (#1052).- Render events are processed with less delay, and seek events are handled better (#1007, #1054).
- A longer event-processing timeout is used while the engine is paused, so the player wakes up less often (#1041).
🔧 Internal
- Dropped the
CXXUnfairLockandCXXDispatchSemaphoredependencies. They're replaced with smaller local implementations. - Replaced
AudioFileWrapperandExtAudioFileWrapperwithunique_ptr/unique_refand custom deleters. - Release builds now define
NDEBUG, and internal checks useassert(). - Input/output tests moved to Swift Testing.
- Plus cleanups to include ordering,
constcorrectness and naming.
Full Changelog: 0.14.0...0.15.0