iOS: stem playlist refresh and covers, download queue page, reorderable Library, whole-library radio
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c8dfb54ca
commit
a4c870251c
18 files changed
+514
-30
No files matched your search
@@ -141,6 +141,18 @@ final class PlexClient: @unchecked Sendable {
|
||||
return (out, total)
|
||||
}
|
||||
|
||||
/// Every track in the library, in pages, shuffled.
|
||||
func allTracksShuffled() async throws -> [Track] {
|
||||
let size = 2000
|
||||
var (out, total) = try await tracksPage(start: 0, size: size)
|
||||
while out.count < total {
|
||||
let (page, _) = try await tracksPage(start: out.count, size: size)
|
||||
if page.isEmpty { break }
|
||||
out += page
|
||||
}
|
||||
return out.shuffled()
|
||||
}
|
||||
|
||||
func randomTracks(_ count: Int) async throws -> [Track] {
|
||||
let each = count / max(sources.count, 1) + 1
|
||||
return Array(try await pool { s in try await s.randomTracks(each).map { $0.tagged(s.server.id) } }.shuffled().prefix(count))
|
||||
@@ -269,5 +281,55 @@ final class PlexClient: @unchecked Sendable {
|
||||
/// A Plex client for plex.tv calls and for a server that isn't saved yet.
|
||||
func plexTv(_ url: String = "") -> PlexServer { PlexServer(http: http, settings: settings, server: MediaServer(url: url)) }
|
||||
|
||||
/// Signs in with a plex.tv token: finds every server on the account, saves each one that
|
||||
/// answers (matched by machine id, so it never duplicates), and switches on all its music
|
||||
/// libraries. Libraries already picked on a known server stay as they were.
|
||||
/// Returns (servers saved, servers on the account that didn't answer).
|
||||
@discardableResult
|
||||
func syncAccount(token: String) async throws -> (saved: Int, unreachable: Int) {
|
||||
let tv = plexTv()
|
||||
let user = await {
|
||||
guard let u = try? await tv.user(token: token) else { return "Plex user" }
|
||||
return u.title ?? u.username ?? "Plex user"
|
||||
}()
|
||||
let found = try await tv.resources(token: token)
|
||||
struct Hit { var res: PlexResource; var url: String; var secs: [MusicSection] }
|
||||
let hits: [Hit] = await withTaskGroup(of: Hit?.self) { group in
|
||||
for r in found {
|
||||
group.addTask { [self] in
|
||||
guard let url = await tv.reach(r, userToken: token) else { return nil }
|
||||
let tok = r.accessToken.isEmpty ? token : r.accessToken
|
||||
let probe = MediaServer(name: r.name, url: url, token: tok, machineId: r.clientIdentifier)
|
||||
let secs = (try? await make(probe).musicSections()) ?? []
|
||||
return Hit(res: r, url: url, secs: secs)
|
||||
}
|
||||
}
|
||||
var out: [Hit] = []
|
||||
for await h in group { if let h { out.append(h) } }
|
||||
return out
|
||||
}
|
||||
let order = Dictionary(uniqueKeysWithValues: found.enumerated().map { ($1.clientIdentifier, $0) })
|
||||
for h in hits.sorted(by: { (order[$0.res.clientIdentifier] ?? 0) < (order[$1.res.clientIdentifier] ?? 0) }) {
|
||||
let known = settings.servers.first { $0.kind == .plex && $0.machineId == h.res.clientIdentifier && !$0.machineId.isEmpty }
|
||||
?? settings.servers.first { $0.kind == .plex && $0.machineId.isEmpty && $0.base == h.url.trimmingCharacters(in: CharacterSet(charactersIn: "/")) }
|
||||
var srv = known ?? MediaServer(id: settings.nextServerId(), kind: .plex)
|
||||
srv.url = h.url
|
||||
srv.token = h.res.accessToken.isEmpty ? token : h.res.accessToken
|
||||
srv.machineId = h.res.clientIdentifier
|
||||
srv.user = user
|
||||
if srv.name.isEmpty || known == nil { srv.name = h.res.name }
|
||||
if known != nil {
|
||||
if !h.secs.isEmpty { srv.sections = srv.sections.filter { k in h.secs.contains { $0.key == k } } }
|
||||
} else {
|
||||
srv.sections = h.secs.map(\.key)
|
||||
}
|
||||
if !h.secs.isEmpty {
|
||||
srv.sectionTitles = srv.sections.compactMap { k in h.secs.first { $0.key == k }?.title }.joined(separator: " + ")
|
||||
}
|
||||
settings.save(srv)
|
||||
}
|
||||
return (hits.count, found.count - hits.count)
|
||||
}
|
||||
|
||||
func jellyfin(_ s: MediaServer) -> JellyfinClient { JellyfinClient(http: http, settings: settings, server: s) }
|
||||
}
|
||||
Reference in new issue
Block a user