@@ -5,55 +5,88 @@ import Foundation
55@MainActor
66extension PlayerService {
77 /// Likes the current track (thumbs up).
8+ /// Delegates to SongLikeStatusManager for unified state management and real-time sync.
89 func likeCurrentTrack( ) {
910 guard let track = currentTrack else { return }
1011 self . logger. info ( " Liking current track: \( track. videoId) " )
12+ let activeAccountID = SongLikeStatusManager . shared. activeAccountID
13+ let client = self . ytMusicClient
1114
1215 // Toggle: if already liked, remove the like
1316 let newStatus : LikeStatus = self . currentTrackLikeStatus == . like ? . indifferent : . like
14- let previousStatus = self . currentTrackLikeStatus
17+ // Optimistic UI update for PlayerBar
1518 self . currentTrackLikeStatus = newStatus
1619
17- // Use API call for reliable rating
20+ // Delegate to SongLikeStatusManager for API call + cache sync + event emission
1821 Task {
19- do {
20- try await self . ytMusicClient? . rateSong ( videoId: track. videoId, rating: newStatus)
21- self . logger. info ( " Successfully rated song as \( newStatus. rawValue) " )
22- } catch {
23- self . logger. error ( " Failed to rate song: \( error. localizedDescription) " )
24- // Revert on failure
25- self . currentTrackLikeStatus = previousStatus
22+ let finalStatus : LikeStatus = if newStatus == . like {
23+ await SongLikeStatusManager . shared. like (
24+ track,
25+ accountID: activeAccountID,
26+ client: client
27+ )
28+ } else {
29+ await SongLikeStatusManager . shared. unlike (
30+ track,
31+ accountID: activeAccountID,
32+ client: client
33+ )
34+ }
35+
36+ guard SongLikeStatusManager . shared. activeAccountID == activeAccountID,
37+ self . currentTrack? . videoId == track. videoId
38+ else {
39+ return
2640 }
41+
42+ self . currentTrackLikeStatus = finalStatus
2743 }
2844 }
2945
3046 /// Dislikes the current track (thumbs down).
47+ /// Delegates to SongLikeStatusManager for unified state management and real-time sync.
3148 func dislikeCurrentTrack( ) {
3249 guard let track = currentTrack else { return }
3350 self . logger. info ( " Disliking current track: \( track. videoId) " )
51+ let activeAccountID = SongLikeStatusManager . shared. activeAccountID
52+ let client = self . ytMusicClient
3453
3554 // Toggle: if already disliked, remove the dislike
3655 let newStatus : LikeStatus = self . currentTrackLikeStatus == . dislike ? . indifferent : . dislike
37- let previousStatus = self . currentTrackLikeStatus
56+ // Optimistic UI update for PlayerBar
3857 self . currentTrackLikeStatus = newStatus
3958
40- // Use API call for reliable rating
59+ // Delegate to SongLikeStatusManager for API call + cache sync + event emission
4160 Task {
42- do {
43- try await self . ytMusicClient? . rateSong ( videoId: track. videoId, rating: newStatus)
44- self . logger. info ( " Successfully rated song as \( newStatus. rawValue) " )
45- } catch {
46- self . logger. error ( " Failed to rate song: \( error. localizedDescription) " )
47- // Revert on failure
48- self . currentTrackLikeStatus = previousStatus
61+ let finalStatus : LikeStatus = if newStatus == . dislike {
62+ await SongLikeStatusManager . shared. dislike (
63+ track,
64+ accountID: activeAccountID,
65+ client: client
66+ )
67+ } else {
68+ await SongLikeStatusManager . shared. undislike (
69+ track,
70+ accountID: activeAccountID,
71+ client: client
72+ )
73+ }
74+
75+ guard SongLikeStatusManager . shared. activeAccountID == activeAccountID,
76+ self . currentTrack? . videoId == track. videoId
77+ else {
78+ return
4979 }
80+
81+ self . currentTrackLikeStatus = finalStatus
5082 }
5183 }
5284
5385 /// Toggles the library status of the current track.
5486 func toggleLibraryStatus( ) {
5587 guard let track = currentTrack else { return }
5688 self . logger. info ( " Toggling library status for current track: \( track. videoId) " )
89+ let activeAccountID = SongLikeStatusManager . shared. activeAccountID
5790
5891 // Determine which token to use based on current state
5992 let isCurrentlyInLibrary = self . currentTrackInLibrary
@@ -90,9 +123,16 @@ extension PlayerService {
90123 // The browse metadata can lag briefly, so delay the refresh and keep
91124 // the optimistic library state if the response is still stale.
92125 try ? await Task . sleep ( for: . milliseconds( 500 ) )
126+
127+ guard SongLikeStatusManager . shared. activeAccountID == activeAccountID else { return }
128+
93129 await self . fetchSongMetadata ( videoId: track. videoId)
94130
95- guard self . currentTrack? . videoId == track. videoId else { return }
131+ guard SongLikeStatusManager . shared. activeAccountID == activeAccountID,
132+ self . currentTrack? . videoId == track. videoId
133+ else {
134+ return
135+ }
96136
97137 if self . currentTrackInLibrary != expectedInLibrary {
98138 self . updateCurrentTrackLibraryState (
@@ -103,7 +143,11 @@ extension PlayerService {
103143 } catch {
104144 self . logger. error ( " Failed to toggle library status: \( error. localizedDescription) " )
105145 // Revert on failure
106- guard self . currentTrack? . videoId == track. videoId else { return }
146+ guard SongLikeStatusManager . shared. activeAccountID == activeAccountID,
147+ self . currentTrack? . videoId == track. videoId
148+ else {
149+ return
150+ }
107151
108152 self . updateCurrentTrackLibraryState (
109153 isInLibrary: previousState,
@@ -114,8 +158,18 @@ extension PlayerService {
114158 }
115159
116160 /// Updates the like status from WebView observation.
161+ /// Only updates PlayerService state for UI; does NOT overwrite SongLikeStatusManager cache
162+ /// because WebView often reports INDIFFERENT as a default when the actual status is unknown.
117163 func updateLikeStatus( _ status: LikeStatus ) {
118- self . currentTrackLikeStatus = status
164+ // Only accept WebView status if SongLikeStatusManager has no cached value
165+ // (cache is more authoritative than WebView's observation)
166+ if let videoId = self . currentTrack? . videoId,
167+ let cachedStatus = SongLikeStatusManager . shared. status ( for: videoId)
168+ {
169+ self . currentTrackLikeStatus = cachedStatus
170+ } else {
171+ self . currentTrackLikeStatus = status
172+ }
119173 }
120174
121175 /// Resets like/library status when track changes.
@@ -145,8 +199,14 @@ extension PlayerService {
145199 return
146200 }
147201
202+ let activeAccountID = SongLikeStatusManager . shared. activeAccountID
203+
148204 do {
149205 let songData = try await client. getSong ( videoId: videoId)
206+ guard SongLikeStatusManager . shared. activeAccountID == activeAccountID else { return }
207+
208+ let cachedLikeStatus = SongLikeStatusManager . shared. status ( for: videoId)
209+ let resolvedLikeStatus = songData. likeStatus ?? cachedLikeStatus
150210
151211 // Update current track with full metadata if it's still the same song
152212 if self . currentTrack? . videoId == videoId {
@@ -163,14 +223,19 @@ extension PlayerService {
163223 thumbnailURL: songData. thumbnailURL ?? self . currentTrack? . thumbnailURL,
164224 videoId: videoId,
165225 musicVideoType: songData. musicVideoType,
166- likeStatus: songData . likeStatus ,
226+ likeStatus: resolvedLikeStatus ,
167227 isInLibrary: songData. isInLibrary,
168228 feedbackTokens: songData. feedbackTokens
169229 )
170230
171- // Update service state
231+ // Update service state and sync with SongLikeStatusManager.
232+ // Unknown like status stays out of the cache so it cannot override
233+ // a known rating from the WebView or a prior user action.
172234 if let likeStatus = songData. likeStatus {
173235 self . currentTrackLikeStatus = likeStatus
236+ SongLikeStatusManager . shared. setStatus ( likeStatus, for: videoId)
237+ } else if let cachedLikeStatus {
238+ self . currentTrackLikeStatus = cachedLikeStatus
174239 }
175240 self . currentTrackInLibrary = songData. isInLibrary ?? false
176241 self . currentTrackFeedbackTokens = songData. feedbackTokens
@@ -196,8 +261,10 @@ extension PlayerService {
196261 currentQueueSong. thumbnailURL == nil
197262
198263 if needsUpdate {
199- self . queue [ queueIndex] = songData
200- self . logger. debug ( " Enriched queue entry at index \( queueIndex) : ' \( songData. title) ' with artists: \( songData. artistsDisplay) " )
264+ var enrichedQueueSong = songData
265+ enrichedQueueSong. likeStatus = resolvedLikeStatus
266+ self . queue [ queueIndex] = enrichedQueueSong
267+ self . logger. debug ( " Enriched queue entry at index \( queueIndex) : ' \( enrichedQueueSong. title) ' with artists: \( enrichedQueueSong. artistsDisplay) " )
201268 // Save the enriched queue to persistence
202269 self . saveQueueForPersistence ( )
203270 }
0 commit comments