Repository navigation
Releases: timusus/AudioPlaybackKit
Releases · timusus/AudioPlaybackKit
Release list
0.20.2
0.20.1
0.20.0
AudioPlaybackKit 0.20.0 reads position on demand instead of pushing it every 100 ms, so a playing transport
wakes only for work and for the observers an app registers (#189,
ADR-0031).
This is a minor bump at 0.x: it changes public API (below). Decode and the PCM played are unchanged.
What changed
- Position is computed when read, as media3
Player.getCurrentPositionand AVPlayercurrentTime().
Transport.position(aTransportPosition:item,seconds) maps the output clock through a snapshot
under the transport's lock, on the caller's thread with no hop to its queue. It is nil while a
setCurrentorseekis called but not taken, and within one item and one call it never goes back.
Player.positionreads it once every call is answered and the heard item is the current one; until then
it holds the value set with the lastdiscontinuityCountchange. - Observers, named as AVPlayer's.
addPeriodicObserver(interval:using:)fires everyintervalof
output time while the clock runs (wall time at any speed).addBoundaryObserver(times:using:)fires once
when playback crosses a time in the heard item; a load or seek landing past it does not. Both are on
Transport(callbacks on its queue) andPlayer(callbacks on the main actor), and return a
TimeObserverTokenthat removes the observer oncancel()or deinit. - Events.
TransportEvent.landed(item:seconds:)is the first position of asetCurrentorseek,
once its first audio is written;seekLandedandseekAdjustedcome from it.
TransportEvent.durationChanged(item:duration:isSeekable:)is sent when the heard item's duration or
seekability changes.firstAudioRenderedis sent on.status(.playing)after a landing, or on a landing
or.crossedwhile playing. NowPlayingSessionstill writes elapsed time only on a jump, a play or pause, a rate or item change,
or drift. After a write while playing it asks the player for one periodic tick, so a play heard late is
rewritten once heard; a paused or held player wakes nothing.- Test support.
ManualPumpTimer.advancemoves the clock in 1/64 s steps, so an output's ready wake
lands as it would on a real clock.
Breaking changes
TransportEvent.position(item:seconds:duration:isSeekable:)is removed, replaced by.landedand
.durationChanged: code matching it, or an exhaustiveswitch, no longer compiles.Player.positionis computed, so SwiftUI andwithObservationTrackingno longer see it move between
discontinuities. A view that showed it by observation stops updating.NowPlayingControllablerequiresaddPeriodicObserver(interval:using:). A
ForwardingNowPlayingControllablewrapper gets it forwarded; a direct conformer must forward it to its player.
Additive: Transport.position, TransportPosition, TimeObserverToken, and both observers on Transport
and Player.
Upgrading
- A progress bar or elapsed label subscribes with
player.addPeriodicObserver(interval:using:), keeps the
token, and readsplayer.positionon each fire. - Ad and silence skips and a sleep timer at an item time use
addBoundaryObserver(times:using:)in place of
comparing each.position. - Code that read duration or seekability from
.positionreads.durationChanged, orPlayer.duration
andisSeekable.
0.19.0
Added
Player.startGateEnded: reports why a start gate ended (.intentWithdrawnor.superseded) and for which item, synchronously at the moment it ends.StartGateEndhas a public initialiser for tests.
Fixed
- A gate cancelled before its task first ran no longer calls
startGate. - A failure of the gated item, or a source that fails to load, now ends the gate and reports it.
0.18.1
0.18.0
0.17.0
Changed
- Playback speed is now a pitch-preserving time-stretch (Apple's AUNewTimePitch) applied before enqueue, and the renderer stays at rate 1. A speed change goes through the gapless processor re-feed path, so it no longer briefly mutes on the iPhone speaker. A new speed is heard about half a second after the call (#188).
Breaking
timePitchAlgorithmis removed fromSampleBufferAudioOutput.init, theTransportinit and thePlayerconvenience init. Drop the argument; see docs/migrating.md.
0.16.1
Fixed
- Live EQ and processor changes no longer cut out on shallow output queues, such as the iPhone speaker. If the renderer refuses a partial flush, the new chain is appended at the end of the queued audio instead of restarting playback (#187).
0.16.0
Fixed
- Choppy AirPlay playback (timusus/Shuttle2#1064). The feeder kept a fixed lead of about two seconds of audio. AirPlay receivers buffer more than the latency they report, so the renderer ran dry and audio was dropped silently, with no stall reported. The feeder now writes while the renderer is ready for more media data, woken by
requestMediaDataWhenReady, and keeps at most 30 s of media written ahead. That cap is a time bound: about 11 MB at 48 kHz stereo Float32, and more at higher sample rates or channel counts.
Changed
- Start and rebuffer depth. Playback starts once 0.5 s (scaled by rate) is queued. After an underrun it waits for 2 s, or starts as soon as the output reports full.
- Effect changes flush. With up to 30 s queued ahead, draining the old chain would delay a change too long.
setProcessorsnow flushes and re-feeds from the frame being heard, which shows as a briefbuffering, as a seek does. This doesn't happen after.ended. - New
processorsChanged()onTransportandPlayer(public API). Call it after changing a live processor parameter, such as an EQ gain or a Voice Boost amount, so the change is heard at once. It does nothing while the transport is failed or ended. - Playback rate is still applied by the synchronizer, so speed changes take effect at once with no flush.
Migrating from 0.15.0
- If your processors read parameters that change while playing, call
player.processorsChanged()after each change. Without it, the change is heard only after the audio already queued (up to 30 s) has played. - Code that relied on
setProcessorsnever flushing will now see a shortbufferingstate.
0.15.0
Render
- Underruns and resumes are now judged against the renderer clock, not the heard playhead (#183). On a high-latency route such as AirPlay (about 2 s), audio written after an upstream stall used to land behind the running clock and be dropped silently, with status still
.playing. Now the output pauses and reports.buffering. Likely cause of timusus/Shuttle2#1064. - Through an underrun the reported position holds at the last heard frame, instead of jumping ahead by the route latency.
- After an underrun, or after the renderer itself runs dry, playback resumes only once the full feed depth (2 s at 1x) is written, as media3's
bufferForPlaybackAfterRebufferMsdoes. This gives one longer pause instead of repeated short stalls. The first start, a seek, a route change and play after a pause still start at the 0.5 s resume depth. - A next item that resolves just after the end of a stream is no longer written behind the clock, whether playing or paused.
- Pausing in the last seconds of a stream on a high-latency route and pressing play now finishes the item instead of staying in
.buffering.
Tooling
- The release caches the FFmpeg source per tag and retries its fetch, alternating git.ffmpeg.org and the GitHub mirror (#186). The source tarball now also contains
scripts/lib/fetch-ffmpeg.sh, whichbuild-ffmpeg.shsources.
No public API change. Decoded PCM is unchanged; what moved is buffering timing (.buffering now appears on AirPlay stalls, and resumes after a stall are later).