Skip to content

Commit 2f61556

Browse files
authored
Prevent potential hang risk while viewing a playlist (#282)
Fixes a bug where viewing a playlist causes app to hang in case artwork collage is stalled during render. This is also a follow-up to #268 for a fix towards Intel macs.
1 parent 32c81b2 commit 2f61556

3 files changed

Lines changed: 44 additions & 21 deletions

File tree

Models/Core/Playlist.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -280,20 +280,28 @@ struct Playlist: Identifiable, FetchableRecord, PersistableRecord {
280280
if let customCover = coverArtworkData {
281281
return customCover
282282
}
283-
284283
let selected = collageTracks()
285284
let selectedIDs = selected.map { $0.id }
285+
return PlaylistArtworkCache.shared.getCachedArtwork(for: id, currentTrackIDs: selectedIDs)
286+
}
286287

288+
func warmArtworkCacheIfNeeded() async -> Data? {
289+
if let customCover = coverArtworkData {
290+
return customCover
291+
}
292+
let selected = collageTracks()
293+
let selectedIDs = selected.map { $0.id }
287294
if let cached = PlaylistArtworkCache.shared.getCachedArtwork(for: id, currentTrackIDs: selectedIDs) {
288295
return cached
289296
}
290-
291-
if let collage = generateCollageArtwork(from: selected) {
292-
PlaylistArtworkCache.shared.setCachedArtwork(collage, for: id, trackIDs: selectedIDs)
293-
return collage
297+
let playlistID = id
298+
let collage = await Task.detached(priority: .utility) {
299+
Self.renderCollageArtwork(from: selected)
300+
}.value
301+
if let collage {
302+
PlaylistArtworkCache.shared.setCachedArtwork(collage, for: playlistID, trackIDs: selectedIDs)
294303
}
295-
296-
return nil
304+
return collage
297305
}
298306

299307
// Get the effective track limit for display
@@ -321,8 +329,7 @@ struct Playlist: Identifiable, FetchableRecord, PersistableRecord {
321329
return result
322330
}
323331

324-
/// Renders a collage image from the given tracks into a 256×256 HEIC.
325-
private func generateCollageArtwork(from collageTracks: [Track]) -> Data? {
332+
fileprivate static func renderCollageArtwork(from collageTracks: [Track]) -> Data? {
326333
guard !collageTracks.isEmpty else { return nil }
327334

328335
let pixelSize = 256
@@ -374,7 +381,14 @@ struct Playlist: Identifiable, FetchableRecord, PersistableRecord {
374381
Logger.warning("Failed to create CGImage from collage context")
375382
return nil
376383
}
384+
385+
// Software HEVC encode deadlocks under concurrent invocation on Intel (issue #265),
386+
// so mirror the JPEG fallback used by ImageUtils.compressImage.
387+
#if arch(x86_64)
388+
return ImageUtils.encodeJPEG(collageImage)
389+
#else
377390
return ImageUtils.encodeHEIC(collageImage)
391+
#endif
378392
}
379393
}
380394

Views/Components/ArtistImageSheet.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,17 @@ struct ArtistImageSheet: View {
171171
Button("Save") {
172172
guard let index = selectedIndex, index < images.count else { return }
173173
let result = images[index]
174-
175-
if let compressed = ImageUtils.compressImage(from: result.imageData, source: "ArtistImageSheet/\(result.source)") {
174+
isPresented = false
175+
Task(priority: .utility) {
176+
guard let compressed = ImageUtils.compressImage(
177+
from: result.imageData,
178+
source: "ArtistImageSheet/\(result.source)"
179+
) else { return }
176180
let source = result.source.components(separatedBy: "").first ?? result.source
177-
saveArtistImage(compressed, url: result.imageUrl, source: source)
181+
await MainActor.run {
182+
saveArtistImage(compressed, url: result.imageUrl, source: source)
183+
}
178184
}
179-
isPresented = false
180185
}
181186
.buttonStyle(.borderedProminent)
182187
.disabled(selectedIndex == nil)

Views/Playlists/PlaylistDetailView.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ struct PlaylistDetailView: View {
99
@State private var showingReorderTracks = false
1010
@State private var isCustomSort = false
1111
@State private var gradientColors: [Color] = []
12+
@State private var artworkData: Data?
1213

1314
@AppStorage("useArtworkColors")
1415
private var useArtworkColors = true
@@ -50,27 +51,25 @@ struct PlaylistDetailView: View {
5051
.sheet(isPresented: $showingReorderTracks) {
5152
ReorderTracksSheet(playlist: playlist)
5253
}
54+
.task(id: playlistArtworkTaskID) {
55+
artworkData = await playlist.warmArtworkCacheIfNeeded()
56+
updateGradientColors()
57+
}
5358
.onChange(of: playlistID) {
5459
selectedTrackID = nil
5560
loadSortPreference()
5661
}
5762
.onChange(of: playlist.dateModified) {
5863
loadPlaylistTracksIfNeeded()
59-
updateGradientColors()
6064
}
6165
.onAppear {
6266
loadPlaylistTracksIfNeeded()
63-
updateGradientColors()
6467
loadSortPreference()
6568
}
6669
.onChange(of: playlist.id) {
6770
loadPlaylistTracksIfNeeded()
68-
updateGradientColors()
6971
loadSortPreference()
7072
}
71-
.onChange(of: playlist.tracks.count) {
72-
updateGradientColors()
73-
}
7473
.onReceive(NotificationCenter.default.publisher(for: .trackTableSortChanged)) { notification in
7574
if let customSort = notification.userInfo?["isCustomSort"] as? Bool {
7675
isCustomSort = customSort
@@ -138,7 +137,7 @@ struct PlaylistDetailView: View {
138137

139138
private var playlistArtwork: some View {
140139
Group {
141-
if let artworkData = playlist?.artworkData,
140+
if let artworkData,
142141
let nsImage = NSImage(data: artworkData) {
143142
Image(nsImage: nsImage)
144143
.resizable()
@@ -377,7 +376,7 @@ struct PlaylistDetailView: View {
377376
private func updateGradientColors() {
378377
guard useArtworkColors,
379378
let playlist = playlist,
380-
let artworkData = playlist.artworkData else {
379+
let artworkData = artworkData else {
381380
gradientColors = []
382381
return
383382
}
@@ -388,6 +387,11 @@ struct PlaylistDetailView: View {
388387
)
389388
}
390389

390+
private var playlistArtworkTaskID: String {
391+
guard let playlist else { return "nil" }
392+
return "\(playlist.id)-\(playlist.dateModified.timeIntervalSince1970)-\(playlist.tracks.count)"
393+
}
394+
391395
private func loadSortPreference() {
392396
let sortManager = PlaylistSortManager.shared
393397

0 commit comments

Comments
 (0)