11import Foundation
22
3+ // MARK: - RadioQueueResult
4+
5+ /// Result from parsing a radio queue, including songs and continuation token.
6+ struct RadioQueueResult {
7+ let songs : [ Song ]
8+ /// Continuation token for fetching more songs (infinite mix).
9+ let continuationToken : String ?
10+ }
11+
12+ // MARK: - RadioQueueParser
13+
314/// Parses radio queue responses from YouTube Music API.
415enum RadioQueueParser {
516 /// Parses the radio queue from the "next" endpoint response.
617 /// - Parameter data: The response from the "next" endpoint with a radio playlist ID
7- /// - Returns: Array of songs in the radio queue
8- static func parse( from data: [ String : Any ] ) -> [ Song ] {
18+ /// - Returns: RadioQueueResult containing songs and optional continuation token
19+ static func parse( from data: [ String : Any ] ) -> RadioQueueResult {
920 guard let contents = data [ " contents " ] as? [ String : Any ] ,
1021 let watchNextRenderer = contents [ " singleColumnMusicWatchNextResultsRenderer " ] as? [ String : Any ] ,
1122 let tabbedRenderer = watchNextRenderer [ " tabbedRenderer " ] as? [ String : Any ] ,
@@ -19,9 +30,50 @@ enum RadioQueueParser {
1930 let playlistPanelRenderer = queueContent [ " playlistPanelRenderer " ] as? [ String : Any ] ,
2031 let playlistContents = playlistPanelRenderer [ " contents " ] as? [ [ String : Any ] ]
2132 else {
22- return [ ]
33+ return RadioQueueResult ( songs: [ ] , continuationToken: nil )
34+ }
35+
36+ // Extract continuation token for infinite mix
37+ var continuationToken : String ?
38+ if let continuations = playlistPanelRenderer [ " continuations " ] as? [ [ String : Any ] ] ,
39+ let firstContinuation = continuations. first,
40+ let nextRadioData = firstContinuation [ " nextRadioContinuationData " ] as? [ String : Any ] ,
41+ let token = nextRadioData [ " continuation " ] as? String
42+ {
43+ continuationToken = token
44+ }
45+
46+ let songs = Self . parseSongs ( from: playlistContents)
47+ return RadioQueueResult ( songs: songs, continuationToken: continuationToken)
48+ }
49+
50+ /// Parses a continuation response for more queue items.
51+ /// - Parameter data: The continuation response
52+ /// - Returns: RadioQueueResult with additional songs and next continuation token
53+ static func parseContinuation( from data: [ String : Any ] ) -> RadioQueueResult {
54+ guard let continuationContents = data [ " continuationContents " ] as? [ String : Any ] ,
55+ let playlistPanelContinuation = continuationContents [ " playlistPanelContinuation " ] as? [ String : Any ] ,
56+ let contents = playlistPanelContinuation [ " contents " ] as? [ [ String : Any ] ]
57+ else {
58+ return RadioQueueResult ( songs: [ ] , continuationToken: nil )
2359 }
2460
61+ // Extract next continuation token
62+ var continuationToken : String ?
63+ if let continuations = playlistPanelContinuation [ " continuations " ] as? [ [ String : Any ] ] ,
64+ let firstContinuation = continuations. first,
65+ let nextRadioData = firstContinuation [ " nextRadioContinuationData " ] as? [ String : Any ] ,
66+ let token = nextRadioData [ " continuation " ] as? String
67+ {
68+ continuationToken = token
69+ }
70+
71+ let songs = Self . parseSongs ( from: contents)
72+ return RadioQueueResult ( songs: songs, continuationToken: continuationToken)
73+ }
74+
75+ /// Parses songs from playlist panel contents.
76+ private static func parseSongs( from playlistContents: [ [ String : Any ] ] ) -> [ Song ] {
2577 var songs : [ Song ] = [ ]
2678 for item in playlistContents {
2779 // Handle both direct and wrapped renderer structures
@@ -67,8 +119,6 @@ enum RadioQueueParser {
67119 return songs
68120 }
69121
70- // MARK: - Parsing Helpers
71-
72122 /// Parses the song title from the panel video renderer.
73123 private static func parseTitle( from renderer: [ String : Any ] ) -> String {
74124 if let titleData = renderer [ " title " ] as? [ String : Any ] ,
0 commit comments