Skip to content

Version 0.15.0

Latest

Choose a tag to compare

@sbooth sbooth released this 12 Nov 15:08
· 1 commit to main since this release
45c468d

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. Call play() to resume a paused player. togglePlayPause() still works as before.
  • pause() now returns Void (it used to return Bool).
  • 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. engineIsRunning is now false while the player is paused.
  • PlaybackState means 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 changing playbackState. 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 new endOfAudioAction property says, then calls the delegate. The default action is .stop. If your app relied on implementing the delegate method to keep the engine running, set endOfAudioAction = .continue (or .pause).

iOS audio session interruptions

  • The player no longer activates the app's AVAudioSession after 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, call pause() there.

Reentrancy restrictions (now documented)

  • The modifyProcessingGraph(_:) block and the delegate's node-reconnection callback must not call any player method other than sourceNode. Doing so may crash or deadlock.

✨ New

  • AudioPlayer.EndOfAudioAction (.stop, .pause, .continue) and the endOfAudioAction property 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 calling play().
  • 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 AudioPlayer is 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 CXXUnfairLock and CXXDispatchSemaphore dependencies. They're replaced with smaller local implementations.
  • Replaced AudioFileWrapper and ExtAudioFileWrapper with unique_ptr/unique_ref and custom deleters.
  • Release builds now define NDEBUG, and internal checks use assert().
  • Input/output tests moved to Swift Testing.
  • Plus cleanups to include ordering, const correctness and naming.

Full Changelog: 0.14.0...0.15.0