- Playlists tab splits into Plex and Stem. Plex playlists are unchanged and now show their custom poster when one is set. - Stem playlists live on the device; each song keeps its own mix. Add from any song or collection menu, or copy a whole Plex playlist. - Queue items can carry a mix, which wins over the song's saved mix. - Softness slider under the Vocals picker in lyrics when set to Soft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
3.1 KiB
Swift
71 lines
3.1 KiB
Swift
import Foundation
|
|
|
|
/// Hand-wired singletons, shared by the phone UI, CarPlay and Siri.
|
|
@MainActor
|
|
final class AppGraph {
|
|
static let shared = AppGraph()
|
|
|
|
let settings: Settings
|
|
let net: Connectivity
|
|
/// Library calls: kept on disk and served from there when the server is unreachable.
|
|
let apiHttp: Http
|
|
/// Same disk cache, but away from home it reads only from disk instead of waiting on LAN timeouts.
|
|
let carHttp: Http
|
|
/// Media ranges; MediaCache is the cache. Long timeout covers server-side Opus transcodes.
|
|
let mediaSession: URLSession
|
|
let plex: PlexClient
|
|
let reach: HomeReach
|
|
let carPlex: PlexClient
|
|
let stems: StemServer
|
|
let cache: MediaCache
|
|
let eq: EqController
|
|
let playback: PlaybackManager
|
|
let stemPlaylists: StemPlaylists
|
|
let downloads: Downloads
|
|
let coverage: StemCoverage
|
|
let nowPlaying: NowPlaying
|
|
let car: CarLibrary
|
|
|
|
private init() {
|
|
let fm = FileManager.default
|
|
let support = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
let caches = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
try? fm.createDirectory(at: support, withIntermediateDirectories: true)
|
|
|
|
settings = Settings()
|
|
net = Connectivity()
|
|
apiHttp = Http(connectTimeout: 6, readTimeout: 30, offlineCache: caches.appendingPathComponent("http"))
|
|
carHttp = Http(connectTimeout: 6, readTimeout: 30, offlineCache: caches.appendingPathComponent("http"))
|
|
let m = URLSessionConfiguration.default
|
|
m.timeoutIntervalForRequest = 90
|
|
m.timeoutIntervalForResource = 24 * 3600
|
|
m.urlCache = nil
|
|
m.requestCachePolicy = .reloadIgnoringLocalCacheData
|
|
mediaSession = URLSession(configuration: m)
|
|
|
|
plex = PlexClient(http: apiHttp, settings: settings)
|
|
reach = HomeReach(settings: settings, net: net)
|
|
let reach = reach
|
|
carHttp.forceCache = { !reach.lastKnown }
|
|
carPlex = PlexClient(http: carHttp, settings: settings)
|
|
stems = StemServer(http: apiHttp, settings: settings, dir: support)
|
|
|
|
// Media lives outside Caches' purge-anytime rules and out of iCloud backups.
|
|
var media = support.appendingPathComponent("media", isDirectory: true)
|
|
try? fm.createDirectory(at: media, withIntermediateDirectories: true)
|
|
var rv = URLResourceValues()
|
|
rv.isExcludedFromBackup = true
|
|
try? media.setResourceValues(rv)
|
|
let settings = settings
|
|
cache = MediaCache(dir: media, session: mediaSession) { Int64(settings.cacheLimitGb) << 30 }
|
|
|
|
eq = EqController(dir: support)
|
|
stemPlaylists = StemPlaylists(dir: support)
|
|
playback = PlaybackManager(settings: settings, plex: plex, stems: stems, cache: cache, eq: eq, dir: support)
|
|
downloads = Downloads(settings: settings, plex: plex, stems: stems, cache: cache, net: net, dir: support)
|
|
coverage = StemCoverage(plex: plex, stems: stems, settings: settings, dir: support)
|
|
nowPlaying = NowPlaying(pm: playback, plex: plex)
|
|
car = CarLibrary(plex: carPlex, reach: reach, downloads: downloads, stems: stems, pm: playback)
|
|
}
|
|
}
|