iOS 1.0 RC 2: Plex streams under upload caps, shuffle and album radio stations, Recently Added list, photo covers, crossfade
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c0e33b5e3e
commit
2fb8040388
17 files changed
+412
-74
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import UIKit
|
||||
|
||||
/// One song in a stem playlist, saved with the mix it plays with.
|
||||
struct StemPlaylistItem: Codable, Hashable, Identifiable {
|
||||
@@ -20,7 +21,12 @@ struct StemPlaylist: Codable, Hashable, Identifiable {
|
||||
|
||||
var tracks: [Track] { items.map(\.track) }
|
||||
var mixes: [[String: StemSetting]] { items.map(\.mix) }
|
||||
var thumb: String? { artThumb ?? items.lazy.compactMap(\.track.thumb).first }
|
||||
/// A cover picked from Photos ("local:<file>") falls back to the songs' art if its file is gone
|
||||
/// (an imported list, say).
|
||||
var thumb: String? {
|
||||
if let a = artThumb, !a.hasPrefix(StemPlaylists.localPrefix) || StemPlaylists.localArt(a) != nil { return a }
|
||||
return items.lazy.compactMap(\.track.thumb).first
|
||||
}
|
||||
var durationMs: Int64 { items.reduce(0) { $0 + $1.track.durationMs } }
|
||||
}
|
||||
|
||||
@@ -53,7 +59,52 @@ final class StemPlaylists {
|
||||
edit(id) { $0.items += tracks.map { StemPlaylistItem(track: $0, mix: MixPresets.normalized(mix)) } }
|
||||
}
|
||||
|
||||
func setArt(_ id: String, _ thumb: String?) { edit(id) { $0.artThumb = thumb } }
|
||||
func setArt(_ id: String, _ thumb: String?) {
|
||||
let old = list(id)?.artThumb
|
||||
edit(id) { $0.artThumb = thumb }
|
||||
if old != thumb { Self.removeLocalArt(old) }
|
||||
}
|
||||
|
||||
// ---- covers from Photos -----------------------------------------------------------------
|
||||
|
||||
nonisolated static let localPrefix = "local:"
|
||||
nonisolated static var artDir: URL {
|
||||
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("playlist-art", isDirectory: true)
|
||||
}
|
||||
|
||||
/// The file behind a "local:" thumb, if it's still there.
|
||||
nonisolated static func localArt(_ thumb: String) -> URL? {
|
||||
guard thumb.hasPrefix(localPrefix) else { return nil }
|
||||
let name = String(thumb.dropFirst(localPrefix.count))
|
||||
guard !name.isEmpty, !name.contains("/") else { return nil }
|
||||
let u = artDir.appendingPathComponent(name)
|
||||
return FileManager.default.fileExists(atPath: u.path) ? u : nil
|
||||
}
|
||||
|
||||
nonisolated private static func removeLocalArt(_ thumb: String?) {
|
||||
if let thumb, let u = localArt(thumb) { try? FileManager.default.removeItem(at: u) }
|
||||
}
|
||||
|
||||
/// Saves [data] (any image format Photos hands over) as the list's cover, scaled to 1200 px.
|
||||
/// Each pick gets a new file name so cached art doesn't show the old one.
|
||||
@discardableResult
|
||||
func setPhotoArt(_ id: String, _ data: Data) -> Bool {
|
||||
guard let img = UIImage(data: data) else { return false }
|
||||
let side: CGFloat = 1200
|
||||
let scale = min(1, side / max(img.size.width, img.size.height, 1))
|
||||
let size = CGSize(width: (img.size.width * scale).rounded(), height: (img.size.height * scale).rounded())
|
||||
let fmt = UIGraphicsImageRendererFormat.default(); fmt.scale = 1
|
||||
let jpeg = UIGraphicsImageRenderer(size: size, format: fmt).jpegData(withCompressionQuality: 0.85) { _ in
|
||||
img.draw(in: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
let name = "\(id)-\(UUID().uuidString.prefix(8).lowercased()).jpg"
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: Self.artDir, withIntermediateDirectories: true)
|
||||
try jpeg.write(to: Self.artDir.appendingPathComponent(name), options: .atomic)
|
||||
} catch { return false }
|
||||
setArt(id, Self.localPrefix + name)
|
||||
return true
|
||||
}
|
||||
|
||||
/// Re-reads every song from the library so renamed, retagged or re-added songs match again.
|
||||
/// Keeps each item's uid and mix, and leaves songs the library can't find as they are.
|
||||
@@ -100,6 +151,7 @@ final class StemPlaylists {
|
||||
func rename(_ id: String, _ title: String) { edit(id) { $0.title = title } }
|
||||
|
||||
func delete(_ id: String) {
|
||||
Self.removeLocalArt(list(id)?.artThumb)
|
||||
lists.removeAll { $0.id == id }
|
||||
save()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user