UNPKG

@tidal-music/player

Version:
323 lines (322 loc) 12.4 kB
import { g as v, e as n, P as u, p as E, o as p, s as c, a as g, b as S, c as P } from "./index-CmV5XX7g.js"; import { B as L, m as f, c as y } from "./basePlayer-C99zz0ed.js"; const k = "active-device-disconnected"; function I() { return new CustomEvent(k); } const N = { file_checksum_mismatch: "NPO02", no_such_file: "NPO01", unreadable_file: "NPO03" }, w = { devicedisconnected: "NPD01", deviceexclusivemodenotallowed: "NPD02", deviceformatnotsupported: "NPD03", devicelocked: "NPD04", devicenotfound: "NPD05", deviceunknownerror: "NPD00" }; let a; class C extends L { #s = "default"; /** * A Boolean which is true if the media contained in the element has finished playing. * * (Native player sends multiple "complete" events. * This variable should be set to true on the first call * to be able to ignore subsequent onces; until reset * for a new media product.) * * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended */ #i; #e; #a; name = "nativePlayer"; playbackEngineHandlerAttached = !1; constructor() { super(), v("outputDevicesEnabled") && (async () => (a = (await import("./output-devices-CMDZU3db.js")).outputDevices, this.#e.listDevices()))(), this.#e = // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore window.NativePlayerComponent.Player(), this.playbackState = "IDLE", this.registerEventListeners(), this.#e.setVolume(100); } #t(e) { n.dispatchError( new u("EUnexpected", w[e]) ); } #r(e) { this.debugLog("handleMediaError", e.target); const t = e.target, i = N[t.errorCode]; this.currentStreamingSessionId && E({ errorCode: i, errorMessage: JSON.stringify(e.target), streamingSessionId: this.currentStreamingSessionId }), n.dispatchError(new u("EUnexpected", i)); } #n(e) { switch (this.debugLog("handleNativePlayerStateChange", e), e) { case "active": this.playbackState = "PLAYING"; break; case "idle": case "seeking": this.playbackState = "STALLED"; break; case "paused": case "ready": this.playbackState = "NOT_PLAYING"; break; case "stopped": this.playbackState = "NOT_PLAYING"; break; case "uninitialized": this.playbackState = "IDLE"; break; default: this.debugLog("No handling for state", e); break; } } async #d() { this.debugLog("handleNetworkError"); const e = p.timestamp( "streaming_metrics:playback_statistics:actualStartTimestamp" ); if ((e !== void 0 ? Math.abs(p.now() - e) : 0) >= 36e5) { const d = structuredClone(this.currentMediaProduct), r = this.currentTime; this.finishCurrentMediaProduct("error"), d && (await this.hardReload(d, r), await this.play()); return; } await Promise.race([ this.mediaStateChange("idle"), new Promise((d) => { window.addEventListener("online", () => d("online")); }) ]) === "idle" && n.dispatchError(new u("PENetwork", "NPN01")); } /** * Clean up native player before leaving for another player. */ abandon() { a?.deviceMode === "exclusive" && this.#e.selectSystemDevice(); } getPosition() { return this.currentTime; } /** * We cannot run multiple instances of native player so this function is * for catching duration for a preloaded item in native player. * * I.e. wait for player to load it and emit mediaduration event, then we * can gather the duration data and send a media product transition. */ async handleAutomaticTransitionToPreloadedMediaProduct() { await this.nativeEvent("mediaduration"), this.#a = void 0; const e = c.getMediaProductTransition( this.preloadedStreamingSessionId ); if (!e) { console.warn( "No media product transition saved for next item. Stopping playback." ), this.playbackState = "NOT_PLAYING"; return; } const { mediaProduct: t, playbackContext: i } = e, s = { ...i, actualDuration: this.#i }; this.preloadedStreamingSessionId && c.saveMediaProductTransition( this.preloadedStreamingSessionId, { mediaProduct: t, playbackContext: s } ), await this.mediaStateChange("active"), n.dispatchEvent( f(t, s) ), this.currentStreamingSessionId = this.preloadedStreamingSessionId, this.mediaProductStarted(this.currentStreamingSessionId); } async load(e, t) { this.debugLog("load", e), this.currentTime = e.assetPosition, this.startAssetPosition = e.assetPosition, await this.reset(); const { assetPosition: i, mediaProduct: s, playbackInfo: d, streamInfo: r } = e, { securityToken: h, streamFormat: o, streamUrl: l } = r; this.currentStreamingSessionId = r.streamingSessionId, t === "explicit" && (this.playbackState = "NOT_PLAYING"); const b = this.nativeEvent("mediaduration"); if (o) this.#e.load(l, o, h); else throw new Error("Stream format is undefined."); if (await b, this.currentStreamingSessionId !== r.streamingSessionId) return; this.debugLog("load() duration is", this.#i), i !== 0 && i < this.#i ? (async () => { await this.mediaStateChange("active"), this.currentStreamingSessionId === r.streamingSessionId && (await this.seek(i), this.currentTime = i); })().catch(console.error) : this.currentTime = 0; const m = y({ assetPosition: i, duration: this.#i, playbackInfo: d, streamInfo: r }); c.saveMediaProductTransition( r.streamingSessionId, { mediaProduct: s, playbackContext: m } ), this.debugLog("load() mediaProductTransition"), n.dispatchEvent( f(s, m) ), this.debugLog("load() pb NOT_PLAYING"), this.debugLog("load() done"); } mediaStateChange(e) { return new Promise((t) => { const i = (s) => { s.target === e && (this.#e.removeEventListener("mediastate", i), t(s.target)); }; this.#e.addEventListener("mediastate", i); }); } nativeEvent(e) { return new Promise((t) => { const i = (s) => { this.#e.removeEventListener(e, i), t(s); }; this.#e.addEventListener(e, i); }); } async next(e) { this.debugLog("next", e), this.hasNextItem() && await this.unloadPreloadedMediaProduct(); const { mediaProduct: t, playbackInfo: i, streamInfo: s } = e, { securityToken: d, streamFormat: r, streamUrl: h, streamingSessionId: o } = s; this.preloadedStreamingSessionId = o, this.debugLog("preloading", h, "for", o), r ? (this.#e.preload(h, r, d), this.isActivePlayer || this.#e.pause()) : console.error("Stream format undefined for preload."), this.debugLog("preloading done"); const l = y({ assetPosition: 0, duration: 0, // TODO: Cannot get duration here, try to solve in some other way... playbackInfo: i, streamInfo: s }); c.saveMediaProductTransition(o, { mediaProduct: t, playbackContext: l }), this.#a = e; } pause() { this.#e.pause(); } async play() { if (this.debugLog("play"), await this.maybeHardReload(), this.playbackState === "IDLE") { this.debugLog("play()", this.playbackState, "returning early"); return; } this.setStateToXIfNotYInZMs(1e3, "PLAYING", "STALLED"), await this.updateOutputDevice(), this.mediaProductStarted(this.currentStreamingSessionId), this.debugLog("nativePlayer", "play()"), this.#e.play(); } async playbackEngineEndedHandler(e) { if (this.isActivePlayer) { const { reason: t } = e.detail; t === "completed" && (this.hasNextItem() ? await this.handleAutomaticTransitionToPreloadedMediaProduct() : (g.preloadedStreamingSessionId ? this.debugLog( `Switching player from ${this.name} to ${g.preloadPlayer?.name}` ) : this.debugLog("No next item queued."), this.playbackState = "NOT_PLAYING")); } } get ready() { return Promise.resolve(); } registerEventListeners() { this.debugLog("registerEventListeners"), this.#e.addEventListener("mediacurrenttime", (e) => { this.currentTime = Number(e.target); }), this.#e.addEventListener( "mediastate", (e) => { e.target === "completed" ? this.finishCurrentMediaProduct("completed") : this.#n(e.target); } ), this.#e.addEventListener( "devices", (e) => { a ? a.addNativeDevices(e.target) : console.error("Output devices not loaded."); } ), this.#e.addEventListener("devicedisconnected", () => { n.dispatchEvent(I()), this.#t("devicedisconnected"); }), this.#e.addEventListener( "deviceexclusivemodenotallowed", () => this.#t("deviceexclusivemodenotallowed") ), this.#e.addEventListener( "deviceformatnotsupported", () => this.#t("deviceformatnotsupported") ), this.#e.addEventListener( "devicelocked", () => this.#t("devicelocked") ), this.#e.addEventListener( "devicenotfound", () => this.#t("devicenotfound") ), this.#e.addEventListener( "deviceunknownerror", () => this.#t("deviceunknownerror") ), this.#e.addEventListener( "mediaduration", (e) => { this.#i = Number(e.target); } ), this.#e.addEventListener( "mediaerror", (e) => this.#r(e) ), this.#e.addEventListener("mediamaxconnectionsreached", () => { this.#d().catch(console.error); }); } async reset({ keepPreload: e } = { keepPreload: !1 }) { this.currentStreamingSessionId !== void 0 && (this.debugLog("reset"), e || await this.unloadPreloadedMediaProduct(), this.#e.stop(), this.playbackState !== "IDLE" && this.finishCurrentMediaProduct("skip"), this.detachPlaybackEngineEndedHandler(), this.currentStreamingSessionId = void 0, e || (this.preloadedStreamingSessionId = void 0), this.playbackState = "IDLE"); } // eslint-disable-next-line @typescript-eslint/no-misused-promises async seek(e) { this.hasStarted() || await this.mediaStateChange("active"), this.seekStart(this.currentTime), this.currentTime = e, this.#e.seek(e), this.seekEnd(this.currentTime); } // Handles track "skip next" and progressions between shaka and native player async skipToPreloadedMediaProduct() { this.debugLog( "skipToPreloadedMediaProduct", this.preloadedStreamingSessionId ); const e = this.currentStreamingSessionId === void 0; if (this.preloadedStreamingSessionId && this.#a) { const t = c.getMediaProductTransition( this.preloadedStreamingSessionId ); t && (this.#a.mediaProduct = t.mediaProduct), await this.load(this.#a, "implicit"), await this.updateOutputDevice(), e && (this.playbackState = "PLAYING"), this.playbackState === "IDLE" && (this.playbackState = "NOT_PLAYING"); return; } console.warn("No preloaded item in native player."); } // eslint-disable-next-line @typescript-eslint/require-await async unloadPreloadedMediaProduct() { this.debugLog( "unloadPreloadedMediaProduct", this.preloadedStreamingSessionId ), this.hasNextItem() && (this.cleanUpStoredPreloadInfo(), "cancelPreload" in this.#e ? this.#e.cancelPreload() : console.warn("cancelPreload not available. Update native player.")); } updateDeviceMode() { this.updateOutputDevice()?.catch(console.error), a && n.dispatchEvent( S(a.deviceMode) ); } updateOutputDevice() { if (!a || (this.debugLog("updateOutputDevice", a.activeDevice), !a.activeDevice)) return Promise.resolve(); const { nativeDeviceId: e } = a.activeDevice; if (this.outputDeviceType = a.activeDevice.type, e === "default") this.#s !== "default" && (this.#e.selectSystemDevice(), n.dispatchEvent(P("default")), this.#s = "default"); else if (e) { const t = a.getNativeDevice(e); t && (this.#e.selectDevice(t, a.deviceMode), n.dispatchEvent( P(a.activeDevice.id) ), n.dispatchEvent( S(a.deviceMode) ), this.#s = e); } else throw new Error(`Device with sinkId ${e} not found.`); return Promise.resolve(); } get volume() { return v("desiredVolumeLevel"); } set volume(e) { this.debugLog("Setting volume to", e), this.#e.setVolume(e * 100); } } export { C as default }; //# sourceMappingURL=nativePlayer-VeywcFxB.js.map