A rewrite. The public API is new throughout, there are no deprecated shims, and nothing from 1.x
compiles against it unchanged.
Added
- Observable player.
AudioPlayeris@MainActor @Observable. SwiftUI views readstate,
currentItem,metadata,progress,quality,networkandupNextdirectly, with no mirroring
layer in between. - Typed event stream.
eventsvends anAsyncStream<AudioPlayerEvent>per caller, so several
observers can listen at once. It covers twelve events, including item completion, queue exhaustion,
interruption, quality change, network change and recoverable stream errors — none of which had a hook
before. - A synchronous state machine. Playback decisions are taken by a pure,
Sendablereducer that
returns effects as values rather than performing them, so retry counting, quality thresholds,
connection-loss deadlines, interruption policy and end-of-item advancement are tested without
AVFoundation, a simulator or a wall clock. PlaybackStatecarrying its item..playingwithout an item is unrepresentable, and.loading
is distinct from.buffering, so a spinner can tell "has not started" from "started then stalled".AudioPlayerConfiguration. OneSendablestruct with.default,.podcastand.liveRadio
presets replaces eighteen loose properties.AudioSessionPolicy.unmanagedleavesAVAudioSession
alone for apps that own it.RepeatModeandisShuffledas two orthogonal properties, replacing theAudioPlayerMode
option set in which.repeatand.repeatAllcould both be on at once.- Typed throws on the public surface, and
AudioPlayerErroras aSendable,Equatable
LocalizedError. Durationthroughout, withClosedRange<Duration>for buffered and seekable ranges in place of
the previous non-Sendabletuple type alias.- Six platforms, each built and tested in CI. watchOS, visionOS and Mac Catalyst are supported for
the first time; all three previously fell through#elsebranches written for macOS. - Configurable logging through
os.Logger, with a level the host app sets and off by default at
anything below the level it chooses. - DocC documentation covering the whole public surface, published on merge, plus an example app
exercising every capability on iOS and macOS.
Changed
AudioItemis aSendablestruct, identified byUUIDand carryingAudioSourcesand
AudioMetadata. It is no longer anObservableObject, no longer open to subclassing, and no longer
mutated by stream metadata — discovered metadata lands onplayer.metadatainstead.AudioPlayerDelegateis replaced by observable state and the event stream.shouldStartPlaying
becomesskippedItems: Set<AudioItem.ID>, so which items the queue passes over is data the player
holds rather than a callback it has to ask.- Reachability is
NWPathMonitor. The deprecatedSCNetworkReachabilitylayer is gone, and
NetworkStatusnow reports the interface, whether it is expensive, and whether it is constrained. - The audio session no longer sets
.mixWithOthers. The default is.playbackwith no options,
which is what lets the app become the Now Playing app cleanly. Opt back in through
AudioSessionPolicy. - Quality downgrades less often. The guard flag that was meant to suppress the buffering event
during a quality change never actually did, so stalls were over-counted. The threshold is
configurable. - Speed is set through
defaultRate, and playback starts withplay()rather than by writing
ratedirectly. - Swift 6 language mode, strict concurrency complete, with
NonisolatedNonsendingByDefaultand
ExistentialAnyenabled. Isolation is annotated explicitly rather than defaulted. - Tests are swift-testing, run against a fake engine and an injected scheduler, so timing-dependent
behaviour is asserted at its boundaries instead of by sleeping. - Licensed under Apache 2.0.
Fixed
- Remote command handlers no longer retain the player forever. Tokens returned by
MPRemoteCommand.addTargetare stored and removed individually, rather than being discarded and
cleared with a blanketremoveTarget(nil)that also removed other frameworks' handlers. - Per-item observations no longer accumulate across item changes,
AVPlayerItemMetadataOutputis
removed as well as added, and end-of-item notifications are filtered by their object so they stop
firing for everyAVPlayerItemin the process. - The background-task refcount is held inside an actor, fixing a read-modify-write that was not atomic
even with the previous lock wrapper. macOS now takes a real activity assertion instead of doing
nothing. - Stream trouble that does not stop playback is reported. New error-log entries are observed and
surfaced as.recoverableErrorLogged, rather than being visible only throughAVPlayerItem.error,
which populates only once the item has already failed. - Seeking updates the published progress instead of leaving it behind.
AVPlayerItemis constructed in one place, so a quality change no longer discards per-item setup.
Removed
AudioPlayerDelegate,AudioPlayerMode,AudioItemQueue, the six event producers, the
NowPlayablelayer, the hand-rolled reachability, the lock wrapper, and the background handler.- The
TimeRangetuple type alias, and thestateBeforeBuffering/stateWhenConnectionLost/
pausedForInterruptionshadow state they were reconciled against.
Requirements
Swift 6.3, iOS 18, macOS 15, tvOS 18, watchOS 11, visionOS 2, Mac Catalyst 18.