@tidal-music/player
Version:
Player logic for TIDAL
260 lines (259 loc) • 8.82 kB
JavaScript
import { B as f, c as g, m as p } from "./basePlayer-DD7cIuDm.js";
import { d as P, m as y, s as c, e as S, g as L, w as I } from "./index-C6ZwgBzI.js";
class b extends f {
#t;
#r;
#i = !0;
#o;
#e;
#s = document.createElement("video");
#a = !1;
name = "browserPlayer";
constructor() {
super(), this.playbackState = "IDLE";
const e = () => {
this.mediaElement && !this.mediaElement.paused && (this.playbackState = "PLAYING");
}, i = (s) => {
this.currentStreamingSessionId && s.target instanceof HTMLMediaElement && c.overwriteDuration(
this.currentStreamingSessionId,
s.target.duration
);
}, t = () => {
this.playbackState = "STALLED";
}, a = () => {
(async () => {
const s = this.#t;
if (s) {
if (this.preloadedStreamingSessionId && s.currentTime === s.duration && (await I(1e3), s.currentTime !== s.duration))
return;
this.playbackState = "NOT_PLAYING";
}
})().catch(console.error);
}, n = (s) => {
if (s instanceof Event) {
const l = s.target;
l.readyState > HTMLMediaElement.HAVE_NOTHING && (this.currentTime = l.currentTime);
}
}, o = (s) => {
n(s), this.finishCurrentMediaProduct("completed");
}, r = (s) => console.error("HTMLMediaElement errored", s), d = () => {
this.mediaElement && (this.currentTime = this.mediaElement.currentTime, this.seekEnd(this.currentTime));
};
this.#e = {
durationChangeHandler: i,
endedHandler: o,
errorHandler: r,
pauseHandler: a,
playHandler: e,
playingHandler: e,
seekedHandler: d,
stalledHandler: t,
timeUpdateHandler: n,
waitingHandler: t
}, P().then().catch(console.error), this.#r = y, this.currentPlayer = this.#r;
}
#n(e, i) {
this.debugLog(
"mediaElementEvents",
i ? "adding to" : "removing from",
e
);
const t = i ? "addEventListener" : "removeEventListener";
e[t](
"durationchange",
this.#e.durationChangeHandler,
{
passive: !0
}
), e[t]("play", this.#e.playHandler, {
passive: !0
}), e[t](
"playing",
this.#e.playingHandler,
{ passive: !0 }
), e[t](
"timeupdate",
this.#e.timeUpdateHandler,
{ passive: !0 }
), e[t](
"pause",
this.#e.pauseHandler,
{ passive: !0 }
), e[t](
"ended",
this.#e.endedHandler,
{ passive: !0 }
), e[t](
"error",
this.#e.errorHandler,
{ passive: !0 }
), e[t](
"waiting",
this.#e.waitingHandler,
{ passive: !0 }
), e[t](
"stalled",
this.#e.stalledHandler,
{ passive: !0 }
), e[t](
"seeked",
this.#e.seekedHandler,
{ passive: !0 }
);
}
getPosition() {
return this.debugLog("getPosition"), this.mediaElement && (this.mediaElement.ended ? this.currentTime = 0 : this.currentTime = this.mediaElement.currentTime), this.currentTime;
}
async load(e, i) {
this.debugLog("load", e), this.currentTime = e.assetPosition, this.startAssetPosition = e.assetPosition, await P(), await this.reset(), this.#i = !1, i === "explicit" && (this.playbackState = "NOT_PLAYING");
const { assetPosition: t, mediaProduct: a, playbackInfo: n, streamInfo: o } = e;
this.currentStreamingSessionId = o.streamingSessionId;
const { currentPlayer: r } = this;
if (!r)
return;
const d = new Promise(
(h) => r.addEventListener("canplay", () => h(), {
once: !0
})
);
r.src = o.streamUrl, r.currentTime = t, r.load();
const s = new Promise(
(h) => r.addEventListener(
"durationchange",
(m) => {
m.target instanceof HTMLMediaElement && h(m.target.duration);
},
{ once: !0 }
)
);
if (await d, this.currentStreamingSessionId !== o.streamingSessionId)
return;
const l = await s, u = g({
assetPosition: t,
duration: l,
playbackInfo: n,
streamInfo: o
});
return c.saveMediaProductTransition(
o.streamingSessionId,
{ mediaProduct: a, playbackContext: u }
), this.debugLog("dispatching mediaProductTransition"), S.dispatchEvent(
p(a, u)
), this.#a ? (this.#a = !1, this.play()) : Promise.resolve();
}
async next(e) {
this.debugLog("next", e), this.playbackState === "IDLE" && (this.playbackState = "NOT_PLAYING");
const { mediaProduct: i, playbackInfo: t, streamInfo: a } = e, n = this.#s;
if (this.preloadedStreamingSessionId = a.streamingSessionId, !n)
return;
n.src = a.streamUrl, n.load();
const o = await new Promise(
(d) => n.addEventListener(
"durationchange",
(s) => {
s.target instanceof HTMLMediaElement && d(s.target.duration);
},
{ once: !0 }
)
), r = g({
assetPosition: 0,
duration: o,
playbackInfo: t,
streamInfo: a
});
c.saveMediaProductTransition(
a.streamingSessionId,
{
mediaProduct: i,
playbackContext: r
}
), this.#i = !1;
}
pause() {
this.debugLog("pause"), this.mediaElement && this.mediaElement.pause();
}
async play() {
if (this.debugLog("play"), await this.maybeHardReload(), this.playbackState === "IDLE")
return this.debugLog("is IDLE, returning early"), this.#a = !0, Promise.resolve();
"setSinkId" in y && await this.updateOutputDevice(), this.currentStreamingSessionId && this.mediaProductStarted(this.currentStreamingSessionId), this.setStateToXIfNotYInZMs(1e3, "PLAYING", "STALLED"), this.currentPlayer && await this.currentPlayer.play().catch(console.error);
}
async playbackEngineEndedHandler(e) {
if (this.isActivePlayer) {
const { reason: i } = e.detail;
i === "completed" && (this.hasNextItem() ? (await this.skipToPreloadedMediaProduct(), await this.play()) : (console.warn("No item preloaded, not progressing."), this.playbackState = "NOT_PLAYING"));
}
}
async reset({ keepPreload: e } = { keepPreload: !1 }) {
if (this.#i)
return Promise.resolve();
this.debugLog("reset"), this.playbackState !== "IDLE" && this.finishCurrentMediaProduct("skip"), this.playbackState = "IDLE", this.detachPlaybackEngineEndedHandler(), this.currentStreamingSessionId = void 0, e || (this.preloadedStreamingSessionId = void 0);
const { currentPlayer: i } = this;
return i && i.readyState !== 0 && (i.load(), await new Promise(
(t) => i.addEventListener("emptied", () => t(), { passive: !0 })
)), this.#i = !0, Promise.resolve();
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
seek(e) {
this.debugLog("seek", e);
const { currentPlayer: i } = this, t = e;
if (i)
return this.seekStart(this.currentTime), "fastSeek" in i ? i.fastSeek(t) : i.currentTime = t, new Promise((a) => {
i.addEventListener("seeked", () => a(i.currentTime), {
once: !0
});
});
}
// eslint-disable-next-line @typescript-eslint/require-await
async skipToPreloadedMediaProduct() {
const e = c.getMediaProductTransition(
this.preloadedStreamingSessionId
);
if (!e) {
this.playbackState = "NOT_PLAYING";
return;
}
if (this.debugLog(
"skipToPreloadedMediaProduct",
this.preloadedStreamingSessionId
), this.#s.src && this.currentPlayer) {
this.currentPlayer.src = this.#s.src, this.currentStreamingSessionId = String(this.preloadedStreamingSessionId), this.preloadedStreamingSessionId = void 0;
const { mediaProduct: i, playbackContext: t } = e;
S.dispatchEvent(
p(i, t)
), this.playbackState === "IDLE" && (this.playbackState = "NOT_PLAYING");
}
}
togglePlayback() {
this.debugLog("togglePlayback"), this.mediaElement && (this.mediaElement.paused ? this.mediaElement.play().catch(console.error) : this.mediaElement.pause());
}
// eslint-disable-next-line @typescript-eslint/require-await
async unloadPreloadedMediaProduct() {
this.debugLog(
"unloadPreloadedMediaProduct",
this.preloadedStreamingSessionId
), this.cleanUpStoredPreloadInfo();
const e = this.#s;
e && (e.src = "", e.load());
}
get currentPlayer() {
return this.#t;
}
set currentPlayer(e) {
this.debugLog("set currentPlayer", e), this.#t && (this.#n(this.#t, !1), this.#t.load()), e && (this.#t = e, this.#n(e, !0));
}
get mediaElement() {
return this.currentPlayer ?? null;
}
get ready() {
return this.#o;
}
get volume() {
return L("desiredVolumeLevel");
}
set volume(e) {
this.debugLog("Setting volume to", e), this.currentPlayer && (this.currentPlayer.volume = e);
}
}
export {
b as default
};