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:
Jis G Jacob 2026-04-16 16:02:10 +05:30 • committed by delli
commit 5bd84270aa
12 changed files with 1946 additions and 83 deletions

View file

@ -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()