Stemify iOS 1.1 and WebUI updates, repo restructure into desktop/ios/server/web
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
894dd5a695
commit
df5d847169
157 files changed
+3935
-742
No files matched your search
@@ -0,0 +1,116 @@
|
||||
import Foundation
|
||||
|
||||
/// One music server the app reads from. The first one ever added has an empty [id], so its
|
||||
/// keys stay bare and everything saved before multi-server support (downloads, mixes, stem
|
||||
/// playlists) still matches. Every other server's keys carry "<id>~" in front.
|
||||
struct MediaServer: Codable, Hashable, Identifiable {
|
||||
enum Kind: String, Codable { case plex, jellyfin }
|
||||
|
||||
var id = ""
|
||||
var kind = Kind.plex
|
||||
var name = ""
|
||||
var url = ""
|
||||
var token = ""
|
||||
var user = ""
|
||||
/// Jellyfin user id; Plex leaves it empty.
|
||||
var userId = ""
|
||||
var machineId = ""
|
||||
var sections: [String] = []
|
||||
var sectionTitles = ""
|
||||
|
||||
var signedIn: Bool { !token.isEmpty }
|
||||
var ready: Bool { signedIn && !sections.isEmpty }
|
||||
var base: String { url.trimmingCharacters(in: CharacterSet(charactersIn: "/")) }
|
||||
var label: String { name.isEmpty ? (kind == .plex ? "Plex" : "Jellyfin") : name }
|
||||
var kindLabel: String { kind == .plex ? "Plex" : "Jellyfin" }
|
||||
|
||||
static func newId() -> String { "s" + String(UUID().uuidString.prefix(6)).lowercased() }
|
||||
}
|
||||
|
||||
/// What a single server can do. [PlexClient] pools these and tags their keys.
|
||||
protocol MediaSource: AnyObject, Sendable {
|
||||
var server: MediaServer { get }
|
||||
func artists(sort: String) async throws -> [Artist]
|
||||
func albums(sort: String) async throws -> [Album]
|
||||
func recentlyAdded(count: Int) async throws -> [Album]
|
||||
func recentlyPlayed(count: Int) async throws -> [Album]
|
||||
func recentlyAddedTracks(count: Int) async throws -> [Track]
|
||||
func randomAlbums(_ count: Int) async throws -> [Album]
|
||||
func tracksPage(start: Int, size: Int, sort: String) async throws -> ([Track], Int)
|
||||
func randomTracks(_ count: Int) async throws -> [Track]
|
||||
func album(_ key: String) async throws -> Album?
|
||||
func albumTracks(_ key: String) async throws -> [Track]
|
||||
func artistAlbums(_ key: String) async throws -> [Album]
|
||||
func artistTracks(_ key: String) async throws -> [Track]
|
||||
func searchTracks(_ title: String) async throws -> [Track]
|
||||
func loudness(_ trackKey: String, samples: Int) async throws -> [Float]?
|
||||
func search(_ query: String) async throws -> SearchResults
|
||||
func playlists(sort: String) async throws -> [Playlist]
|
||||
func playlistTracks(_ key: String) async throws -> [Track]
|
||||
func createPlaylist(_ title: String, trackKeys: [String]) async throws -> Playlist?
|
||||
func addToPlaylist(_ key: String, trackKeys: [String]) async throws
|
||||
func removeFromPlaylist(_ key: String, playlistItemId: Int64) async throws
|
||||
func movePlaylistItem(_ key: String, playlistItemId: Int64, afterItemId: Int64?) async throws
|
||||
func renamePlaylist(_ key: String, title: String) async throws
|
||||
func deletePlaylist(_ key: String) async throws
|
||||
func scrobble(_ trackKey: String) async
|
||||
func timeline(_ track: Track, state: String, positionMs: Int64) async
|
||||
func artUrl(_ thumb: String?, px: Int) -> URL?
|
||||
func partUrl(_ partKey: String) -> URL?
|
||||
/// Music libraries on the server, for the picker in Settings.
|
||||
func musicSections() async throws -> [MusicSection]
|
||||
}
|
||||
|
||||
// ---- key tagging -----------------------------------------------------------------
|
||||
|
||||
extension String {
|
||||
func tagged(_ id: String) -> String { id.isEmpty ? self : id + "~" + self }
|
||||
}
|
||||
|
||||
extension Optional where Wrapped == String {
|
||||
func tagged(_ id: String) -> String? { map { $0.tagged(id) } }
|
||||
}
|
||||
|
||||
extension Track {
|
||||
func tagged(_ id: String) -> Track {
|
||||
guard !id.isEmpty else { return self }
|
||||
var t = self
|
||||
t.ratingKey = ratingKey.tagged(id); t.albumKey = albumKey.tagged(id); t.artistKey = artistKey.tagged(id)
|
||||
t.thumb = thumb.tagged(id); t.partKey = partKey.tagged(id)
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
extension Album {
|
||||
func tagged(_ id: String) -> Album {
|
||||
guard !id.isEmpty else { return self }
|
||||
var a = self
|
||||
a.ratingKey = ratingKey.tagged(id); a.artistKey = artistKey.tagged(id); a.thumb = thumb.tagged(id)
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
extension Artist {
|
||||
func tagged(_ id: String) -> Artist {
|
||||
guard !id.isEmpty else { return self }
|
||||
var a = self
|
||||
a.ratingKey = ratingKey.tagged(id); a.thumb = thumb.tagged(id)
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
extension Playlist {
|
||||
func tagged(_ id: String) -> Playlist {
|
||||
guard !id.isEmpty else { return self }
|
||||
var p = self
|
||||
p.ratingKey = ratingKey.tagged(id); p.thumb = thumb.tagged(id)
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchResults {
|
||||
func tagged(_ id: String) -> SearchResults {
|
||||
SearchResults(artists: artists.map { $0.tagged(id) }, albums: albums.map { $0.tagged(id) },
|
||||
tracks: tracks.map { $0.tagged(id) }, playlists: playlists.map { $0.tagged(id) })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user