Skip to content

2.0.0

Latest

Choose a tag to compare

@dioKaratzas dioKaratzas released this 31 Aug 10:46
· 1 commit to master since this release

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. AudioPlayer is @MainActor @Observable. SwiftUI views read state,
    currentItem, metadata, progress, quality, network and upNext directly, with no mirroring
    layer in between.
  • Typed event stream. events vends an AsyncStream<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, Sendable reducer 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.
  • PlaybackState carrying its item. .playing without an item is unrepresentable, and .loading
    is distinct from .buffering, so a spinner can tell "has not started" from "started then stalled".
  • AudioPlayerConfiguration. One Sendable struct with .default, .podcast and .liveRadio
    presets replaces eighteen loose properties. AudioSessionPolicy.unmanaged leaves AVAudioSession
    alone for apps that own it.
  • RepeatMode and isShuffled as two orthogonal properties, replacing the AudioPlayerMode
    option set in which .repeat and .repeatAll could both be on at once.
  • Typed throws on the public surface, and AudioPlayerError as a Sendable, Equatable
    LocalizedError.
  • Duration throughout, with ClosedRange<Duration> for buffered and seekable ranges in place of
    the previous non-Sendable tuple 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 #else branches 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

  • AudioItem is a Sendable struct, identified by UUID and carrying AudioSources and
    AudioMetadata. It is no longer an ObservableObject, no longer open to subclassing, and no longer
    mutated by stream metadata — discovered metadata lands on player.metadata instead.
  • AudioPlayerDelegate is replaced by observable state and the event stream. shouldStartPlaying
    becomes skippedItems: 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 deprecated SCNetworkReachability layer is gone, and
    NetworkStatus now reports the interface, whether it is expensive, and whether it is constrained.
  • The audio session no longer sets .mixWithOthers. The default is .playback with 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 with play() rather than by writing
    rate directly.
  • Swift 6 language mode, strict concurrency complete, with NonisolatedNonsendingByDefault and
    ExistentialAny enabled. 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.addTarget are stored and removed individually, rather than being discarded and
    cleared with a blanket removeTarget(nil) that also removed other frameworks' handlers.
  • Per-item observations no longer accumulate across item changes, AVPlayerItemMetadataOutput is
    removed as well as added, and end-of-item notifications are filtered by their object so they stop
    firing for every AVPlayerItem in 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 through AVPlayerItem.error,
    which populates only once the item has already failed.
  • Seeking updates the published progress instead of leaving it behind.
  • AVPlayerItem is constructed in one place, so a quality change no longer discards per-item setup.

Removed

  • AudioPlayerDelegate, AudioPlayerMode, AudioItemQueue, the six event producers, the
    NowPlayable layer, the hand-rolled reachability, the lock wrapper, and the background handler.
  • The TimeRange tuple type alias, and the stateBeforeBuffering / stateWhenConnectionLost /
    pausedForInterruption shadow state they were reconciled against.

Requirements

Swift 6.3, iOS 18, macOS 15, tvOS 18, watchOS 11, visionOS 2, Mac Catalyst 18.