UNPKG

@tidal-music/player

Version:
531 lines (530 loc) 15.1 kB
import { l as P, e as o, a as d, o as n, s as r, g as m, n as g, q as h, j as y, p, r as T, t as f, u as v, v as b, x as A, y as c, w as k } from "./index-CmV5XX7g.js"; function w(s, e) { return new CustomEvent( "media-product-transition", { detail: { mediaProduct: s, playbackContext: e } } ); } const R = ({ assetPosition: s, duration: e, playbackInfo: t, streamInfo: a }) => ({ actualAssetPresentation: t.assetPresentation, actualAudioMode: "audioMode" in t ? t.audioMode : null, actualAudioQuality: "audioQuality" in t ? t.audioQuality : null, actualDuration: e, actualProductId: String( "videoId" in t ? t.videoId : t.trackId ), actualStreamType: "streamType" in t ? t.streamType : null, actualVideoQuality: "videoQuality" in t ? t.videoQuality : null, assetPosition: s, bandwidth: null, bitDepth: a.bitDepth ?? null, codec: a.codec ?? null, playbackSessionId: a.streamingSessionId, previewReason: t.previewReason ?? void 0, sampleRate: a.sampleRate ?? null }); function I(s, e) { return new CustomEvent("ended", { detail: { mediaProduct: e, reason: s } }); } function E(s) { return new CustomEvent("playback-state-change", { detail: { state: s } }); } function L() { return new CustomEvent("preload-request"); } function S(s) { return Math.min(Math.pow(10, (4 + s) / 20), 1 / 1); } function M(s) { switch (s) { case "completed": return "COMPLETE"; case "error": return "ERROR"; default: return "OTHER"; } } class x { #e; #i; #t; #r = void 0; #s; #a = "IDLE"; #n; #o; name; constructor() { P.addEventListener("desiredVolumeLevel", () => { this.isActivePlayer && this.updateVolumeLevel(); }); } // Implements #d() { this.duration && Math.abs(this.#i - this.duration) <= 30 && // A false check, rather than undefined, ensures a media product transition hs been made. this.#r === !1 && (this.#r = !0, o.dispatchEvent(L())); } /** * This method should be call whenever a playback ends, for **whatever** reason. * * ended, completed, skip, reset etc */ #c({ endAssetPosition: e, endReason: t, streamingSessionId: a }) { this.debugLog("mediaProductEnded"), d.preloadedStreamingSessionId && performance.mark( "streaming_metrics:playback_statistics:idealStartTimestamp", { detail: d.preloadedStreamingSessionId, startTime: n.now() } ); const i = r.getMediaProductTransition(a); i && o.dispatchEvent( I(t, i.mediaProduct) ), this.eventTrackingStreamingEnded(a, { endAssetPosition: e, endReason: t }), r.deleteSession(a), this.currentStreamingSessionId === a && (this.currentStreamingSessionId = void 0), this.updateVolumeLevelForNextProduct(); } adjustedVolume(e) { const t = m("desiredVolumeLevel"), a = m("loudnessNormalizationMode"); let i = t; return a === "ALBUM" && e.albumReplayGain && (i *= S(e.albumReplayGain)), a === "TRACK" && e.trackReplayGain && (i *= S(e.trackReplayGain)), this.debugLog( "adjustedVolume", `Volume adjusted from ${t} to ${i}` ), parseFloat(i.toFixed(2)); } // Implements attachPlaybackEngineEndedHandler() { this.#t || (this.#t = this.playbackEngineEndedHandler.bind(this), o.addEventListener( "ended", this.#t )); } /** * Cleans up stored stream info and media product transitions * for preloadedStreamingSessionId if it does not match * currentStreamingSessionId. */ cleanUpStoredPreloadInfo() { this.preloadedStreamingSessionId && this.preloadedStreamingSessionId !== this.currentStreamingSessionId && (r.deleteSession(this.preloadedStreamingSessionId), this.preloadedStreamingSessionId = void 0); } get currentMediaProduct() { return r.getMediaProductTransition( this.currentStreamingSessionId )?.mediaProduct ?? null; } set currentStreamingSessionId(e) { this.#e = e; } get currentStreamingSessionId() { return this.#e; } set currentTime(e) { this.#i = e, this.#d(); } get currentTime() { return this.#i; } // Implements debugLog(...e) { document.location.href.includes("localhost") && document.location.hash.includes("debug") && console.debug( `[%cPlayerSDK${this.name ? `%c${d.activePlayer?.name === this.name ? "⚯" : "⚮"}%c` + this.name : ""}${this.#e ? "%c::%c" + this.#e?.split("-").pop() : ""}%c]`, "color:#00d6ff", ...this.name ? [ "color:inherit", "color:#b7fa34" // green foreground ] : [], ...this.#e ? [ "color:inherit", "color:#d947ff" // purple foreground ] : [], "color:inherit", ...e ); } detachPlaybackEngineEndedHandler() { this.#t && (o.removeEventListener( "ended", this.#t ), this.#t = void 0); } get duration() { const e = r.getMediaProductTransition( this.currentStreamingSessionId ); return e ? e.playbackContext.actualDuration : null; } /** * Commits play_log playbackSession and streaming_metrics playbackStatistics. * * @param streamingSessionId */ eventTrackingStreamingEnded(e, { endAssetPosition: t, endReason: a }) { const i = n.now(); g([ h({ endAssetPosition: t, endTimestamp: i, streamingSessionId: e }) ]).catch(console.error), y([ p({ endReason: M(a), endTimestamp: i, streamingSessionId: e }), T({ streamingSessionId: e, timestamp: i }) ]).catch(console.error); } eventTrackingStreamingStarted(e) { if (!e) return; performance.mark( "streaming_metrics:playback_statistics:actualStartTimestamp", { detail: e, startTime: n.now() } ), performance.measure("idealStartTimestamp -> actualStartTimestamp", { detail: e, end: "streaming_metrics:playback_statistics:actualStartTimestamp", start: "streaming_metrics:playback_statistics:idealStartTimestamp" }); try { p({ actualStartTimestamp: n.timestamp( "streaming_metrics:playback_statistics:actualStartTimestamp", e ), idealStartTimestamp: n.timestamp( "streaming_metrics:playback_statistics:idealStartTimestamp", e ), outputDevice: this.#s, streamingSessionId: e }); } catch (l) { console.error( l, "actualStartTimestamp or idealStartTimestamp is missing for this streaming session" ); } finally { performance.clearMarks( "streaming_metrics:playback_statistics:actualStartTimestamp" ), performance.clearMarks( "streaming_metrics:playback_statistics:idealStartTimestamp" ); } const t = r.getMediaProductTransition(e); if (!t) { r.hasStartedStreamInfo(e) ? console.error( `A media product transition for streaming session #${e} has not been saved and could thus not be found for play log reporting.` ) : (r.deleteStreamInfo(e), console.warn( `Streaming session #${e} 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.` )); return; } const { mediaProduct: a, playbackContext: i } = t, u = n.now(); h({ actualAssetPresentation: i.actualAssetPresentation, actualAudioMode: "actualAudioMode" in i ? i.actualAudioMode : null, actualProductId: i.actualProductId, actualQuality: i.actualAudioQuality || i.actualVideoQuality, extras: a.extras, isPostPaywall: v( i.actualAssetPresentation, a ), playbackSessionId: e, productType: f( a.productType ), requestedProductId: a.productId, sourceId: a.sourceId, sourceType: a.sourceType, startAssetPosition: this.startAssetPosition, startTimestamp: u, streamingSessionId: e }).catch(console.error); } get expired() { const e = r.getStreamInfo( this.currentStreamingSessionId ); return e ? e.expires <= Date.now() : !1; } finishCurrentMediaProduct(e) { if (!this.hasStarted()) return; const t = this.#e, a = t ? r.hasStreamInfo(t) : !1; this.preloadedStreamingSessionId || (this.playbackState = "IDLE"), t && a && this.#c({ endAssetPosition: this.currentTime, endReason: e, streamingSessionId: t }); } getPosition() { return 0; } /** * Refetches playbackinfo. */ async hardReload(e, t) { return this.currentStreamingSessionId && this.finishCurrentMediaProduct("skip"), b(e, t); } hasNextItem() { return this.preloadedStreamingSessionId; } hasStarted() { return this.currentStreamingSessionId && r.hasStartedStreamInfo(this.currentStreamingSessionId); } get isActivePlayer() { return d.activePlayer && this.name === d.activePlayer.name; } // eslint-disable-next-line @typescript-eslint/no-unused-vars load(e, t) { return Promise.resolve(); } /** * If playback info is prefetched or expired, do a hard reload. * * @returns {boolean} True if hard reloaded, else false. */ async maybeHardReload() { const e = this.prefetched || this.expired; return this.currentMediaProduct && e ? (await this.hardReload(this.currentMediaProduct, this.currentTime), !0) : !1; } /** * This method should be call whenever a playback starts, for **whatever** reason. * * skip, load. * * @param streamingSessionId */ mediaProductStarted(e) { !e || r.hasStartedStreamInfo(e) || (this.debugLog("mediaProductStarted"), this.eventTrackingStreamingStarted(e), r.setStartedStreamInfo(e), this.updateVolumeLevel(), this.#r = !1, this.preloadedStreamingSessionId = void 0, this.unloadPreloadedMediaProduct().catch(console.error), this.attachPlaybackEngineEndedHandler()); } // eslint-disable-next-line @typescript-eslint/no-unused-vars next(e) { return Promise.resolve(); } get nextItem() { if (this.preloadedStreamingSessionId) return r.getMediaProductTransition( this.preloadedStreamingSessionId ); } set outputDeviceType(e) { this.#s = e ? A(e) : void 0; } /** * When re-using a nexted item for a load, overwrite the nexted MediaProduct with the provided one. * To ensure sourceId, sourceType and referenceId from the load call is correct for the playback - * and not a stale incorrect one from the next call. * * @param streamingSessionId * @param partialMediaProduct */ overwriteMediaProduct(e, t) { const a = r.getMediaProductTransition(e); if (a) { r.deleteMediaProductTransition(e); const i = { mediaProduct: { ...a.mediaProduct, ...t }, playbackContext: { ...a.playbackContext } }; r.saveMediaProductTransition( e, i ); } } pause() { } play() { return Promise.resolve(); } // eslint-disable-next-line @typescript-eslint/no-unused-vars playbackEngineEndedHandler(e) { return Promise.resolve(); } set playbackState(e) { const t = this.#a; if (t === e || !this.currentStreamingSessionId) return; const a = (u, l) => t === u && l === e; switch (!0) { case a("NOT_PLAYING", "STALLED"): case a("IDLE", "STALLED"): return; case a("PLAYING", "NOT_PLAYING"): case a("PLAYING", "IDLE"): { this.duration && this.currentTime < this.duration && c(this.currentStreamingSessionId, { actionType: "PLAYBACK_STOP", assetPosition: this.currentTime, timestamp: n.now() }).catch(console.error); break; } case a("IDLE", "PLAYING"): case a("NOT_PLAYING", "PLAYING"): { this.currentTime !== this.startAssetPosition && c(this.currentStreamingSessionId, { actionType: "PLAYBACK_START", assetPosition: this.currentTime, timestamp: n.now() }).catch(console.error); break; } } this.#a = e, this.debugLog(`playbackState: ${e}`); const i = d.activePlayer === void 0; (this.isActivePlayer || i) && o.dispatchEvent(E(this.#a)); } get playbackState() { return this.#a; } get prefetched() { return r.getStreamInfo( this.currentStreamingSessionId )?.prefetched; } set preloadedStreamingSessionId(e) { this.#n = e; } get preloadedStreamingSessionId() { return this.#n; } // eslint-disable-next-line @typescript-eslint/no-unused-vars reset(e) { return Promise.resolve(); } // eslint-disable-next-line @typescript-eslint/no-unused-vars seek(e) { } /** * Handle play log reporting for seeking. * Seek start should log a PLAYBACK_START action if playing post seek. */ seekEnd(e) { const t = this.currentStreamingSessionId; if (t) { const a = () => c(t, { actionType: "PLAYBACK_START", assetPosition: e, timestamp: n.now() }); if (this.playbackState === "PLAYING") a().catch(console.error); else { const i = () => { this.playbackState === "PLAYING" && (a().catch(console.error), o.removeEventListener( "playback-state-change", i )); }; o.addEventListener( "playback-state-change", i ); } } } /** * Handle play log reporting for seeking. * Seek start should log a PLAYBACK_STOP action. */ seekStart(e) { this.currentStreamingSessionId && c(this.currentStreamingSessionId, { actionType: "PLAYBACK_STOP", assetPosition: e, timestamp: n.now() }).catch(console.error); } async setStateToXIfNotYInZMs(e, t, a) { await k(e), this.playbackState !== t && (this.playbackState = a); } skipToPreloadedMediaProduct() { return Promise.resolve(); } get startAssetPosition() { return this.#o; } set startAssetPosition(e) { this.#o = e; } unloadPreloadedMediaProduct() { return Promise.resolve(); } updateOutputDevice() { return Promise.resolve(); } /** * Hydrates the volume level from config, and adjusts * it before setting, if loudness normalization is * enabled. */ updateVolumeLevel() { const e = r.getStreamInfo( this.currentStreamingSessionId ); e && (this.volume = this.adjustedVolume(e)); } /** * Adjusts the volume for the next track. * Can be called on product ended to have the level ready. */ updateVolumeLevelForNextProduct() { const e = r.getStreamInfo( this.preloadedStreamingSessionId ); e && (this.volume = this.adjustedVolume(e)); } get volume() { return 1; } set volume(e) { } } export { x as B, R as c, w as m }; //# sourceMappingURL=basePlayer-C99zz0ed.js.map