@@ -7,135 +7,6 @@ import { APP_NAME } from "./appInfo";
77import { showPromptDialog } from "./dialogs" ;
88import { showErrorToast , showSuccessToast , showWarningToast } from "./toast" ;
99
10- export async function updateQueueFromSnapshot (
11- snapshot : Snapshot ,
12- mode : "append" | "replace" ,
13- buttonEl ?: HTMLButtonElement ,
14- ) : Promise < void > {
15- const opName = mode === "replace" ? "Replace" : "Append to" ;
16- const opVerb = mode === "replace" ? "Replacing" : "Appending" ;
17- const opNoun = mode === "replace" ? "replace" : "append" ;
18-
19- try {
20- if ( ! snapshot . items . length ) {
21- showWarningToast ( "Snapshot is empty" ) ;
22- return ;
23- }
24- if ( buttonEl ) {
25- buttonEl . disabled = true ;
26- setButtonLabel ( buttonEl , `${ opVerb } …` ) ;
27- }
28-
29- const itemsToQueue = snapshot . items . slice ( ) ;
30- let playbackStarted = false ;
31-
32- if ( mode === "replace" ) {
33- const first = itemsToQueue . shift ( ) ! ;
34-
35- try {
36- await Spicetify . Platform . PlayerAPI . clearQueue ( ) ;
37- } catch ( e ) {
38- console . warn ( `${ APP_NAME } : clearQueue failed (non-fatal)` , e ) ;
39- }
40-
41- if ( first . startsWith ( "spotify:local:" ) ) {
42- try {
43- await Spicetify . Platform . PlayerAPI . play (
44- {
45- uri : "spotify:internal:local-files" ,
46- pages : [ { items : [ { uri : first } ] } ] ,
47- } ,
48- { } ,
49- { skipTo : { index : 0 } } ,
50- ) ;
51- playbackStarted = true ;
52- } catch ( eLocal ) {
53- console . warn ( `${ APP_NAME } : local fallback failed` , eLocal ) ;
54- }
55- } else {
56- try {
57- await Spicetify . Player . playUri ( first ) ;
58- playbackStarted = true ;
59- } catch ( e1 ) {
60- try {
61- await Spicetify . Platform . PlayerAPI . play ( { uri : first } , { } , { } ) ;
62- playbackStarted = true ;
63- } catch ( e2 ) {
64- console . warn ( `${ APP_NAME } : failed to start first track` , e2 ) ;
65- }
66- }
67- }
68-
69- if ( ! playbackStarted ) {
70- throw new Error ( "Failed to start playback" ) ;
71- }
72-
73- // Give the player a brief moment to switch track
74- await new Promise ( r => setTimeout ( r , 250 ) ) ;
75- }
76-
77- // Enqueue remaining items in order
78- let addedToQueue = 0 ;
79- for ( let i = 0 ; i < itemsToQueue . length ; i += 100 ) {
80- const chunkUris = itemsToQueue . slice ( i , i + 100 ) ;
81- const chunk = chunkUris . map ( uri => ( { uri } ) ) ;
82- try {
83- await Spicetify . Platform . PlayerAPI . addToQueue ( chunk ) ;
84- addedToQueue += chunk . length ;
85- } catch ( err ) {
86- console . warn ( `${ APP_NAME } : addToQueue chunk failed` , err ) ;
87- for ( const uri of chunkUris ) {
88- try {
89- await Spicetify . Platform . PlayerAPI . addToQueue ( [ { uri } ] ) ;
90- addedToQueue += 1 ;
91- } catch ( singleErr ) {
92- console . warn ( `${ APP_NAME } : addToQueue single failed` , { uri, error : singleErr } ) ;
93- }
94- }
95- }
96- }
97-
98- const totalExpected = snapshot . items . length ;
99- if ( mode === "replace" ) {
100- const totalInQueue = ( playbackStarted ? 1 : 0 ) + addedToQueue ;
101- if ( totalInQueue === totalExpected ) {
102- showSuccessToast ( `Queue replaced (${ totalExpected } items)` ) ;
103- } else {
104- showWarningToast ( `Replaced; some items couldn't be queued (${ totalInQueue } /${ totalExpected } )` ) ;
105- }
106- } else {
107- // append
108- if ( addedToQueue === totalExpected ) {
109- showSuccessToast ( `Added ${ totalExpected } ${ totalExpected === 1 ? "item" : "items" } to queue` ) ;
110- } else if ( addedToQueue > 0 ) {
111- showWarningToast ( `Added ${ addedToQueue } /${ totalExpected } items to queue` ) ;
112- } else {
113- showErrorToast ( "Failed to queue snapshot items" ) ;
114- }
115- }
116-
117- try {
118- console . log ( `${ APP_NAME } : ${ opName } result` , {
119- snapshotId : snapshot . id ,
120- totalExpected,
121- added : mode === "replace" ? ( playbackStarted ? 1 : 0 ) + addedToQueue : addedToQueue ,
122- } ) ;
123- } catch { }
124- } catch ( e ) {
125- console . error ( `${ APP_NAME } : ${ opNoun } queue failed` , e ) ;
126- if ( mode === "replace" && ( e as Error ) ?. message === "Failed to start playback" ) {
127- showErrorToast ( "Failed to start playback" ) ;
128- } else {
129- showErrorToast ( `Failed to ${ opNoun } queue` ) ;
130- }
131- } finally {
132- if ( buttonEl ) {
133- buttonEl . disabled = false ;
134- setButtonLabel ( buttonEl , `${ opName } queue` ) ;
135- }
136- }
137- }
138-
13910export async function createManualSnapshot ( ) : Promise < void > {
14011 try {
14112 const items = await getQueueFromSpicetify ( ) ;
@@ -256,4 +127,151 @@ export async function exportSnapshotToPlaylist(snapshot: Snapshot, buttonEl?: HT
256127 setButtonLabel ( buttonEl , "Export" ) ;
257128 }
258129 }
130+ }
131+
132+ export async function appendSnapshotToQueue ( snapshot : Snapshot , buttonEl ?: HTMLButtonElement ) : Promise < void > {
133+ try {
134+ if ( ! snapshot . items . length ) {
135+ showWarningToast ( "Snapshot is empty" ) ;
136+ return ;
137+ }
138+
139+ if ( buttonEl ) {
140+ buttonEl . disabled = true ;
141+ setButtonLabel ( buttonEl , "Appending…" ) ;
142+ }
143+
144+ const items = snapshot . items . slice ( ) ;
145+ let added = 0 ;
146+
147+ for ( let i = 0 ; i < items . length ; i += 100 ) {
148+ const chunkUris = items . slice ( i , i + 100 ) ;
149+ const chunk = chunkUris . map ( uri => ( { uri } ) ) ;
150+ try {
151+ await Spicetify . Platform . PlayerAPI . addToQueue ( chunk ) ;
152+ added += chunk . length ;
153+ } catch ( err ) {
154+ console . warn ( `${ APP_NAME } : addToQueue chunk failed` , err ) ;
155+ for ( const uri of chunkUris ) {
156+ try {
157+ await Spicetify . Platform . PlayerAPI . addToQueue ( [ { uri } ] ) ;
158+ added += 1 ;
159+ } catch ( singleErr ) {
160+ console . warn ( `${ APP_NAME } : addToQueue single failed` , { uri, error : singleErr } ) ;
161+ }
162+ }
163+ }
164+ }
165+
166+ const totalExpected = items . length ;
167+
168+ if ( added === totalExpected ) {
169+ showSuccessToast ( `Added ${ totalExpected } ${ totalExpected === 1 ? "item" : "items" } to queue` ) ;
170+ } else if ( added > 0 ) {
171+ showWarningToast ( `Added ${ added } /${ totalExpected } items to queue` ) ;
172+ } else {
173+ showErrorToast ( "Failed to queue snapshot items" ) ;
174+ }
175+
176+ try {
177+ console . log ( `${ APP_NAME } : Append result` , {
178+ snapshotId : snapshot . id ,
179+ totalExpected,
180+ added,
181+ } ) ;
182+ } catch { }
183+ } catch ( e ) {
184+ console . error ( `${ APP_NAME } : append queue failed` , e ) ;
185+ showErrorToast ( "Failed to append to queue" ) ;
186+ } finally {
187+ if ( buttonEl ) {
188+ buttonEl . disabled = false ;
189+ setButtonLabel ( buttonEl , "Append to queue" ) ;
190+ }
191+ }
192+ }
193+
194+ export async function replaceQueueWithSnapshot ( snapshot : Snapshot , buttonEl ?: HTMLButtonElement ) : Promise < void > {
195+ try {
196+ if ( ! snapshot . items . length ) {
197+ showWarningToast ( "Snapshot is empty" ) ;
198+ return ;
199+ }
200+ if ( buttonEl ) {
201+ buttonEl . disabled = true ;
202+ setButtonLabel ( buttonEl , "Replacing…" ) ;
203+ }
204+
205+ const items = snapshot . items . slice ( ) ;
206+ const first = items . shift ( ) ! ;
207+
208+ try {
209+ await Spicetify . Platform . PlayerAPI . clearQueue ( ) ;
210+ } catch ( e ) {
211+ console . warn ( `${ APP_NAME } : clearQueue failed (non-fatal)` , e ) ;
212+ }
213+
214+ if ( first . startsWith ( "spotify:local:" ) ) {
215+ try {
216+ await Spicetify . Platform . PlayerAPI . play ( {
217+ uri : "spotify:internal:local-files" ,
218+ pages : [
219+ {
220+ items : [ { uri : first } ] ,
221+ } ,
222+ ] ,
223+ } , { } , {
224+ skipTo : {
225+ index : 0 ,
226+ } ,
227+ } ) ;
228+ } catch ( eLocal ) {
229+ console . warn ( `${ APP_NAME } : local fallback failed` , eLocal ) ;
230+ showErrorToast ( "Failed to start playback" ) ;
231+ return ;
232+ }
233+ } else {
234+ // Start playback with the first item
235+ try {
236+ await Spicetify . Player . playUri ( first ) ;
237+ } catch ( e1 ) {
238+ try {
239+ await Spicetify . Platform . PlayerAPI . play ( { uri : first } , { } , { } ) ;
240+ } catch ( e2 ) {
241+ console . warn ( `${ APP_NAME } : failed to start first track` , e2 ) ;
242+ showErrorToast ( "Failed to start playback" ) ;
243+ return ;
244+ }
245+ }
246+ }
247+
248+ // Give the player a brief moment to switch track
249+ await new Promise ( r => setTimeout ( r , 250 ) ) ;
250+
251+ // Enqueue remaining items in order
252+ let added = 0 ;
253+ for ( let i = 0 ; i < items . length ; i += 100 ) {
254+ const chunk = items . slice ( i , i + 100 ) . map ( u => ( { uri : u } ) ) ;
255+ try {
256+ await Spicetify . Platform . PlayerAPI . addToQueue ( chunk ) ;
257+ added += chunk . length ;
258+ } catch ( e ) {
259+ console . warn ( `${ APP_NAME } : addToQueue chunk failed` , e ) ;
260+ }
261+ }
262+
263+ if ( added === items . length ) {
264+ showSuccessToast ( `Queue replaced (${ snapshot . items . length } items)` ) ;
265+ } else {
266+ showWarningToast ( `Replaced; some items couldn't be queued (${ added + 1 } /${ snapshot . items . length } )` ) ;
267+ }
268+ } catch ( e ) {
269+ console . error ( `${ APP_NAME } : replace queue failed` , e ) ;
270+ showErrorToast ( "Failed to replace queue" ) ;
271+ } finally {
272+ if ( buttonEl ) {
273+ buttonEl . disabled = false ;
274+ setButtonLabel ( buttonEl , "Replace queue" ) ;
275+ }
276+ }
259277}
0 commit comments