Working but NowPlaying slider not working

This commit is contained in:
Hariharan Mudaliar 2025-10-05 23:48:38 +05:30
commit 80de66b3ad
3 changed files with 43 additions and 69 deletions

View file

@ -135,7 +135,7 @@ struct MinimalisticMusicPlayerView: View {
// Update slider value based on playback // Update slider value based on playback
if musicManager.isPlaying { if musicManager.isPlaying {
let timeSinceLastUpdate = date.timeIntervalSince(musicManager.lastUpdated) let timeSinceLastUpdate = date.timeIntervalSince(musicManager.timestampDate)
let estimatedElapsed = musicManager.elapsedTime + (timeSinceLastUpdate * musicManager.playbackRate) let estimatedElapsed = musicManager.elapsedTime + (timeSinceLastUpdate * musicManager.playbackRate)
return min(estimatedElapsed, musicManager.songDuration) return min(estimatedElapsed, musicManager.songDuration)
} }

View file

@ -156,8 +156,6 @@ struct MusicControlsView: View {
color: musicManager.avgColor, color: musicManager.avgColor,
dragging: $dragging, dragging: $dragging,
currentDate: timeline.date, currentDate: timeline.date,
lastUpdated: musicManager.lastUpdated,
ignoreLastUpdated: musicManager.ignoreLastUpdated,
timestampDate: musicManager.timestampDate, timestampDate: musicManager.timestampDate,
elapsedTime: musicManager.elapsedTime, elapsedTime: musicManager.elapsedTime,
playbackRate: musicManager.playbackRate, playbackRate: musicManager.playbackRate,
@ -285,8 +283,6 @@ struct MusicSliderView: View {
var color: NSColor var color: NSColor
@Binding var dragging: Bool @Binding var dragging: Bool
let currentDate: Date let currentDate: Date
let lastUpdated: Date
let ignoreLastUpdated: Bool
let timestampDate: Date let timestampDate: Date
let elapsedTime: Double let elapsedTime: Double
let playbackRate: Double let playbackRate: Double

View file

@ -34,8 +34,6 @@ class MusicManager: ObservableObject {
@Published var albumArt: NSImage = defaultImage @Published var albumArt: NSImage = defaultImage
@Published var isPlaying = false @Published var isPlaying = false
@Published var album: String = "Self Love" @Published var album: String = "Self Love"
@Published var lastUpdated: Date = .distantPast
@Published var ignoreLastUpdated = true
@Published var isPlayerIdle: Bool = true @Published var isPlayerIdle: Bool = true
@Published var animations: DynamicIslandAnimations = .init() @Published var animations: DynamicIslandAnimations = .init()
@Published var avgColor: NSColor = .white @Published var avgColor: NSColor = .white
@ -48,9 +46,15 @@ class MusicManager: ObservableObject {
@Published var repeatMode: RepeatMode = .off @Published var repeatMode: RepeatMode = .off
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@Published var usingAppIconForArtwork: Bool = false @Published var usingAppIconForArtwork: Bool = false
private var artworkData: Data? = nil private var artworkData: Data? = nil
// Store last values at the time artwork was changed
private var lastArtworkTitle: String = "I'm Handsome"
private var lastArtworkArtist: String = "Me"
private var lastArtworkAlbum: String = "Self Love"
private var lastArtworkBundleIdentifier: String? = nil
@Published var isFlipping: Bool = false @Published var isFlipping: Bool = false
private var flipWorkItem: DispatchWorkItem? private var flipWorkItem: DispatchWorkItem?
@ -82,12 +86,16 @@ class MusicManager: ObservableObject {
} }
deinit { deinit {
destroy()
}
public func destroy() {
debounceIdleTask?.cancel() debounceIdleTask?.cancel()
cancellables.removeAll() cancellables.removeAll()
controllerCancellables.removeAll() controllerCancellables.removeAll()
flipWorkItem?.cancel() flipWorkItem?.cancel()
transitionWorkItem?.cancel() transitionWorkItem?.cancel()
// Release active controller // Release active controller
activeController = nil activeController = nil
} }
@ -106,35 +114,22 @@ class MusicManager: ObservableObject {
case .nowPlaying: case .nowPlaying:
// Only create NowPlayingController if not deprecated on this macOS version // Only create NowPlayingController if not deprecated on this macOS version
if !self.isNowPlayingDeprecated { if !self.isNowPlayingDeprecated {
ignoreLastUpdated = false newController = NowPlayingController()
if let controller = NowPlayingController() {
// Check if the controller can actually work
if controller.isWorking {
newController = controller
} else {
print("⚠️ NowPlayingController created but resources unavailable, falling back to default")
return nil
}
} else {
return nil
}
} else { } else {
return nil return nil
} }
case .appleMusic: case .appleMusic:
ignoreLastUpdated = true
newController = AppleMusicController() newController = AppleMusicController()
case .spotify: case .spotify:
ignoreLastUpdated = true
newController = SpotifyController() newController = SpotifyController()
case .youtubeMusic: case .youtubeMusic:
ignoreLastUpdated = true
newController = YouTubeMusicController() newController = YouTubeMusicController()
} }
// Set up state observation for the new controller // Set up state observation for the new controller
if let controller = newController { if let controller = newController {
controller.playbackStatePublisher controller.playbackStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] state in .sink { [weak self] state in
guard let self = self, guard let self = self,
self.activeController === controller else { return } self.activeController === controller else { return }
@ -164,19 +159,8 @@ class MusicManager: ObservableObject {
} }
private func setActiveController(_ controller: any MediaControllerProtocol) { private func setActiveController(_ controller: any MediaControllerProtocol) {
// Transition animation when changing controllers // Set the active controller
transitionWorkItem?.cancel() self.activeController = controller
let workItem = DispatchWorkItem { [weak self] in
self?.isTransitioning = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self?.isTransitioning = false
}
}
transitionWorkItem = workItem
DispatchQueue.main.async(execute: workItem)
// Set new active controller
activeController = controller
// Get current state from active controller // Get current state from active controller
if let state = Mirror(reflecting: controller).children.first(where: { $0.label == "playbackState" })?.value as? PlaybackState { if let state = Mirror(reflecting: controller).children.first(where: { $0.label == "playbackState" })?.value as? PlaybackState {
@ -192,7 +176,6 @@ class MusicManager: ObservableObject {
// Check for playback state changes (playing/paused) // Check for playback state changes (playing/paused)
if state.isPlaying != self.isPlaying { if state.isPlaying != self.isPlaying {
self.lastUpdated = Date()
withAnimation(.smooth) { withAnimation(.smooth) {
self.isPlaying = state.isPlaying self.isPlaying = state.isPlaying
self.updateIdleState(state: state.isPlaying) self.updateIdleState(state: state.isPlaying)
@ -218,7 +201,7 @@ class MusicManager: ObservableObject {
if artworkChanged, let artwork = state.artwork { if artworkChanged, let artwork = state.artwork {
self.updateArtwork(artwork) self.updateArtwork(artwork)
} else if hasContentChange && state.artwork == nil { } else if state.artwork == nil {
// Try to use app icon if no artwork but track changed // Try to use app icon if no artwork but track changed
if let appIconImage = AppIconAsNSImage(for: state.bundleIdentifier) { if let appIconImage = AppIconAsNSImage(for: state.bundleIdentifier) {
self.usingAppIconForArtwork = true self.usingAppIconForArtwork = true
@ -227,6 +210,14 @@ class MusicManager: ObservableObject {
} }
self.artworkData = state.artwork self.artworkData = state.artwork
if artworkChanged || state.artwork == nil {
// Update last artwork change values
self.lastArtworkTitle = state.title
self.lastArtworkArtist = state.artist
self.lastArtworkAlbum = state.album
self.lastArtworkBundleIdentifier = state.bundleIdentifier
}
// Only update sneak peek if there's actual content and something changed // Only update sneak peek if there's actual content and something changed
if !state.title.isEmpty && !state.artist.isEmpty && state.isPlaying { if !state.title.isEmpty && !state.artist.isEmpty && state.isPlaying {
self.updateSneakPeek() self.updateSneakPeek()
@ -236,16 +227,18 @@ class MusicManager: ObservableObject {
let timeChanged = state.currentTime != self.elapsedTime let timeChanged = state.currentTime != self.elapsedTime
let durationChanged = state.duration != self.songDuration let durationChanged = state.duration != self.songDuration
let playbackRateChanged = state.playbackRate != self.playbackRate let playbackRateChanged = state.playbackRate != self.playbackRate
let shuffleChanged = state.isShuffled != self.isShuffled
let repeatModeChanged = state.repeatMode != self.repeatMode
if titleChanged { if state.title != self.songTitle {
self.songTitle = state.title self.songTitle = state.title
} }
if artistChanged { if state.artist != self.artistName {
self.artistName = state.artist self.artistName = state.artist
} }
if albumChanged { if state.album != self.album {
self.album = state.album self.album = state.album
} }
@ -261,30 +254,19 @@ class MusicManager: ObservableObject {
self.playbackRate = state.playbackRate self.playbackRate = state.playbackRate
} }
if shuffleChanged {
self.isShuffled = state.isShuffled
}
if state.bundleIdentifier != self.bundleIdentifier { if state.bundleIdentifier != self.bundleIdentifier {
self.bundleIdentifier = state.bundleIdentifier self.bundleIdentifier = state.bundleIdentifier
} }
// Update shuffle and repeat state from controller if repeatModeChanged {
if state.isShuffled != self.isShuffled {
self.isShuffled = state.isShuffled
}
if state.repeatMode != self.repeatMode {
self.repeatMode = state.repeatMode self.repeatMode = state.repeatMode
} }
// Update timestamp - use current time if state doesn't provide recent timestamp self.timestampDate = state.lastUpdated
let stateTimestamp = state.lastUpdated
let now = Date()
// If the state timestamp is very recent (within 1 second), use it
// Otherwise, use current time for better real-time calculation
if abs(stateTimestamp.timeIntervalSince(now)) < 1.0 {
self.timestampDate = stateTimestamp
} else {
self.timestampDate = now
}
} }
// Execute the batch update on the main thread // Execute the batch update on the main thread
@ -312,9 +294,9 @@ class MusicManager: ObservableObject {
guard let self = self else { return } guard let self = self else { return }
if let artworkImage = NSImage(data: artworkData) { if let artworkImage = NSImage(data: artworkData) {
DispatchQueue.main.async { DispatchQueue.main.async { [weak self] in
self.usingAppIconForArtwork = false self?.usingAppIconForArtwork = false
self.updateAlbumArt(newAlbumArt: artworkImage) self?.updateAlbumArt(newAlbumArt: artworkImage)
} }
} }
} }
@ -327,14 +309,10 @@ class MusicManager: ObservableObject {
} else { } else {
debounceIdleTask?.cancel() debounceIdleTask?.cancel()
debounceIdleTask = Task { [weak self] in debounceIdleTask = Task { [weak self] in
guard let self = self else { return }
try? await Task.sleep(for: .seconds(Defaults[.waitInterval])) try? await Task.sleep(for: .seconds(Defaults[.waitInterval]))
guard let self = self, !Task.isCancelled else { return } withAnimation {
await MainActor.run { self.isPlayerIdle = !self.isPlaying
if self.lastUpdated.timeIntervalSinceNow < -Defaults[.waitInterval] {
withAnimation {
self.isPlayerIdle = !self.isPlaying
}
}
} }
} }
} }