@tidal-music/player-web-components 0.1.0

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.
@@ -0,0 +1,579 @@
1
+ import { _ as I, F as T, y as d, l as o, k as r, b as m, P as n, a as h, j as v, n as p, o as b, B as k, G as c, R as f, Q as A, c as g, w as P, d as y } from "./index-tM9JvbA8.js";
2
+ function L(s) {
3
+ return g(
4
+ () => P(y, {
5
+ type: "play_log",
6
+ ...s
7
+ })
8
+ );
9
+ }
10
+ function N(s, t) {
11
+ return new CustomEvent(
12
+ "media-product-transition",
13
+ {
14
+ detail: {
15
+ mediaProduct: s,
16
+ playbackContext: t
17
+ }
18
+ }
19
+ );
20
+ }
21
+ const G = ({
22
+ assetPosition: s,
23
+ duration: t,
24
+ playbackInfo: e,
25
+ streamInfo: a
26
+ }) => ({
27
+ actualAssetPresentation: e.assetPresentation,
28
+ actualAudioMode: "audioMode" in e ? e.audioMode : null,
29
+ actualAudioQuality: "audioQuality" in e ? e.audioQuality : null,
30
+ actualDuration: t,
31
+ actualProductId: String(
32
+ "videoId" in e ? e.videoId : e.trackId
33
+ ),
34
+ actualStreamType: "streamType" in e ? e.streamType : null,
35
+ actualVideoQuality: "videoQuality" in e ? e.videoQuality : null,
36
+ assetPosition: s,
37
+ bitDepth: a.bitDepth ?? null,
38
+ codec: a.codec ?? null,
39
+ playbackSessionId: a.streamingSessionId,
40
+ sampleRate: a.sampleRate ?? null
41
+ });
42
+ function M(s, t) {
43
+ return new CustomEvent("ended", {
44
+ detail: {
45
+ mediaProduct: t,
46
+ reason: s
47
+ }
48
+ });
49
+ }
50
+ function E(s) {
51
+ return new CustomEvent("playback-state-change", {
52
+ detail: {
53
+ state: s
54
+ }
55
+ });
56
+ }
57
+ function R() {
58
+ return new CustomEvent("preload-request");
59
+ }
60
+ const _ = {
61
+ playback: {
62
+ durationMS: 0,
63
+ id: "",
64
+ playedMS: 0,
65
+ source: {
66
+ id: "",
67
+ type: "playlist"
68
+ },
69
+ type: "TRACK"
70
+ }
71
+ }, w = await I(
72
+ "progress",
73
+ _
74
+ );
75
+ function C(s) {
76
+ return w(s);
77
+ }
78
+ function D(s) {
79
+ return g(
80
+ () => P(y, {
81
+ ...s,
82
+ type: "playback"
83
+ })
84
+ );
85
+ }
86
+ function S(s) {
87
+ return Math.min(Math.pow(10, (4 + s) / 20), 1 / 1);
88
+ }
89
+ function x(s) {
90
+ switch (s) {
91
+ case "completed":
92
+ return "COMPLETE";
93
+ case "error":
94
+ return "ERROR";
95
+ case "skip":
96
+ default:
97
+ return "OTHER";
98
+ }
99
+ }
100
+ class Y {
101
+ #t;
102
+ #i;
103
+ #e;
104
+ #r = void 0;
105
+ #n;
106
+ #s = "IDLE";
107
+ #o;
108
+ #d;
109
+ #a = /* @__PURE__ */ new Map();
110
+ name;
111
+ constructor() {
112
+ T.addEventListener("desiredVolumeLevel", () => {
113
+ this.isActivePlayer && this.updateVolumeLevel();
114
+ });
115
+ }
116
+ // Implements
117
+ #c() {
118
+ this.duration && Math.abs(this.#i - this.duration) <= 30 && // A false check, rather than undefined, ensures a media product transition hs been made.
119
+ this.#r === !1 && (this.#r = !0, d.dispatchEvent(R()));
120
+ }
121
+ /**
122
+ * This method should be call whenever a playback ends, for **whatever** reason.
123
+ *
124
+ * ended, completed, skip, reset etc
125
+ */
126
+ #u({
127
+ endAssetPosition: t,
128
+ endReason: e,
129
+ streamingSessionId: a
130
+ }) {
131
+ this.debugLog("mediaProductEnded"), o.preloadedStreamingSessionId && performance.mark(
132
+ "streaming_metrics:playback_statistics:idealStartTimestamp",
133
+ {
134
+ detail: o.preloadedStreamingSessionId
135
+ }
136
+ );
137
+ const i = r.getMediaProductTransition(a);
138
+ i && d.dispatchEvent(
139
+ M(e, i.mediaProduct)
140
+ ), this.eventTrackingStreamingEnded(a, {
141
+ endAssetPosition: t,
142
+ endReason: e
143
+ }), this.reportPlaybackProgress(a), r.deleteSession(a), this.#a.delete(a), this.currentStreamingSessionId === a && (this.currentStreamingSessionId = void 0), this.updateVolumeLevelForNextProduct();
144
+ }
145
+ adjustedVolume(t) {
146
+ const e = m("desiredVolumeLevel"), a = m("loudnessNormalizationMode");
147
+ let i = e;
148
+ return a === "ALBUM" && t.albumReplayGain && (i *= S(t.albumReplayGain)), a === "TRACK" && t.trackReplayGain && (i *= S(t.trackReplayGain)), this.debugLog(
149
+ "adjustedVolume",
150
+ `Volume adjusted from ${e} to ${i}`
151
+ ), parseFloat(i.toFixed(2));
152
+ }
153
+ // Implements
154
+ attachPlaybackEngineEndedHandler() {
155
+ this.#e || (this.#e = this.playbackEngineEndedHandler.bind(this), d.addEventListener(
156
+ "ended",
157
+ this.#e
158
+ ));
159
+ }
160
+ /**
161
+ * Cleans up stored stream info and media product transitions
162
+ * for preloadedStreamingSessionId if it does not match
163
+ * currentStreamingSessionId.
164
+ */
165
+ cleanUpStoredPreloadInfo() {
166
+ this.preloadedStreamingSessionId && this.preloadedStreamingSessionId !== this.currentStreamingSessionId && (r.deleteSession(this.preloadedStreamingSessionId), this.preloadedStreamingSessionId = void 0);
167
+ }
168
+ // Implements
169
+ debugLog(...t) {
170
+ document.location.href.includes("localhost") && document.location.hash.includes("debug") && console.debug(
171
+ `[%cPlayerSDK${this.name ? `%c${o.activePlayer?.name === this.name ? "⚯" : "⚮"}%c` + this.name : ""}${this.#t ? "%c::%c" + this.#t?.split("-").pop() : ""}%c]`,
172
+ "color:#00d6ff",
173
+ ...this.name ? [
174
+ "color:inherit",
175
+ "color:#b7fa34"
176
+ // green foreground
177
+ ] : [],
178
+ ...this.#t ? [
179
+ "color:inherit",
180
+ "color:#d947ff"
181
+ // purple foreground
182
+ ] : [],
183
+ "color:inherit",
184
+ ...t
185
+ );
186
+ }
187
+ detachPlaybackEngineEndedHandler() {
188
+ this.#e && (d.removeEventListener(
189
+ "ended",
190
+ this.#e
191
+ ), this.#e = void 0);
192
+ }
193
+ /**
194
+ * Commits play_log playbackSession and streaming_metrics playbackStatistics.
195
+ *
196
+ * @param streamingSessionId
197
+ */
198
+ eventTrackingStreamingEnded(t, {
199
+ endAssetPosition: e,
200
+ endReason: a
201
+ }) {
202
+ const i = n.now();
203
+ L({
204
+ events: [
205
+ h({
206
+ endAssetPosition: e,
207
+ endTimestamp: i,
208
+ streamingSessionId: t
209
+ })
210
+ ]
211
+ }).catch(console.error), v({
212
+ events: [
213
+ p({
214
+ endReason: x(a),
215
+ endTimestamp: i,
216
+ streamingSessionId: t
217
+ }),
218
+ b({
219
+ streamingSessionId: t,
220
+ timestamp: i
221
+ })
222
+ ]
223
+ }).catch(console.error);
224
+ }
225
+ eventTrackingStreamingStarted(t) {
226
+ if (!t)
227
+ return;
228
+ performance.mark(
229
+ "streaming_metrics:playback_statistics:actualStartTimestamp",
230
+ {
231
+ detail: t
232
+ }
233
+ ), performance.measure("idealStartTimestamp -> actualStartTimestamp", {
234
+ detail: t,
235
+ end: "streaming_metrics:playback_statistics:actualStartTimestamp",
236
+ start: "streaming_metrics:playback_statistics:idealStartTimestamp"
237
+ });
238
+ try {
239
+ p({
240
+ actualStartTimestamp: n.timestamp(
241
+ "streaming_metrics:playback_statistics:actualStartTimestamp",
242
+ t
243
+ ),
244
+ idealStartTimestamp: n.timestamp(
245
+ "streaming_metrics:playback_statistics:idealStartTimestamp",
246
+ t
247
+ ),
248
+ outputDevice: this.#n,
249
+ streamingSessionId: t
250
+ });
251
+ } catch (l) {
252
+ console.error(
253
+ l,
254
+ "actualStartTimestamp or idealStartTimestamp is missing for this streaming session"
255
+ );
256
+ } finally {
257
+ performance.clearMarks(
258
+ "streaming_metrics:playback_statistics:actualStartTimestamp"
259
+ ), performance.clearMarks(
260
+ "streaming_metrics:playback_statistics:idealStartTimestamp"
261
+ );
262
+ }
263
+ const e = r.getMediaProductTransition(t);
264
+ if (!e) {
265
+ this.#a.has(t) ? console.error(
266
+ `A media product transition for streaming session #${t} has not been saved and could thus not be found for play log reporting.`
267
+ ) : (r.deleteStreamInfo(t), console.warn(
268
+ `Streaming session #${t} has been discarded due to a new load. This could mean you have a bug in your code where you call load on Player more than once time in a very short time frame.`
269
+ ));
270
+ return;
271
+ }
272
+ const { mediaProduct: a, playbackContext: i } = e, u = n.now();
273
+ h({
274
+ actualAssetPresentation: i.actualAssetPresentation,
275
+ actualAudioMode: "actualAudioMode" in i ? i.actualAudioMode : null,
276
+ actualProductId: i.actualProductId,
277
+ actualQuality: i.actualAudioQuality || i.actualVideoQuality,
278
+ isPostPaywall: i.actualAssetPresentation === "FULL",
279
+ playbackSessionId: t,
280
+ productType: a.productType === "track" ? "TRACK" : "VIDEO",
281
+ requestedProductId: a.productId,
282
+ sourceId: a.sourceId,
283
+ sourceType: a.sourceType,
284
+ startAssetPosition: this.startAssetPosition,
285
+ startTimestamp: u,
286
+ streamingSessionId: t
287
+ });
288
+ }
289
+ finishCurrentMediaProduct(t) {
290
+ const e = this.#t, a = e ? r.hasStreamInfo(e) : !1;
291
+ this.preloadedStreamingSessionId || (this.playbackState = "IDLE"), e && a && this.#u({
292
+ endAssetPosition: this.currentTime,
293
+ endReason: t,
294
+ streamingSessionId: e
295
+ });
296
+ }
297
+ getPosition() {
298
+ return 0;
299
+ }
300
+ /**
301
+ * Refetches playbackinfo.
302
+ */
303
+ async hardReload(t, e) {
304
+ return k(t, e);
305
+ }
306
+ hasNextItem() {
307
+ return this.preloadedStreamingSessionId && r.hasMediaProductTransition(
308
+ this.preloadedStreamingSessionId
309
+ );
310
+ }
311
+ hasStarted() {
312
+ return this.currentStreamingSessionId && this.#a.has(this.currentStreamingSessionId);
313
+ }
314
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
315
+ load(t, e) {
316
+ return Promise.resolve();
317
+ }
318
+ /**
319
+ * If playback info is prefetched or expired, do a hard reload.
320
+ *
321
+ * @returns {boolean} True if hard reloaded, else false.
322
+ */
323
+ async maybeHardReload() {
324
+ const t = this.prefetched || this.expired;
325
+ return this.currentMediaProduct && t ? (await this.hardReload(this.currentMediaProduct, 0), !0) : !1;
326
+ }
327
+ /**
328
+ * This method should be call whenever a playback starts, for **whatever** reason.
329
+ *
330
+ * skip, load.
331
+ *
332
+ * @param streamingSessionId
333
+ */
334
+ mediaProductStarted(t) {
335
+ !t || this.#a.has(t) || (this.debugLog("mediaProductStarted"), this.eventTrackingStreamingStarted(t), this.#a.set(t, !0), this.updateVolumeLevel(), this.#r = !1, this.preloadedStreamingSessionId = void 0, this.unloadPreloadedMediaProduct().catch(console.error), this.attachPlaybackEngineEndedHandler());
336
+ }
337
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
338
+ next(t) {
339
+ return Promise.resolve();
340
+ }
341
+ /**
342
+ * When re-using a nexted item for a load, overwrite the nexted MediaProduct with the provided one.
343
+ * To ensure sourceId, sourceType and referenceId from the load call is correct for the playback -
344
+ * and not a stale incorrect one from the next call.
345
+ *
346
+ * @param streamingSessionId
347
+ * @param partialMediaProduct
348
+ */
349
+ overwriteMediaProduct(t, e) {
350
+ const a = r.getMediaProductTransition(t);
351
+ if (a) {
352
+ r.deleteMediaProductTransition(t);
353
+ const i = {
354
+ mediaProduct: {
355
+ ...a.mediaProduct,
356
+ ...e
357
+ },
358
+ playbackContext: {
359
+ ...a.playbackContext
360
+ }
361
+ };
362
+ r.saveMediaProductTransition(
363
+ t,
364
+ i
365
+ );
366
+ }
367
+ }
368
+ pause() {
369
+ }
370
+ play() {
371
+ return Promise.resolve();
372
+ }
373
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
374
+ playbackEngineEndedHandler(t) {
375
+ return Promise.resolve();
376
+ }
377
+ reportPlaybackProgress(t) {
378
+ const e = r.getMediaProductTransition(t);
379
+ if (!e)
380
+ return;
381
+ const { mediaProduct: a, playbackContext: i } = e;
382
+ this.#t && D({
383
+ events: [
384
+ C({
385
+ playback: {
386
+ durationMS: Math.floor(i.actualDuration * 1e3),
387
+ id: a.productId,
388
+ playedMS: Math.floor(this.currentTime * 1e3),
389
+ source: {
390
+ id: a.sourceId,
391
+ type: a.sourceType
392
+ },
393
+ type: a.productType === "track" ? "TRACK" : "VIDEO"
394
+ },
395
+ streamingSessionId: this.#t
396
+ })
397
+ ]
398
+ }).catch(console.error);
399
+ }
400
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
401
+ reset(t) {
402
+ return Promise.resolve();
403
+ }
404
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
405
+ seek(t) {
406
+ }
407
+ /**
408
+ * Handle play log reporting for seeking.
409
+ * Seek start should log a PLAYBACK_START action if playing post seek.
410
+ */
411
+ seekEnd() {
412
+ this.currentStreamingSessionId && this.playbackState === "PLAYING" && c(this.currentStreamingSessionId, {
413
+ actionType: "PLAYBACK_START",
414
+ assetPosition: this.currentTime,
415
+ timestamp: n.now()
416
+ });
417
+ }
418
+ /**
419
+ * Handle play log reporting for seeking.
420
+ * Seek start should log a PLAYBACK_STOP action.
421
+ */
422
+ seekStart() {
423
+ this.currentStreamingSessionId && c(this.currentStreamingSessionId, {
424
+ actionType: "PLAYBACK_STOP",
425
+ assetPosition: this.currentTime,
426
+ timestamp: n.now()
427
+ });
428
+ }
429
+ async setStateToXIfNotYInZMs(t, e, a) {
430
+ await f(t), this.playbackState !== e && (this.playbackState = a);
431
+ }
432
+ skipToPreloadedMediaProduct() {
433
+ return Promise.resolve();
434
+ }
435
+ unloadPreloadedMediaProduct() {
436
+ return Promise.resolve();
437
+ }
438
+ updateOutputDevice() {
439
+ return Promise.resolve();
440
+ }
441
+ /**
442
+ * Hydrates the volume level from config, and adjusts
443
+ * it before setting, if loudness normalization is
444
+ * enabled.
445
+ */
446
+ updateVolumeLevel() {
447
+ const t = r.getStreamInfo(
448
+ this.currentStreamingSessionId
449
+ );
450
+ t && (this.volume = this.adjustedVolume(t));
451
+ }
452
+ /**
453
+ * Adjusts the volume for the next track.
454
+ * Can be called on product ended to have the level ready.
455
+ */
456
+ updateVolumeLevelForNextProduct() {
457
+ const t = r.getStreamInfo(
458
+ this.preloadedStreamingSessionId
459
+ );
460
+ t && (this.volume = this.adjustedVolume(t));
461
+ }
462
+ get currentMediaProduct() {
463
+ return r.getMediaProductTransition(
464
+ this.currentStreamingSessionId
465
+ )?.mediaProduct ?? null;
466
+ }
467
+ set currentStreamingSessionId(t) {
468
+ this.#t = t;
469
+ }
470
+ get currentStreamingSessionId() {
471
+ return this.#t;
472
+ }
473
+ set currentTime(t) {
474
+ this.#i = t, this.#c();
475
+ }
476
+ get currentTime() {
477
+ return this.#i;
478
+ }
479
+ get duration() {
480
+ const t = r.getMediaProductTransition(
481
+ this.currentStreamingSessionId
482
+ );
483
+ return t ? t.playbackContext.actualDuration : null;
484
+ }
485
+ get expired() {
486
+ const t = r.getStreamInfo(
487
+ this.currentStreamingSessionId
488
+ );
489
+ return t ? t.expires <= Date.now() : !1;
490
+ }
491
+ get isActivePlayer() {
492
+ return o.activePlayer && this.name === o.activePlayer.name;
493
+ }
494
+ get nextItem() {
495
+ if (this.preloadedStreamingSessionId)
496
+ return r.getMediaProductTransition(
497
+ this.preloadedStreamingSessionId
498
+ );
499
+ }
500
+ // eslint-disable-next-line accessor-pairs
501
+ set outputDeviceType(t) {
502
+ this.#n = t ? A(t) : void 0;
503
+ }
504
+ set playbackState(t) {
505
+ const e = this.#s;
506
+ if (e === t || !this.currentStreamingSessionId)
507
+ return;
508
+ const a = (u, l) => e === u && l === t;
509
+ switch (!0) {
510
+ case a("NOT_PLAYING", "STALLED"):
511
+ case a("IDLE", "STALLED"):
512
+ return;
513
+ case a("PLAYING", "NOT_PLAYING"):
514
+ case a("PLAYING", "IDLE"): {
515
+ this.reportPlaybackProgress(this.currentStreamingSessionId), console.debug(
516
+ "PS",
517
+ {
518
+ from: e,
519
+ to: t
520
+ },
521
+ {
522
+ currentTime: this.#i,
523
+ doIt: this.duration && this.currentTime < this.duration,
524
+ duration: this.duration
525
+ }
526
+ ), this.duration && this.currentTime < this.duration && c(this.currentStreamingSessionId, {
527
+ actionType: "PLAYBACK_STOP",
528
+ assetPosition: this.currentTime,
529
+ timestamp: n.now()
530
+ }).catch(console.error);
531
+ break;
532
+ }
533
+ case a("IDLE", "PLAYING"):
534
+ case a("NOT_PLAYING", "PLAYING"): {
535
+ this.currentTime !== this.startAssetPosition && c(this.currentStreamingSessionId, {
536
+ actionType: "PLAYBACK_START",
537
+ assetPosition: this.currentTime,
538
+ timestamp: n.now()
539
+ }).catch(console.error);
540
+ break;
541
+ }
542
+ }
543
+ this.#s = t, this.debugLog(`playbackState: ${t}`);
544
+ const i = o.activePlayer === void 0;
545
+ (this.isActivePlayer || i) && d.dispatchEvent(E(this.#s));
546
+ }
547
+ get playbackState() {
548
+ return this.#s;
549
+ }
550
+ get prefetched() {
551
+ const t = r.getStreamInfo(
552
+ this.currentStreamingSessionId
553
+ );
554
+ return t && t.prefetched;
555
+ }
556
+ set preloadedStreamingSessionId(t) {
557
+ this.#o = t;
558
+ }
559
+ get preloadedStreamingSessionId() {
560
+ return this.#o;
561
+ }
562
+ get startAssetPosition() {
563
+ return this.#d;
564
+ }
565
+ set startAssetPosition(t) {
566
+ this.#d = t;
567
+ }
568
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
569
+ get volume() {
570
+ return 1;
571
+ }
572
+ set volume(t) {
573
+ }
574
+ }
575
+ export {
576
+ G as O,
577
+ N as V,
578
+ Y
579
+ };