All notable changes to PlayolaPlayer are documented here. This project follows Semantic Versioning. Versions correspond to git tags, which Swift Package Manager consumers pin to.
- Bounded retry/backoff on the initial schedule fetch.
play(stationId:)now retries a failed initialGET /v1/stations/{id}/scheduleup to 3 times with exponential backoff (0.5s / 1s / 2s) before giving up, matching the recovery behavior the player already used for spin loading and the ongoing schedule poll. Only transient failures are retried — server5xxresponses and an explicit allow-list of connectivityURLErrors (timeouts, host/DNS, connection-lost, etc.). Permanent failures (404, decode errors, empty schedules, and non-connectivityURLErrors such as.badURL) still fail fast. This means a transient backend outage no longer instantly fails a station start with no automatic recovery.
-
PlayolaStationPlayer.Stategained a terminal.error(StationPlayerError)case. A failedplay(...)now emits.loading(0)when the attempt begins and.error(_)when it fails terminally (instead of throwing without ever changingstate, which could leave consumers stuck on a loading spinner with no signal).play(...)stillthrowsas before — the new state is emitted in addition to the thrown error. Cancellation does not produce an.errorstate.Source-breaking for consumers that
switchexhaustively overState: add acase .error(ordefault) arm. A typical handler treats.erroras a recoverable, retry-able state (show the message, let the user tap play again).StationPlayerErroris nowSendable. -
State transitions are now supersession-safe (last
play()wins). Eachplay(...)/stop()takes a new internal generation; work from a prior attempt (its scheduling loop, or a spin whose audio starts later) can no longer publish state into a newer attempt. This closes a race where a lingering session could overwrite a freshly-emitted.error(or.idle) with.playing. Behavioral note: starting a newplay()supersedes the previous session immediately, so a failed station switch lands on.errorrather than rolling back to the previously-playing station. -
playNow(from:to:)andschedulePlay(at:)are nowasync. Bothpublicmethods changed from synchronous toasyncso their audio work can run off the main thread. Source-breaking for direct callers: addawaitat the call site (callers must already be in anasynccontext, e.g. aTask).
-
Network logging seam for observing the library's JSON API traffic (binary audio downloads are intentionally excluded). New public API in
PlayolaCore:PlayolaNetworkLogEvent— aSendablevalue describing a single request/response (method, url, request headers/body, status code, response body, duration, and any error description). Events are raw and unredacted; the consuming app is responsible for redacting sensitive values such asAuthorizationheaders before storing them.PlayolaNetworkLogger.handler— a thread-safe,Sendablehook the consuming app sets at startup to receive events.nil(the default) disables logging, leaving behavior and performance unchanged.PlayolaNetworkLoggingSession— a transparentURLSessionProtocolwrapper that times each call and emits an event on both success and thrown error without altering return values or rethrown errors.
The schedule fetch (
PlayolaStationPlayer) and the listening-session reporter (ListeningSessionReporter) now route their JSON API calls through this wrapper by default. No breaking API changes: existing initializers and call sites compile unchanged.