You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Lazy — fetches once, caches for all subsequent calls
letcaps=tryawait client.loadCapabilities()print("Server: \(caps.serverType ??"unknown")\(caps.serverVersion ??"")")print("OpenSubsonic: \(caps.isOpenSubsonic)")
// String overload
if caps.supports("songLyrics"){…}
// Typed KnownExtension overload (compile-time safe)
if caps.supports(.songLyrics){…}if caps.supports(.apiKeyAuthentication){…}
// Force a fresh fetch (e.g. after re-auth)
letrefreshed=tryawait client.refreshCapabilities()
Browsing
// All artists, grouped by index letter
letindexes=tryawait client.getArtists()forindexin indexes {forartistin index.artist {print(artist.name)}}
// Music folders
letfolders=tryawait client.getMusicFolders()
SwiftSonicError also exposes convenience helpers for common checks:
Helper
Returns true when…
isTransient
error is safe to retry (timeouts, 5xx, rate limits)
isAuthenticationFailure
auth failed (wrong credentials, 401, 403)
isDNSFailure
DNS resolution failed (.cannotFindHost, .dnsLookupFailed)
isCertificateError
TLS certificate validation failed (server or client cert)
suggestedRetryDelay
(non-nil)Retry-After seconds from a rate-limit response
Retry and resilience
SwiftSonicClient automatically retries transient failures (network errors, HTTP 5xx, HTTP 429) with exponential back-off. The default policy makes up to 3 attempts:
Non-transient errors (authentication failures, 4xx, decoding errors) are never retried. A 429 response honours the Retry-After header when present.
Observability
Logging
Pass logSubsystem: to enable os.Logger output under the SwiftSonicClient category. The client logs every attempt, retry, success, and failure — visible in Console.app and Instruments.
letclient=SwiftSonicClient(
configuration: config,
logSubsystem:"com.example.MyApp" // silent by default
)
Metrics hook
Implement SwiftSonicMetricsCollector to integrate with your observability backend (Datadog, Sentry, custom analytics):
// Legacy plain-text lyrics
if let lyrics =tryawait client.getLyrics(artist:"Nine Inch Nails", title:"Hurt"){print(lyrics.value ??"")}
// OpenSubsonic structured lyrics (synced + multi-language)
let list =tryawait client.getLyricsBySongId(id: song.id)
for set in list.structuredLyrics{print("\(set.lang) synced=\(set.synced)")forlinein set.line {iflet ms = line.start {print("[\(ms)ms] \(line.value)")}else{print(line.value)}}}
Thread-safe by construction — SwiftSonicClient is a Swift actor
Zero dependencies — only Foundation and CryptoKit
Sendable everywhere — all public types conform to Sendable, zero warnings in strict concurrency
Injectable transport — swap out URLSession for testing, proxying, or cert pinning
No UI coupling — Data and URL only, never UIImage or SwiftUI.Image
Resilient by default — 3-attempt exponential back-off retry, configurable via RetryPolicy
Observable — logSubsystem: for os.Logger output; metricsCollector: for custom metrics
Documentation
Design decisions, the internal security audit, and cross-cutting technical notes live
in the CassetteLab knowledge vault — an
Obsidian vault versioned with Git, shared across the whole ecosystem.
Anything that explains why a choice was made lives there rather than in this repo.
README.md, CONTRIBUTING.md, SECURITY.md, CHANGELOG.md, and LICENSE stay here.
The vault is private to the organisation — open an issue if you need access.