gapless.js 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -13
- package/dist/index.mjs +1 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -1
- package/src/Queue.ts +110 -138
- package/src/Track.ts +134 -218
- package/src/machines/fetchDecode.machine.ts +130 -0
- package/src/machines/queue.machine.ts +237 -68
- package/src/machines/track.machine.ts +254 -74
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
// ended The last track in the queue has finished.
|
|
9
9
|
//
|
|
10
10
|
// Root-level `on:` eliminates the handler duplication that plagued v2.
|
|
11
|
+
//
|
|
12
|
+
// Named actions (no-op defaults here, real implementations via .provide()):
|
|
13
|
+
// Actions before assign() see the OLD context (e.g. deactivateCurrent
|
|
14
|
+
// sees the old currentTrackIndex). Actions after assign() see the NEW
|
|
15
|
+
// context (e.g. activateAndPlayCurrent sees the incremented index).
|
|
16
|
+
// Implementations MUST use the ({ context }) parameter, NOT getSnapshot().
|
|
11
17
|
// ---------------------------------------------------------------------------
|
|
12
18
|
|
|
13
19
|
import { setup, assign } from 'xstate';
|
|
@@ -48,6 +54,60 @@ export function createQueueMachine(initialContext: QueueContext) {
|
|
|
48
54
|
playImmediately: ({ event }) =>
|
|
49
55
|
!!(event as { type: 'GOTO'; playImmediately?: boolean }).playImmediately,
|
|
50
56
|
},
|
|
57
|
+
actions: {
|
|
58
|
+
// --- named assign actions (avoids mixing inline assign with custom actions) ---
|
|
59
|
+
incrementTrackCount: assign({
|
|
60
|
+
trackCount: ({ context }) => context.trackCount + 1,
|
|
61
|
+
}),
|
|
62
|
+
decrementTrackCount: assign({
|
|
63
|
+
trackCount: ({ context }) => Math.max(0, context.trackCount - 1),
|
|
64
|
+
currentTrackIndex: ({ context, event }) => {
|
|
65
|
+
const e = event as { type: 'REMOVE_TRACK'; index: number };
|
|
66
|
+
if (e.index < context.currentTrackIndex) {
|
|
67
|
+
return Math.max(0, context.currentTrackIndex - 1);
|
|
68
|
+
}
|
|
69
|
+
return context.currentTrackIndex;
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
gotoTrackIndex: assign({
|
|
73
|
+
currentTrackIndex: ({ event }) =>
|
|
74
|
+
(event as { type: 'GOTO'; index: number }).index,
|
|
75
|
+
}),
|
|
76
|
+
advanceToNextTrack: assign({
|
|
77
|
+
currentTrackIndex: ({ context }) => {
|
|
78
|
+
const next = context.currentTrackIndex + 1;
|
|
79
|
+
return next < context.trackCount ? next : context.currentTrackIndex;
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
goToPreviousTrack: assign({
|
|
83
|
+
currentTrackIndex: ({ context }) => Math.max(0, context.currentTrackIndex - 1),
|
|
84
|
+
}),
|
|
85
|
+
advanceOnTrackEnd: assign({
|
|
86
|
+
currentTrackIndex: ({ context }) => context.currentTrackIndex + 1,
|
|
87
|
+
}),
|
|
88
|
+
resetToFirstTrack: assign({
|
|
89
|
+
currentTrackIndex: () => 0,
|
|
90
|
+
}),
|
|
91
|
+
// --- side-effect actions (no-op defaults, provided by Queue.ts) ---
|
|
92
|
+
deactivateCurrent: () => {},
|
|
93
|
+
deactivateEndedTrack: () => {},
|
|
94
|
+
activateAndPlayCurrent: () => {},
|
|
95
|
+
playOrContinueGapless: () => {},
|
|
96
|
+
cancelAllGapless: () => {},
|
|
97
|
+
notifyStartNewTrack: () => {},
|
|
98
|
+
notifyPlayNextTrack: () => {},
|
|
99
|
+
notifyPlayPreviousTrack: () => {},
|
|
100
|
+
notifyEnded: () => {},
|
|
101
|
+
updateMediaSessionMetadata: () => {},
|
|
102
|
+
preloadAhead: () => {},
|
|
103
|
+
playCurrent: () => {},
|
|
104
|
+
pauseCurrent: () => {},
|
|
105
|
+
seekCurrent: () => {},
|
|
106
|
+
seekCurrentToZero: () => {},
|
|
107
|
+
scheduleGapless: () => {},
|
|
108
|
+
cancelScheduledGapless: () => {},
|
|
109
|
+
cancelAndRescheduleGapless: () => {},
|
|
110
|
+
},
|
|
51
111
|
}).createMachine({
|
|
52
112
|
id: 'queue',
|
|
53
113
|
initial: 'idle',
|
|
@@ -56,19 +116,10 @@ export function createQueueMachine(initialContext: QueueContext) {
|
|
|
56
116
|
// Global handlers — shared across all states
|
|
57
117
|
on: {
|
|
58
118
|
ADD_TRACK: {
|
|
59
|
-
actions:
|
|
119
|
+
actions: 'incrementTrackCount',
|
|
60
120
|
},
|
|
61
121
|
REMOVE_TRACK: {
|
|
62
|
-
actions:
|
|
63
|
-
trackCount: ({ context }) => Math.max(0, context.trackCount - 1),
|
|
64
|
-
currentTrackIndex: ({ context, event }) => {
|
|
65
|
-
const e = event as { type: 'REMOVE_TRACK'; index: number };
|
|
66
|
-
if (e.index < context.currentTrackIndex) {
|
|
67
|
-
return Math.max(0, context.currentTrackIndex - 1);
|
|
68
|
-
}
|
|
69
|
-
return context.currentTrackIndex;
|
|
70
|
-
},
|
|
71
|
-
}),
|
|
122
|
+
actions: 'decrementTrackCount',
|
|
72
123
|
},
|
|
73
124
|
},
|
|
74
125
|
|
|
@@ -78,22 +129,38 @@ export function createQueueMachine(initialContext: QueueContext) {
|
|
|
78
129
|
// -----------------------------------------------------------------
|
|
79
130
|
idle: {
|
|
80
131
|
on: {
|
|
81
|
-
PLAY: {
|
|
132
|
+
PLAY: {
|
|
133
|
+
target: 'playing',
|
|
134
|
+
actions: ['playCurrent', 'updateMediaSessionMetadata', 'preloadAhead', 'scheduleGapless'],
|
|
135
|
+
},
|
|
82
136
|
GOTO: [
|
|
83
137
|
{
|
|
84
138
|
guard: 'playImmediately',
|
|
85
139
|
target: 'playing',
|
|
86
|
-
actions:
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
actions: [
|
|
141
|
+
'deactivateCurrent',
|
|
142
|
+
'cancelAllGapless',
|
|
143
|
+
'gotoTrackIndex',
|
|
144
|
+
'activateAndPlayCurrent',
|
|
145
|
+
'notifyStartNewTrack',
|
|
146
|
+
'updateMediaSessionMetadata',
|
|
147
|
+
'preloadAhead',
|
|
148
|
+
],
|
|
89
149
|
},
|
|
90
150
|
{
|
|
91
151
|
target: 'paused',
|
|
92
|
-
actions:
|
|
93
|
-
|
|
94
|
-
|
|
152
|
+
actions: [
|
|
153
|
+
'deactivateCurrent',
|
|
154
|
+
'cancelAllGapless',
|
|
155
|
+
'gotoTrackIndex',
|
|
156
|
+
'seekCurrentToZero',
|
|
157
|
+
'preloadAhead',
|
|
158
|
+
],
|
|
95
159
|
},
|
|
96
160
|
],
|
|
161
|
+
TRACK_LOADED: {
|
|
162
|
+
actions: ['preloadAhead'],
|
|
163
|
+
},
|
|
97
164
|
},
|
|
98
165
|
},
|
|
99
166
|
|
|
@@ -102,38 +169,86 @@ export function createQueueMachine(initialContext: QueueContext) {
|
|
|
102
169
|
// -----------------------------------------------------------------
|
|
103
170
|
playing: {
|
|
104
171
|
on: {
|
|
105
|
-
PAUSE: {
|
|
106
|
-
|
|
172
|
+
PAUSE: {
|
|
173
|
+
target: 'paused',
|
|
174
|
+
actions: ['cancelScheduledGapless', 'pauseCurrent'],
|
|
175
|
+
},
|
|
176
|
+
TOGGLE: {
|
|
177
|
+
target: 'paused',
|
|
178
|
+
actions: ['cancelScheduledGapless', 'pauseCurrent'],
|
|
179
|
+
},
|
|
107
180
|
NEXT: {
|
|
108
|
-
actions:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
181
|
+
actions: [
|
|
182
|
+
'deactivateCurrent',
|
|
183
|
+
'cancelAllGapless',
|
|
184
|
+
'advanceToNextTrack',
|
|
185
|
+
'activateAndPlayCurrent',
|
|
186
|
+
'notifyStartNewTrack',
|
|
187
|
+
'notifyPlayNextTrack',
|
|
188
|
+
'updateMediaSessionMetadata',
|
|
189
|
+
'preloadAhead',
|
|
190
|
+
],
|
|
114
191
|
},
|
|
115
192
|
PREVIOUS: {
|
|
116
|
-
actions:
|
|
117
|
-
|
|
118
|
-
|
|
193
|
+
actions: [
|
|
194
|
+
'deactivateCurrent',
|
|
195
|
+
'cancelAllGapless',
|
|
196
|
+
'goToPreviousTrack',
|
|
197
|
+
'activateAndPlayCurrent',
|
|
198
|
+
'notifyStartNewTrack',
|
|
199
|
+
'notifyPlayPreviousTrack',
|
|
200
|
+
'updateMediaSessionMetadata',
|
|
201
|
+
'preloadAhead',
|
|
202
|
+
],
|
|
119
203
|
},
|
|
120
|
-
GOTO:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
204
|
+
GOTO: [
|
|
205
|
+
{
|
|
206
|
+
guard: 'playImmediately',
|
|
207
|
+
actions: [
|
|
208
|
+
'deactivateCurrent',
|
|
209
|
+
'cancelAllGapless',
|
|
210
|
+
'gotoTrackIndex',
|
|
211
|
+
'activateAndPlayCurrent',
|
|
212
|
+
'notifyStartNewTrack',
|
|
213
|
+
'updateMediaSessionMetadata',
|
|
214
|
+
'preloadAhead',
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
actions: [
|
|
219
|
+
'deactivateCurrent',
|
|
220
|
+
'cancelAllGapless',
|
|
221
|
+
'gotoTrackIndex',
|
|
222
|
+
'seekCurrentToZero',
|
|
223
|
+
'preloadAhead',
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
SEEK: {
|
|
228
|
+
actions: ['seekCurrent', 'cancelAndRescheduleGapless'],
|
|
124
229
|
},
|
|
125
|
-
SEEK: {},
|
|
126
230
|
TRACK_ENDED: [
|
|
127
231
|
{
|
|
128
232
|
guard: 'hasNextTrack',
|
|
129
233
|
target: 'playing',
|
|
130
|
-
actions:
|
|
131
|
-
|
|
132
|
-
|
|
234
|
+
actions: [
|
|
235
|
+
'deactivateEndedTrack',
|
|
236
|
+
'advanceOnTrackEnd',
|
|
237
|
+
'playOrContinueGapless',
|
|
238
|
+
'notifyStartNewTrack',
|
|
239
|
+
'notifyPlayNextTrack',
|
|
240
|
+
'updateMediaSessionMetadata',
|
|
241
|
+
'preloadAhead',
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
target: 'ended',
|
|
246
|
+
actions: ['deactivateEndedTrack', 'notifyEnded'],
|
|
133
247
|
},
|
|
134
|
-
{ target: 'ended' },
|
|
135
248
|
],
|
|
136
|
-
TRACK_LOADED: {
|
|
249
|
+
TRACK_LOADED: {
|
|
250
|
+
actions: ['scheduleGapless', 'preloadAhead'],
|
|
251
|
+
},
|
|
137
252
|
},
|
|
138
253
|
},
|
|
139
254
|
|
|
@@ -142,46 +257,84 @@ export function createQueueMachine(initialContext: QueueContext) {
|
|
|
142
257
|
// -----------------------------------------------------------------
|
|
143
258
|
paused: {
|
|
144
259
|
on: {
|
|
145
|
-
PLAY: {
|
|
146
|
-
|
|
260
|
+
PLAY: {
|
|
261
|
+
target: 'playing',
|
|
262
|
+
actions: ['playCurrent', 'updateMediaSessionMetadata', 'preloadAhead', 'scheduleGapless'],
|
|
263
|
+
},
|
|
264
|
+
TOGGLE: {
|
|
265
|
+
target: 'playing',
|
|
266
|
+
actions: ['playCurrent', 'updateMediaSessionMetadata', 'preloadAhead', 'scheduleGapless'],
|
|
267
|
+
},
|
|
147
268
|
NEXT: {
|
|
148
|
-
actions:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
269
|
+
actions: [
|
|
270
|
+
'deactivateCurrent',
|
|
271
|
+
'cancelAllGapless',
|
|
272
|
+
'advanceToNextTrack',
|
|
273
|
+
'activateAndPlayCurrent',
|
|
274
|
+
'notifyStartNewTrack',
|
|
275
|
+
'notifyPlayNextTrack',
|
|
276
|
+
'updateMediaSessionMetadata',
|
|
277
|
+
'preloadAhead',
|
|
278
|
+
],
|
|
154
279
|
},
|
|
155
280
|
PREVIOUS: {
|
|
156
|
-
actions:
|
|
157
|
-
|
|
158
|
-
|
|
281
|
+
actions: [
|
|
282
|
+
'deactivateCurrent',
|
|
283
|
+
'cancelAllGapless',
|
|
284
|
+
'goToPreviousTrack',
|
|
285
|
+
'activateAndPlayCurrent',
|
|
286
|
+
'notifyStartNewTrack',
|
|
287
|
+
'notifyPlayPreviousTrack',
|
|
288
|
+
'updateMediaSessionMetadata',
|
|
289
|
+
'preloadAhead',
|
|
290
|
+
],
|
|
159
291
|
},
|
|
160
292
|
GOTO: [
|
|
161
293
|
{
|
|
162
294
|
guard: 'playImmediately',
|
|
163
295
|
target: 'playing',
|
|
164
|
-
actions:
|
|
165
|
-
|
|
166
|
-
|
|
296
|
+
actions: [
|
|
297
|
+
'deactivateCurrent',
|
|
298
|
+
'cancelAllGapless',
|
|
299
|
+
'gotoTrackIndex',
|
|
300
|
+
'activateAndPlayCurrent',
|
|
301
|
+
'notifyStartNewTrack',
|
|
302
|
+
'updateMediaSessionMetadata',
|
|
303
|
+
'preloadAhead',
|
|
304
|
+
],
|
|
167
305
|
},
|
|
168
306
|
{
|
|
169
|
-
actions:
|
|
170
|
-
|
|
171
|
-
|
|
307
|
+
actions: [
|
|
308
|
+
'deactivateCurrent',
|
|
309
|
+
'cancelAllGapless',
|
|
310
|
+
'gotoTrackIndex',
|
|
311
|
+
'seekCurrentToZero',
|
|
312
|
+
'preloadAhead',
|
|
313
|
+
],
|
|
172
314
|
},
|
|
173
315
|
],
|
|
174
|
-
SEEK: {
|
|
316
|
+
SEEK: {
|
|
317
|
+
actions: ['seekCurrent'],
|
|
318
|
+
},
|
|
175
319
|
TRACK_ENDED: [
|
|
176
320
|
{
|
|
177
321
|
guard: 'hasNextTrack',
|
|
178
322
|
target: 'paused',
|
|
179
|
-
actions:
|
|
180
|
-
|
|
181
|
-
|
|
323
|
+
actions: [
|
|
324
|
+
'deactivateEndedTrack',
|
|
325
|
+
'advanceOnTrackEnd',
|
|
326
|
+
'notifyStartNewTrack',
|
|
327
|
+
'updateMediaSessionMetadata',
|
|
328
|
+
],
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
target: 'ended',
|
|
332
|
+
actions: ['deactivateEndedTrack', 'notifyEnded'],
|
|
182
333
|
},
|
|
183
|
-
{ target: 'ended' },
|
|
184
334
|
],
|
|
335
|
+
TRACK_LOADED: {
|
|
336
|
+
actions: ['preloadAhead'],
|
|
337
|
+
},
|
|
185
338
|
},
|
|
186
339
|
},
|
|
187
340
|
|
|
@@ -192,21 +345,37 @@ export function createQueueMachine(initialContext: QueueContext) {
|
|
|
192
345
|
on: {
|
|
193
346
|
PLAY: {
|
|
194
347
|
target: 'playing',
|
|
195
|
-
actions:
|
|
348
|
+
actions: [
|
|
349
|
+
'resetToFirstTrack',
|
|
350
|
+
'playCurrent',
|
|
351
|
+
'updateMediaSessionMetadata',
|
|
352
|
+
'preloadAhead',
|
|
353
|
+
'scheduleGapless',
|
|
354
|
+
],
|
|
196
355
|
},
|
|
197
356
|
GOTO: [
|
|
198
357
|
{
|
|
199
358
|
guard: 'playImmediately',
|
|
200
359
|
target: 'playing',
|
|
201
|
-
actions:
|
|
202
|
-
|
|
203
|
-
|
|
360
|
+
actions: [
|
|
361
|
+
'deactivateCurrent',
|
|
362
|
+
'cancelAllGapless',
|
|
363
|
+
'gotoTrackIndex',
|
|
364
|
+
'activateAndPlayCurrent',
|
|
365
|
+
'notifyStartNewTrack',
|
|
366
|
+
'updateMediaSessionMetadata',
|
|
367
|
+
'preloadAhead',
|
|
368
|
+
],
|
|
204
369
|
},
|
|
205
370
|
{
|
|
206
371
|
target: 'paused',
|
|
207
|
-
actions:
|
|
208
|
-
|
|
209
|
-
|
|
372
|
+
actions: [
|
|
373
|
+
'deactivateCurrent',
|
|
374
|
+
'cancelAllGapless',
|
|
375
|
+
'gotoTrackIndex',
|
|
376
|
+
'seekCurrentToZero',
|
|
377
|
+
'preloadAhead',
|
|
378
|
+
],
|
|
210
379
|
},
|
|
211
380
|
],
|
|
212
381
|
},
|