mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 23:54:32 +00:00
Atoll: fullscreen album artwork as lockscreen wallpaper
Right-click on the album art in the lock screen music panel to set it as the desktop wallpaper. Right-click again or click anywhere on the background to restore the original wallpaper. Wallpaper also restores automatically on track change or screen unlock. Uses direct plist manipulation of the wallpaper config to avoid leaving traces in the wallpaper picker. Album art PNG is pre-cached in the background on track change for instant application. Adds video artwork URL fetching from iTunes catalog for Apple Music tracks (musicVideo entity search). Signed-off-by: Jis G Jacob <studiokeys@blissroms.org>
This commit is contained in:
parent
78bf018fe2
commit
5bd84270aa
12 changed files with 1946 additions and 83 deletions
|
|
@ -478,7 +478,9 @@ class MusicManager: ObservableObject {
|
|||
private var explicitLookupTask: Task<Void, Never>?
|
||||
private var explicitLookupKey: String?
|
||||
|
||||
private var artworkData: Data? = nil
|
||||
private(set) var artworkData: Data? = nil
|
||||
|
||||
@Published var videoArtworkURL: URL? = nil
|
||||
|
||||
private var liveStreamUnknownDurationCount: Int = 0
|
||||
private var liveStreamEdgeObservationCount: Int = 0
|
||||
|
|
@ -709,6 +711,11 @@ class MusicManager: ObservableObject {
|
|||
|| bundleChanged
|
||||
|| contentIdentifierChanged
|
||||
|| contentURLChanged
|
||||
let liveArtworkChanged = state.liveArtworkURL != self.videoArtworkURL
|
||||
|
||||
if liveArtworkChanged {
|
||||
self.videoArtworkURL = state.liveArtworkURL
|
||||
}
|
||||
|
||||
// Handle artwork and visual transitions for changed content
|
||||
let shouldAutoPeekOnTrackChange = Defaults[.showSneakPeekOnTrackChange]
|
||||
|
|
@ -735,8 +742,13 @@ class MusicManager: ObservableObject {
|
|||
self.lastArtworkContentIdentifier = state.contentIdentifier
|
||||
self.lastArtworkContentURL = state.contentURL
|
||||
|
||||
// Fetch lyrics for new track whenever content changes
|
||||
self.fetchLyrics()
|
||||
if let liveArtworkURL = state.liveArtworkURL {
|
||||
self.videoArtworkURL = liveArtworkURL
|
||||
} else {
|
||||
self.fetchVideoArtwork()
|
||||
}
|
||||
|
||||
self.refreshExplicitFlag(for: state)
|
||||
|
||||
// Only update sneak peek if there's actual content and something changed
|
||||
|
|
@ -1424,6 +1436,76 @@ class MusicManager: ObservableObject {
|
|||
lyricSyncTask = nil
|
||||
}
|
||||
|
||||
// MARK: - Video Artwork
|
||||
|
||||
func fetchVideoArtwork() {
|
||||
guard Defaults[.lockScreenMusicFullscreenVideoArtwork] else {
|
||||
videoArtworkURL = nil
|
||||
return
|
||||
}
|
||||
guard bundleIdentifier == "com.apple.Music" else {
|
||||
return
|
||||
}
|
||||
|
||||
let title = songTitle
|
||||
let artist = artistName
|
||||
|
||||
Task {
|
||||
let url = await fetchAnimatedArtworkURL(title: title, artist: artist)
|
||||
await MainActor.run {
|
||||
self.videoArtworkURL = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchAnimatedArtworkURL(title: String, artist: String) async -> URL? {
|
||||
guard !title.isEmpty, !artist.isEmpty else { return nil }
|
||||
|
||||
let query = "\(title) \(artist)"
|
||||
guard let encoded = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
|
||||
let searchURL = URL(string: "https://itunes.apple.com/search?term=\(encoded)&media=music&entity=musicVideo&limit=5")
|
||||
else { return nil }
|
||||
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: searchURL)
|
||||
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let results = json["results"] as? [[String: Any]],
|
||||
let first = results.first,
|
||||
let previewURLString = first["previewUrl"] as? String,
|
||||
let previewURL = URL(string: previewURLString)
|
||||
else { return nil }
|
||||
|
||||
return previewURL
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func fetchHighResArtworkURL() async -> URL? {
|
||||
let title = songTitle
|
||||
let artist = artistName
|
||||
guard !title.isEmpty, !artist.isEmpty else { return nil }
|
||||
|
||||
let query = "\(title) \(artist)"
|
||||
guard let encoded = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
|
||||
let searchURL = URL(string: "https://itunes.apple.com/search?term=\(encoded)&media=music&entity=song&limit=5")
|
||||
else { return nil }
|
||||
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: searchURL)
|
||||
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let results = json["results"] as? [[String: Any]],
|
||||
let first = results.first,
|
||||
let artworkURLString = first["artworkUrl100"] as? String
|
||||
else { return nil }
|
||||
|
||||
let highRes = artworkURLString.replacingOccurrences(of: "100x100", with: "3000x3000")
|
||||
return URL(string: highRes)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func toggleLyrics() {
|
||||
// Toggle the UI state first so the views can react immediately.
|
||||
showLyrics.toggle()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue