Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

// Register for system sleep/wake notifications
self.registerForSleepWakeNotifications()

// Restore saved queue if available
self.playerService?.restoreQueueFromPersistence()
}

func applicationWillTerminate(_: Notification) {
// Save queue for persistence on next launch
self.playerService?.saveQueueForPersistence()
DiagnosticsLogger.player.info("Application will terminate - saved queue for persistence")
}

/// Registers for system sleep and wake notifications to handle playback appropriately.
Expand Down
4 changes: 4 additions & 0 deletions App/KasetApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ struct KasetApp: App {
_notificationService = State(initialValue: NotificationService(playerService: player))
_accountService = State(initialValue: account)

// Wire up PlayerService to AppDelegate immediately (not in onAppear)
// This ensures playerService is available for lifecycle events like queue restoration
self.appDelegate.playerService = player

if UITestConfig.isUITestMode {
DiagnosticsLogger.ui.info("App launched in UI Test mode")
}
Expand Down
23 changes: 23 additions & 0 deletions Core/Models/QueueDisplayMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

// MARK: - QueueDisplayMode

/// Display mode for the playback queue panel.
enum QueueDisplayMode: String, Codable, CaseIterable, Sendable {
case popup
case sidepanel

var displayName: String {
switch self {
case .popup: "Popup"
case .sidepanel: "Side Panel"
}
}

var description: String {
switch self {
case .popup: "Compact overlay view"
case .sidepanel: "Full-width panel with reordering"
}
}
}
9 changes: 9 additions & 0 deletions Core/Models/Song.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CoreTransferable
import Foundation

// MARK: - Song
Expand Down Expand Up @@ -140,3 +141,11 @@ extension Song {
hasher.combine(self.videoId)
}
}

// MARK: Transferable

extension Song: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(for: Song.self, contentType: .data)
}
}
19 changes: 19 additions & 0 deletions Core/Services/Player/PlayerService+Library.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ extension PlayerService {
}

self.logger.info("Updated track metadata - inLibrary: \(self.currentTrackInLibrary), hasTokens: \(self.currentTrackFeedbackTokens != nil)")

// Also update the corresponding song in the queue with enriched metadata
// This ensures the queue displays complete info without separate API calls
if let queueIndex = self.queue.firstIndex(where: { $0.videoId == videoId }) {
// Only update if the queue entry is missing metadata
let currentQueueSong = self.queue[queueIndex]
let needsUpdate = currentQueueSong.artists.isEmpty ||
currentQueueSong.artists.allSatisfy { $0.name.isEmpty || $0.name == "Unknown Artist" } ||
currentQueueSong.title.isEmpty ||
currentQueueSong.title == "Loading..." ||
currentQueueSong.thumbnailURL == nil

if needsUpdate {
self.queue[queueIndex] = songData
self.logger.debug("Enriched queue entry at index \(queueIndex): '\(songData.title)' with artists: \(songData.artistsDisplay)")
// Save the enriched queue to persistence
self.saveQueueForPersistence()
}
}
}
} catch {
self.logger.warning("Failed to fetch song metadata: \(error.localizedDescription)")
Expand Down
Loading
Loading