@@ -25,6 +25,9 @@ extension PlayerService {
2525 // Sync the web view's current video ID so Kaset knows the player is already on this track
2626 SingletonPlayerWebView . shared. currentVideoId = observedVideoId
2727 if observedVideoId == previousVideoId, !self . queue. isEmpty {
28+ Task {
29+ await self . fetchSongMetadata ( videoId: observedVideoId)
30+ }
2831 return
2932 }
3033 self . mixContinuationToken = nil
@@ -140,13 +143,46 @@ extension PlayerService {
140143 guard let nextIndex = self . expectedQueueIndexAfterCurrentTrack ( ) ,
141144 let nextSong = self . queue [ safe: nextIndex] else { return }
142145
143- if self . injectedWebQueueVideoId != nextSong. videoId {
144- self . injectedWebQueueVideoId = nextSong. videoId
145- SingletonPlayerWebView . shared. injectNextSong ( videoId: nextSong. videoId)
146- self . logger. info ( " Synced web queue: injected \( nextSong. videoId) to play next natively " )
146+ guard self . injectedWebQueueVideoId != nextSong. videoId,
147+ self . pendingWebQueueInjectionVideoId != nextSong. videoId
148+ else { return }
149+
150+ self . pendingWebQueueInjectionVideoId = nextSong. videoId
151+ if SingletonPlayerWebView . shared. injectNextSong ( videoId: nextSong. videoId) {
152+ self . logger. info ( " Syncing web queue: requested injection of \( nextSong. videoId) to play next natively " )
153+ } else {
154+ self . pendingWebQueueInjectionVideoId = nil
147155 }
148156 }
149157
158+ /// Records the WebView result for an attempted native queue injection.
159+ func handleWebQueueInjectionResult( videoId: String , success: Bool , reason: String ? ) {
160+ if self . pendingWebQueueInjectionVideoId == videoId {
161+ self . pendingWebQueueInjectionVideoId = nil
162+ }
163+
164+ guard success else {
165+ if self . injectedWebQueueVideoId == videoId {
166+ self . injectedWebQueueVideoId = nil
167+ }
168+ self . logger. warning ( " Web queue injection failed for \( videoId) : \( reason ?? " unknown " ) " )
169+ return
170+ }
171+
172+ guard let nextIndex = self . expectedQueueIndexAfterCurrentTrack ( ) ,
173+ self . queue [ safe: nextIndex] ? . videoId == videoId
174+ else {
175+ if self . injectedWebQueueVideoId == videoId {
176+ self . injectedWebQueueVideoId = nil
177+ }
178+ self . logger. debug ( " Ignoring stale web queue injection confirmation for \( videoId) " )
179+ return
180+ }
181+
182+ self . injectedWebQueueVideoId = videoId
183+ self . logger. info ( " Synced web queue: confirmed \( videoId) to play next natively " )
184+ }
185+
150186 private var canAdvanceNativeQueueAfterTrackEnd : Bool {
151187 self . shuffleEnabled
152188 || self . repeatMode == . one
@@ -571,14 +607,25 @@ extension PlayerService {
571607 self . pushForwardSkipStackIfLeavingIndex ( for: expectedIndex)
572608 self . currentIndex = expectedIndex
573609 self . currentTrack = expectedSong
610+ self . pendingPlayVideoId = expectedSong. videoId
611+ self . state = . loading
612+ self . songNearingEnd = false
613+ self . shouldSuppressAutoplayAfterQueueEnd = false
574614 self . isKasetInitiatedPlayback = true
615+ self . currentTrackHasVideo = expectedSong. musicVideoType? . hasVideoContent ?? expectedSong. hasVideo ?? false
575616 self . resetTrackStatus ( )
576617 if let cachedStatus = SongLikeStatusManager . shared. status ( for: expectedSong. videoId) {
577618 self . currentTrackLikeStatus = cachedStatus
578619 }
579- self . saveQueueForPersistence ( )
580- // Pre-inject the *next* next track for the following transition
581- self . syncWebQueue ( )
620+ if let details = expectedSong. feedbackTokens {
621+ self . currentTrackFeedbackTokens = details
622+ self . currentTrackInLibrary = expectedSong. isInLibrary ?? false
623+ self . currentTrackLikeStatus = expectedSong. likeStatus ?? self . currentTrackLikeStatus
624+ }
625+ self . saveQueueForPersistence ( syncWebQueue: false )
626+ await self . fetchMoreMixSongsIfNeeded ( )
627+ await self . fillSmartShuffleWindow ( )
628+ self . saveQueueForPersistence ( syncWebQueue: false )
582629 return
583630 }
584631
@@ -590,9 +637,22 @@ extension PlayerService {
590637 func updateTrackMetadata( title: String , artist: String , thumbnailUrl: String , videoId observedVideoId: String ? ) {
591638 self . logger. debug ( " Track metadata updated: \( title) - \( artist) " )
592639
593- let isRestoringFromCloud = self . queue. isEmpty && !self . isKasetInitiatedPlayback && observedVideoId != nil
640+ let isRestoringFromCloud = self . isAwaitingWebRestoredTrack
641+ && !self . isKasetInitiatedPlayback
642+ && observedVideoId != nil
643+
644+ if self . isPendingRestoredLoadDeferred {
645+ guard self . isAwaitingWebRestoredTrack else { return }
646+ self . applyDeferredRestoredMetadata (
647+ title: title,
648+ artist: artist,
649+ thumbnailUrl: thumbnailUrl,
650+ videoId: observedVideoId
651+ )
652+ return
653+ }
594654
595- if self . isPendingRestoredLoadDeferred || isRestoringFromCloud {
655+ if isRestoringFromCloud {
596656 self . applyDeferredRestoredMetadata (
597657 title: title,
598658 artist: artist,
@@ -676,6 +736,7 @@ extension PlayerService {
676736 )
677737
678738 if trackChanged {
739+ self . clearWebQueueInjectionState ( )
679740 self . resetTrackStatus ( )
680741 // Immediately restore like status from SongLikeStatusManager cache
681742 if let cachedStatus = SongLikeStatusManager . shared. status ( for: resolvedVideoId) {
0 commit comments