bigscreen-player
Version:
Simplified media playback for bigscreen devices.
1,071 lines (869 loc) • 34.6 kB
JavaScript
import { MediaPlayer } from 'dashjs/index_mediaplayerOnly';
import { U as Utils, M as MediaKinds, b as LiveSupport, a as DebugTool, c as ManifestType, d as MediaState, P as Plugins, e as autoResumeAtStartOfRange, D as DOMHelpers } from './main-4e864739.js';
import 'tslib';
function filter(manifest, representationOptions) {
const constantFps = representationOptions.constantFps;
const maxFps = representationOptions.maxFps;
if (constantFps || maxFps) {
manifest.Period.AdaptationSet = manifest.Period.AdaptationSet.map((adaptationSet) => {
if (adaptationSet.contentType === "video") {
const frameRates = [];
adaptationSet.Representation_asArray = adaptationSet.Representation_asArray.filter((representation) => {
if (!maxFps || representation.frameRate <= maxFps) {
frameRates.push(representation.frameRate);
return true
}
}).filter((representation) => {
return !constantFps || representation.frameRate === Math.max.apply(null, frameRates)
});
}
return adaptationSet
});
}
return manifest
}
function extractBaseUrl(manifest) {
if (manifest.Period && typeof manifest.Period.BaseURL === "string") {
return manifest.Period.BaseURL
}
if (manifest.Period && manifest.Period.BaseURL && typeof manifest.Period.BaseURL.__text === "string") {
return manifest.Period.BaseURL.__text
}
if (typeof manifest.BaseURL === "string") {
return manifest.BaseURL
}
if (manifest.BaseURL && typeof manifest.BaseURL.__text === "string") {
return manifest.BaseURL.__text
}
}
function generateBaseUrls(manifest, sources) {
if (!manifest) return
const baseUrl = extractBaseUrl(manifest);
if (isBaseUrlAbsolute(baseUrl)) {
setAbsoluteBaseUrl(baseUrl);
} else {
if (baseUrl) {
setBaseUrlsFromBaseUrl(baseUrl);
} else {
setBaseUrlsFromSource();
}
}
removeUnusedPeriodAttributes();
function generateBaseUrl(source, priority, serviceLocation) {
return {
__text: source,
"dvb:priority": priority,
"dvb:weight": isNaN(source.dpw) ? 0 : source.dpw,
serviceLocation: serviceLocation,
}
}
function removeUnusedPeriodAttributes() {
if (manifest.Period && manifest.Period.BaseURL) delete manifest.Period.BaseURL;
if (manifest.Period && manifest.Period.BaseURL_asArray) delete manifest.Period.BaseURL_asArray;
}
function isBaseUrlAbsolute(baseUrl) {
return baseUrl && baseUrl.match(/^https?:\/\//)
}
function setAbsoluteBaseUrl(baseUrl) {
const newBaseUrl = generateBaseUrl(baseUrl, 0, sources[0]);
manifest.BaseURL_asArray = [newBaseUrl];
if (manifest.BaseURL || (manifest.Period && manifest.Period.BaseURL)) {
manifest.BaseURL = newBaseUrl;
}
}
function setBaseUrlsFromBaseUrl(baseUrl) {
manifest.BaseURL_asArray = sources.map((source, priority) => {
const sourceUrl = new URL(baseUrl, source);
return generateBaseUrl(sourceUrl.href, priority, source)
});
}
function setBaseUrlsFromSource() {
manifest.BaseURL_asArray = sources.map((source, priority) => {
return generateBaseUrl(source, priority, source)
});
}
}
var ManifestModifier = {
filter: filter,
extractBaseUrl: extractBaseUrl,
generateBaseUrls: generateBaseUrls,
};
function convertTimeRangesToArray(ranges) {
const array = [];
for (let rangesSoFar = 0; rangesSoFar < ranges.length; rangesSoFar += 1) {
array.push([ranges.start(rangesSoFar), ranges.end(rangesSoFar)]);
}
return array;
}
const DEFAULT_SETTINGS = {
liveDelay: 0,
seekDurationPadding: 1.1,
};
function MSEStrategy(
mediaSources,
mediaKind,
playbackElement,
_isUHD = false,
customPlayerSettings = {},
audioDescribedOpts = {}
) {
const audioDescribed = { callback: undefined, enable: false, ...audioDescribedOpts };
let mediaPlayer;
let mediaElement;
const manifestType = mediaSources.time().manifestType;
const playerSettings = Utils.merge(
{
debug: {
logLevel: 2,
},
streaming: {
blacklistExpiryTime: mediaSources.failoverResetTime(),
buffer: {
bufferToKeep: 4,
bufferTimeAtTopQuality: 12,
bufferTimeAtTopQualityLongForm: 15,
},
lastMediaSettingsCachingInfo: { enabled: false },
},
},
customPlayerSettings
);
let eventCallbacks = [];
let errorCallback;
let timeUpdateCallback;
const seekDurationPadding = isNaN(playerSettings.streaming?.seekDurationPadding)
? DEFAULT_SETTINGS.seekDurationPadding
: playerSettings.streaming?.seekDurationPadding;
const liveDelay = isNaN(playerSettings.streaming?.delay?.liveDelay)
? DEFAULT_SETTINGS.liveDelay
: playerSettings.streaming?.delay?.liveDelay;
let isEnded = false;
const cached = {
seekableRange: undefined,
duration: 0,
currentTime: 0,
};
let dashMetrics;
let lastError;
let publishedSeekEvent = false;
let isSeeking = false;
let manifestRequestTime;
let manifestLoadCount = 0;
let playerMetadata = {
downloadQuality: {
[MediaKinds.AUDIO]: undefined,
[MediaKinds.VIDEO]: undefined,
},
playbackBitrate: undefined,
bufferLength: undefined,
fragmentInfo: {
requestTime: undefined,
numDownloaded: undefined,
},
};
const DashJSEvents = {
LOG: "log",
ERROR: "error",
GAP_JUMP: "gapCausedInternalSeek",
GAP_JUMP_TO_END: "gapCausedSeekToPeriodEnd",
MANIFEST_LOADED: "manifestLoaded",
MANIFEST_LOADING_FINISHED: "manifestLoadingFinished",
DOWNLOAD_MANIFEST_ERROR_CODE: 25,
DOWNLOAD_CONTENT_ERROR_CODE: 27,
DOWNLOAD_INIT_SEGMENT_ERROR_CODE: 28,
UNSUPPORTED_CODEC: 30,
MANIFEST_VALIDITY_CHANGED: "manifestValidityChanged",
QUALITY_CHANGE_REQUESTED: "qualityChangeRequested",
QUALITY_CHANGE_RENDERED: "qualityChangeRendered",
BASE_URL_SELECTED: "baseUrlSelected",
SERVICE_LOCATION_AVAILABLE: "serviceLocationUnblacklisted",
URL_RESOLUTION_FAILED: "urlResolutionFailed",
METRIC_ADDED: "metricAdded",
METRIC_CHANGED: "metricChanged",
STREAM_INITIALIZED: "streamInitialized",
FRAGMENT_CONTENT_LENGTH_MISMATCH: "fragmentContentLengthMismatch",
QUOTA_EXCEEDED: "quotaExceeded",
CURRENT_TRACK_CHANGED: "currentTrackChanged",
};
function onLoadedMetaData() {
DebugTool.event("loadedmetadata", "MediaElement");
DebugTool.dynamicMetric("ready-state", mediaElement.readyState);
}
function onLoadedData() {
DebugTool.event("loadeddata", "MediaElement");
DebugTool.dynamicMetric("ready-state", mediaElement.readyState);
}
function onPlay() {
DebugTool.event("play", "MediaElement");
DebugTool.dynamicMetric("paused", mediaElement.paused);
}
function onPlaying() {
DebugTool.event("playing", "MediaElement");
DebugTool.dynamicMetric("ready-state", mediaElement.readyState);
getBufferedRanges().map(({ kind, buffered }) => DebugTool.buffered(kind, buffered));
isEnded = false;
publishMediaState(MediaState.PLAYING);
}
function onPaused() {
DebugTool.event("paused", "MediaElement");
DebugTool.dynamicMetric("paused", mediaElement.paused);
publishMediaState(MediaState.PAUSED);
}
function onBuffering() {
isEnded = false;
if (!isSeeking || !publishedSeekEvent) {
publishMediaState(MediaState.WAITING);
publishedSeekEvent = true;
}
}
function onSeeked() {
DebugTool.event("seeked", "MediaElement");
DebugTool.dynamicMetric("seeking", mediaElement.seeking);
isSeeking = false;
if (isPaused()) {
if (manifestType === ManifestType.DYNAMIC && isSliding()) {
startAutoResumeTimeout();
}
publishMediaState(MediaState.PAUSED);
} else {
publishMediaState(MediaState.PLAYING);
}
}
function onSeeking() {
DebugTool.event("seeking", "MediaElement");
DebugTool.dynamicMetric("seeking", mediaElement.seeking);
onBuffering();
}
function onWaiting() {
DebugTool.event("waiting", "MediaElement");
DebugTool.dynamicMetric("ready-state", mediaElement.readyState);
getBufferedRanges().map(({ kind, buffered }) => DebugTool.buffered(kind, buffered));
onBuffering();
}
function onEnded() {
DebugTool.event("ended", "MediaElement");
DebugTool.dynamicMetric("ended", mediaElement.ended);
isEnded = true;
publishMediaState(MediaState.ENDED);
}
function onRateChange() {
DebugTool.dynamicMetric("playback-rate", mediaElement.playbackRate);
}
function onTimeUpdate() {
DebugTool.updateElementTime(mediaElement.currentTime);
if (!isNaN(mediaPlayer.getCurrentLiveLatency())) {
DebugTool.staticMetric("current-latency", mediaPlayer.getCurrentLiveLatency());
DebugTool.staticMetric("target-latency", mediaPlayer.getTargetLiveDelay());
}
const currentPresentationTimeInSeconds = mediaElement.currentTime;
// Note: Multiple consecutive CDN failover logic
// A newly loaded video element will always report a 0 time update
// This is slightly unhelpful if we want to continue from a later point but consult failoverTime as the source of truth.
if (
typeof currentPresentationTimeInSeconds === "number" &&
isFinite(currentPresentationTimeInSeconds) &&
parseInt(currentPresentationTimeInSeconds) > 0
) {
cached.currentTime = currentPresentationTimeInSeconds;
}
publishTimeUpdate();
}
function onError(event) {
if (event.error && event.error.data) {
delete event.error.data;
}
if (event.error && event.error.message) {
DebugTool.error(`${event.error.message} (code: ${event.error.code})`);
lastError = event.error;
// Don't raise an error on fragment download error
if (
(event.error.code === DashJSEvents.DOWNLOAD_CONTENT_ERROR_CODE ||
event.error.code === DashJSEvents.DOWNLOAD_INIT_SEGMENT_ERROR_CODE) &&
mediaSources.availableSources().length > 1
) {
return
}
if (event.error.code === DashJSEvents.DOWNLOAD_MANIFEST_ERROR_CODE) {
manifestDownloadError(event.error);
return
}
// It is possible audio could play back even if the video codec is not supported. Resetting here prevents this.
if (event.error.code === DashJSEvents.UNSUPPORTED_CODEC) {
mediaPlayer.reset();
}
}
publishError(event.error);
}
function onGapJump({ seekTime, duration }) {
DebugTool.gap(seekTime - duration, seekTime);
}
function onQuotaExceeded(event) {
// Note: criticalBufferLevel (Total buffered ranges * 0.8) is set BEFORE this event is triggered,
// therefore it should actually be `criticalBufferLevel * 1.25` to see what the buffer size was on the device when this happened.
const bufferLevel = event.criticalBufferLevel * 1.25;
DebugTool.quotaExceeded(bufferLevel, event.quotaExceededTime);
Plugins.interface.onQuotaExceeded({ criticalBufferLevel: bufferLevel, quotaExceededTime: event.quotaExceededTime });
}
function manifestDownloadError(mediaError) {
const failoverParams = {
isBufferingTimeoutError: false,
currentTime: getCurrentTime(),
duration: getDuration(),
code: mediaError.code,
message: mediaError.message,
};
mediaSources
.failover(failoverParams)
.then(() => load())
.catch(() => publishError(mediaError));
}
function onManifestLoaded(event) {
if (event.data) {
DebugTool.info(`Manifest loaded. Duration is: ${event.data.mediaPresentationDuration}`);
let manifest = event.data;
const representationOptions = window.bigscreenPlayer.representationOptions || {};
ManifestModifier.filter(manifest, representationOptions);
ManifestModifier.generateBaseUrls(manifest, mediaSources.availableSources());
manifest = { ...manifest, manifestLoadCount, manifestRequestTime };
manifestLoadCount = 0;
emitManifestInfo(manifest);
}
}
function emitManifestInfo(manifest) {
Plugins.interface.onManifestLoaded(manifest);
}
function onManifestValidityChange(event) {
DebugTool.info(`Manifest validity changed. Duration is: ${event.newDuration}`);
if (manifestType === ManifestType.DYNAMIC) {
mediaPlayer.refreshManifest((manifest) => {
DebugTool.info(`Manifest Refreshed. Duration is: ${manifest.mediaPresentationDuration}`);
});
}
}
function onStreamInitialised() {
if (window.bigscreenPlayer?.overrides?.mseDurationOverride && manifestType === ManifestType.DYNAMIC) {
// Workaround for no setLiveSeekableRange/clearLiveSeekableRange
mediaPlayer.setMediaDuration(Number.MAX_SAFE_INTEGER);
}
if (mediaKind === MediaKinds.VIDEO) {
dispatchDownloadQualityChangeForKind(MediaKinds.VIDEO);
dispatchMaxQualityChangeForKind(MediaKinds.VIDEO);
}
dispatchMaxQualityChangeForKind(MediaKinds.AUDIO);
dispatchDownloadQualityChangeForKind(MediaKinds.AUDIO);
emitPlayerInfo();
}
function emitPlayerInfo() {
playerMetadata.playbackBitrate =
mediaKind === MediaKinds.VIDEO
? currentPlaybackBitrateInKbps(MediaKinds.VIDEO) + currentPlaybackBitrateInKbps(MediaKinds.AUDIO)
: currentPlaybackBitrateInKbps(MediaKinds.AUDIO);
Plugins.interface.onPlayerInfoUpdated({
bufferLength: playerMetadata.bufferLength,
playbackBitrate: playerMetadata.playbackBitrate,
});
}
function dispatchDownloadQualityChangeForKind(kind) {
const { qualityIndex: prevQualityIndex, bitrateInBps: prevBitrateInBps } =
playerMetadata.downloadQuality[kind] ?? {};
const qualityIndex = mediaPlayer.getQualityFor(kind);
if (prevQualityIndex === qualityIndex) {
return
}
const bitrateInBps = playbackBitrateForRepresentationIndex(qualityIndex, kind);
playerMetadata.downloadQuality[kind] = { bitrateInBps, qualityIndex };
DebugTool.dynamicMetric(`${kind}-download-quality`, [qualityIndex, bitrateInBps]);
const abrChangePart = `ABR ${kind} download quality switched`;
const switchFromPart =
prevQualityIndex == null ? "" : ` from ${prevQualityIndex} (${(prevBitrateInBps / 1000).toFixed(0)} kbps)`;
const switchToPart = ` to ${qualityIndex} (${(bitrateInBps / 1000).toFixed(0)} kbps)`;
DebugTool.info(`${abrChangePart}${switchFromPart}${switchToPart}`);
}
function dispatchMaxQualityChangeForKind(kind) {
const { qualityIndex, bitrate: bitrateInBps } = mediaPlayer.getTopBitrateInfoFor(kind);
DebugTool.dynamicMetric(`${kind}-max-quality`, [qualityIndex, bitrateInBps]);
}
function getBufferedRanges() {
if (mediaPlayer == null) {
return []
}
return mediaPlayer
.getActiveStream()
.getProcessors()
.filter((processor) => processor.getType() === "audio" || processor.getType() === "video")
.map((processor) => ({
kind: processor.getType(),
buffered: convertTimeRangesToArray(processor.getBuffer().getAllBufferRanges()),
}))
}
function currentPlaybackBitrateInKbps(mediaKind) {
const representationSwitch = mediaPlayer.getDashMetrics().getCurrentRepresentationSwitch(mediaKind);
const representation = representationSwitch ? representationSwitch.to : "";
return playbackBitrateForRepresentation(representation, mediaKind) / 1000
}
function playbackBitrateForRepresentation(representation, mediaKind) {
const repIdx = mediaPlayer.getDashAdapter().getIndexForRepresentation(representation, 0);
return playbackBitrateForRepresentationIndex(repIdx, mediaKind)
}
function playbackBitrateForRepresentationIndex(index, mediaKind) {
if (index === -1) return 0
const bitrateInfoList = mediaPlayer.getBitrateInfoListFor(mediaKind);
return bitrateInfoList[index].bitrate ?? 0
}
function onQualityChangeRequested(event) {
Plugins.interface.onQualityChangeRequested(event);
}
function onQualityChangeRendered(event) {
if (
event.newQuality !== undefined &&
(event.mediaType === MediaKinds.AUDIO || event.mediaType === MediaKinds.VIDEO)
) {
const { mediaType, newQuality } = event;
DebugTool.dynamicMetric(`${mediaType}-playback-quality`, [
newQuality,
playbackBitrateForRepresentationIndex(newQuality, mediaType),
]);
dispatchMaxQualityChangeForKind(mediaType);
}
emitPlayerInfo();
Plugins.interface.onQualityChangedRendered(event);
}
/**
* Base url selected events are fired from dash.js whenever a priority weighted url is selected from a manifest
* Note: we ignore the initial selection as it isn't a failover.
* @param {*} event
*/
function onBaseUrlSelected(event) {
const failoverInfo = {
isBufferingTimeoutError: false,
code: lastError && lastError.code,
message: lastError && lastError.message,
};
function log() {
DebugTool.info(`BaseUrl selected: ${event.baseUrl.url}`);
lastError = undefined;
}
failoverInfo.serviceLocation = event.baseUrl.serviceLocation;
mediaSources.failover(failoverInfo).then(
() => log(),
() => log()
);
}
function onServiceLocationAvailable(event) {
DebugTool.info(`Service Location available: ${event.entry}`);
}
function onURLResolutionFailed() {
DebugTool.info("URL Resolution failed");
}
function onMetricAdded(event) {
if (event.mediaType === "video" && event.metric === "DroppedFrames") {
DebugTool.staticMetric("frames-dropped", event.value.droppedFrames);
}
if (event.mediaType === mediaKind && event.metric === "BufferLevel") {
dashMetrics = mediaPlayer.getDashMetrics();
if (dashMetrics) {
playerMetadata.bufferLength = dashMetrics.getCurrentBufferLevel(event.mediaType);
DebugTool.staticMetric("buffer-length", playerMetadata.bufferLength);
Plugins.interface.onPlayerInfoUpdated({
bufferLength: playerMetadata.bufferLength,
playbackBitrate: playerMetadata.playbackBitrate,
});
}
}
if (
event.metric === "RepSwitchList" &&
(event.mediaType === MediaKinds.AUDIO || event.mediaType === MediaKinds.VIDEO)
) {
const { mediaType } = event;
dispatchDownloadQualityChangeForKind(mediaType);
dispatchMaxQualityChangeForKind(mediaType);
}
}
function onDebugLog(event) {
DebugTool.debug(event.message);
}
function onFragmentContentLengthMismatch(event) {
DebugTool.info(`Fragment Content Length Mismatch: ${event.responseUrl} (${event.mediaType})`);
DebugTool.info(`Header Length ${event.headerLength}`);
DebugTool.info(`Body Length ${event.bodyLength})`);
Plugins.interface.onFragmentContentLengthMismatch(event);
}
function onCurrentTrackChanged(event) {
if (!isAudioDescribedAvailable()) return
const mediaType = event.newMediaInfo.type;
DebugTool.info(
`${mediaType} track changed.${
mediaType === "audio" ? (isAudioDescribedEnabled() ? " Audio Described on." : " Audio Described off.") : ""
}`
);
audioDescribed.callback && audioDescribed.callback(isAudioDescribedEnabled());
}
function publishMediaState(mediaState) {
for (let index = 0; index < eventCallbacks.length; index++) {
eventCallbacks[index](mediaState);
}
}
function publishTimeUpdate() {
if (timeUpdateCallback) {
timeUpdateCallback();
}
}
function publishError(mediaError) {
if (errorCallback) {
errorCallback(mediaError);
}
}
function isPaused() {
return mediaPlayer && mediaPlayer.isReady() ? mediaPlayer.isPaused() : undefined
}
function load(mimeType, presentationTimeInSeconds) {
if (mediaPlayer) {
modifySource(cached.currentTime);
} else {
if (typeof presentationTimeInSeconds === "number" && isFinite(presentationTimeInSeconds)) {
cached.currentTime = presentationTimeInSeconds;
}
setUpMediaElement(playbackElement);
setUpMediaPlayer(presentationTimeInSeconds);
setUpMediaListeners();
}
}
function setUpMediaElement(playbackElement) {
mediaElement = mediaKind === MediaKinds.AUDIO ? document.createElement("audio") : document.createElement("video");
mediaElement.style.position = "absolute";
mediaElement.style.width = "100%";
mediaElement.style.height = "100%";
playbackElement.insertBefore(mediaElement, playbackElement.firstChild);
}
function getDashSettings(playerSettings) {
const settings = Utils.deepClone(playerSettings);
// BSP Specific Settings
delete settings.failoverResetTime;
delete settings.failoverSort;
delete settings.streaming?.seekDurationPadding;
return settings
}
function setUpMediaPlayer(presentationTimeInSeconds) {
const dashSettings = getDashSettings(playerSettings);
mediaPlayer = MediaPlayer().create();
mediaPlayer.updateSettings(dashSettings);
mediaPlayer.initialize(mediaElement, null);
mediaPlayer.setInitialMediaSettingsFor(
"audio",
audioDescribed.enable
? {
role: "alternate",
accessibility: { schemeIdUri: "urn:tva:metadata:cs:AudioPurposeCS:2007", value: "1" },
}
: {
role: "main",
}
);
modifySource(presentationTimeInSeconds);
}
function modifySource(presentationTimeInSeconds) {
const source = mediaSources.currentSource();
const anchor = buildSourceAnchor(presentationTimeInSeconds);
mediaPlayer.attachSource(`${source}${anchor}`);
}
/**
* Calculate time anchor tag for playback within dashjs
*
* Anchor tags applied to the MPD source for playback:
*
* #t=<time> - Seeks MPD timeline. By itself it means time since the beginning of the first period defined in the DASH manifest
*
* @param {number} presentationTimeInSeconds
* @returns {string}
*/
function buildSourceAnchor(presentationTimeInSeconds) {
if (typeof presentationTimeInSeconds !== "number" || !isFinite(presentationTimeInSeconds)) {
return ""
}
const wholeSeconds = parseInt(presentationTimeInSeconds);
return `#t=${wholeSeconds}`
}
function setUpMediaListeners() {
DebugTool.dynamicMetric("ended", mediaElement.ended);
DebugTool.dynamicMetric("paused", mediaElement.paused);
DebugTool.dynamicMetric("playback-rate", mediaElement.playbackRate);
DebugTool.dynamicMetric("ready-state", mediaElement.readyState);
DebugTool.dynamicMetric("seeking", mediaElement.seeking);
mediaElement.addEventListener("timeupdate", onTimeUpdate);
mediaElement.addEventListener("loadedmetadata", onLoadedMetaData);
mediaElement.addEventListener("loadeddata", onLoadedData);
mediaElement.addEventListener("play", onPlay);
mediaElement.addEventListener("playing", onPlaying);
mediaElement.addEventListener("pause", onPaused);
mediaElement.addEventListener("waiting", onWaiting);
mediaElement.addEventListener("seeking", onSeeking);
mediaElement.addEventListener("seeked", onSeeked);
mediaElement.addEventListener("ended", onEnded);
mediaElement.addEventListener("ratechange", onRateChange);
mediaPlayer.on(DashJSEvents.ERROR, onError);
mediaPlayer.on(DashJSEvents.MANIFEST_LOADED, onManifestLoaded);
mediaPlayer.on(DashJSEvents.STREAM_INITIALIZED, onStreamInitialised);
mediaPlayer.on(DashJSEvents.MANIFEST_VALIDITY_CHANGED, onManifestValidityChange);
mediaPlayer.on(DashJSEvents.QUALITY_CHANGE_REQUESTED, onQualityChangeRequested);
mediaPlayer.on(DashJSEvents.QUALITY_CHANGE_RENDERED, onQualityChangeRendered);
mediaPlayer.on(DashJSEvents.BASE_URL_SELECTED, onBaseUrlSelected);
mediaPlayer.on(DashJSEvents.METRIC_ADDED, onMetricAdded);
mediaPlayer.on(DashJSEvents.LOG, onDebugLog);
mediaPlayer.on(DashJSEvents.SERVICE_LOCATION_AVAILABLE, onServiceLocationAvailable);
mediaPlayer.on(DashJSEvents.URL_RESOLUTION_FAILED, onURLResolutionFailed);
mediaPlayer.on(DashJSEvents.FRAGMENT_CONTENT_LENGTH_MISMATCH, onFragmentContentLengthMismatch);
mediaPlayer.on(DashJSEvents.GAP_JUMP, onGapJump);
mediaPlayer.on(DashJSEvents.GAP_JUMP_TO_END, onGapJump);
mediaPlayer.on(DashJSEvents.QUOTA_EXCEEDED, onQuotaExceeded);
mediaPlayer.on(DashJSEvents.MANIFEST_LOADING_FINISHED, manifestLoadingFinished);
mediaPlayer.on(DashJSEvents.CURRENT_TRACK_CHANGED, onCurrentTrackChanged);
}
function manifestLoadingFinished(event) {
manifestLoadCount++;
manifestRequestTime = event.request.requestEndDate.getTime() - event.request.requestStartDate.getTime();
}
function getSeekableRange() {
if (manifestType === ManifestType.STATIC || !mediaPlayer?.isReady()) {
return cached.seekableRange || { start: 0, end: getDuration() }
}
const dvrInfo = mediaPlayer.getDashMetrics().getCurrentDVRInfo(mediaKind);
// FIX: Dash.js briefly returns `null` on a failover for the first time update
if (dvrInfo) {
const seekableRange = { start: dvrInfo.range.start, end: dvrInfo.range.end };
// Save good seekable range value
cached.seekableRange = Utils.clone(seekableRange);
return { start: seekableRange.start, end: seekableRange.end - liveDelay }
}
return cached.seekableRange
? { ...cached.seekableRange, end: cached.seekableRange.end - liveDelay }
: { start: 0, end: getDuration() }
}
function getDuration() {
const duration = mediaPlayer && mediaPlayer.isReady() && mediaPlayer.duration();
// If duration is a number, return that, else return cached value (default 0)
if (typeof duration === "number" && isFinite(duration)) {
cached.duration = duration;
return duration
}
return cached.duration
}
function getCurrentTime() {
const currentTime = mediaElement?.currentTime;
if (typeof currentTime === "number" && isFinite(currentTime)) {
cached.currentTime = currentTime;
return currentTime
}
return cached.currentTime
}
function refreshManifestBeforeSeek(presentationTimeInSeconds) {
if (typeof presentationTimeInSeconds === "number" && isFinite(presentationTimeInSeconds)) {
cached.currentTime = presentationTimeInSeconds;
}
mediaPlayer.refreshManifest((manifest) => {
const mediaPresentationDuration = manifest?.mediaPresentationDuration;
if (typeof mediaPresentationDuration === "number" && isFinite(mediaPresentationDuration)) {
DebugTool.info(`Stream ended`);
}
const dvrTimeInSeconds = convertPresentationTimeToDVRTime(
presentationTimeInSeconds > mediaPresentationDuration
? clampPresentationTimeToSafeRange(mediaPresentationDuration)
: presentationTimeInSeconds
);
mediaPlayer.seek(dvrTimeInSeconds);
});
}
function addEventCallback(thisArg, newCallback) {
const eventCallback = (event) => newCallback.call(thisArg, event);
eventCallbacks.push(eventCallback);
}
function removeEventCallback(callback) {
const index = eventCallbacks.indexOf(callback);
if (index !== -1) {
eventCallbacks.splice(index, 1);
}
}
function isSliding() {
const { timeShiftBufferDepthInMilliseconds } = mediaSources.time();
return (
typeof timeShiftBufferDepthInMilliseconds === "number" &&
isFinite(timeShiftBufferDepthInMilliseconds) &&
timeShiftBufferDepthInMilliseconds > 0
)
}
function startAutoResumeTimeout() {
autoResumeAtStartOfRange(
getCurrentTime(),
getSafelySeekableRange(),
addEventCallback,
removeEventCallback,
(event) => event !== MediaState.PAUSED,
mediaPlayer.play,
mediaSources.time().timeShiftBufferDepthInMilliseconds / 1000
);
}
function isTrackAudioDescribed(track) {
return (
track.roles.includes("alternate") &&
track.accessibilitiesWithSchemeIdUri.some(
(scheme) => scheme.schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && scheme.value === "1"
)
)
}
function getAudioDescribedTrack() {
const audioTracks = mediaPlayer.getTracksFor("audio");
return audioTracks.find((track) => isTrackAudioDescribed(track))
}
function isAudioDescribedAvailable() {
const audioTracks = mediaPlayer.getTracksFor("audio");
return audioTracks.some((track) => isTrackAudioDescribed(track))
}
function isAudioDescribedEnabled() {
const currentAudioTrack = mediaPlayer.getCurrentTrackFor("audio");
return currentAudioTrack ? isTrackAudioDescribed(currentAudioTrack) : false
}
function setAudioDescribedOff() {
const audioTracks = mediaPlayer.getTracksFor("audio");
const mainTrack = audioTracks.find((track) => track.roles.includes("main"));
mediaPlayer.setCurrentTrack(mainTrack);
if (isPaused()) mediaPlayer.play();
}
function setAudioDescribedOn() {
const ADTrack = getAudioDescribedTrack();
if (ADTrack) {
mediaPlayer.setCurrentTrack(ADTrack);
if (isPaused()) mediaPlayer.play();
}
}
function cleanUpMediaPlayer() {
if (mediaPlayer) {
mediaPlayer.destroy();
mediaPlayer.off(DashJSEvents.ERROR, onError);
mediaPlayer.off(DashJSEvents.MANIFEST_LOADED, onManifestLoaded);
mediaPlayer.off(DashJSEvents.MANIFEST_VALIDITY_CHANGED, onManifestValidityChange);
mediaPlayer.off(DashJSEvents.STREAM_INITIALIZED, onStreamInitialised);
mediaPlayer.off(DashJSEvents.QUALITY_CHANGE_RENDERED, onQualityChangeRendered);
mediaPlayer.off(DashJSEvents.QUALITY_CHANGE_REQUESTED, onQualityChangeRequested);
mediaPlayer.off(DashJSEvents.METRIC_ADDED, onMetricAdded);
mediaPlayer.off(DashJSEvents.BASE_URL_SELECTED, onBaseUrlSelected);
mediaPlayer.off(DashJSEvents.LOG, onDebugLog);
mediaPlayer.off(DashJSEvents.SERVICE_LOCATION_AVAILABLE, onServiceLocationAvailable);
mediaPlayer.off(DashJSEvents.URL_RESOLUTION_FAILED, onURLResolutionFailed);
mediaPlayer.off(DashJSEvents.GAP_JUMP, onGapJump);
mediaPlayer.off(DashJSEvents.GAP_JUMP_TO_END, onGapJump);
mediaPlayer.off(DashJSEvents.QUOTA_EXCEEDED, onQuotaExceeded);
mediaPlayer.off(DashJSEvents.CURRENT_TRACK_CHANGED, onCurrentTrackChanged);
mediaPlayer = undefined;
}
if (mediaElement) {
mediaElement.removeEventListener("timeupdate", onTimeUpdate);
mediaElement.removeEventListener("loadedmetadata", onLoadedMetaData);
mediaElement.removeEventListener("loadeddata", onLoadedData);
mediaElement.removeEventListener("play", onPlay);
mediaElement.removeEventListener("playing", onPlaying);
mediaElement.removeEventListener("pause", onPaused);
mediaElement.removeEventListener("waiting", onWaiting);
mediaElement.removeEventListener("seeking", onSeeking);
mediaElement.removeEventListener("seeked", onSeeked);
mediaElement.removeEventListener("ended", onEnded);
mediaElement.removeEventListener("ratechange", onRateChange);
DOMHelpers.safeRemoveElement(mediaElement);
mediaElement = undefined;
}
}
function getSafelySeekableRange() {
if (manifestType === ManifestType.STATIC || !mediaPlayer?.isReady()) {
return cached.seekableRange || { start: 0, end: getDuration() - seekDurationPadding }
}
const dvrInfo = mediaPlayer.getDashMetrics().getCurrentDVRInfo(mediaKind);
// FIX: Dash.js briefly returns `null` on a failover for the first time update
if (dvrInfo) {
const seekableRange = { start: dvrInfo.range.start, end: dvrInfo.range.end };
// Save good seekable range value
cached.seekableRange = Utils.clone(seekableRange);
return { start: seekableRange.start, end: seekableRange.end - seekDurationPadding }
}
return cached.seekableRange
? { ...cached.seekableRange, end: cached.seekableRange.end - seekDurationPadding }
: { start: 0, end: getDuration() - seekDurationPadding }
}
function clampPresentationTimeToSafeRange(presentationTimeInSeconds) {
const { start, end } = getSafelySeekableRange();
return Math.min(Math.max(presentationTimeInSeconds, start), end)
}
function convertPresentationTimeToDVRTime(presentationTimeInSeconds) {
const { start } = getSafelySeekableRange();
return presentationTimeInSeconds - start
}
function setCurrentTime(presentationTimeInSeconds) {
publishedSeekEvent = false;
isSeeking = true;
const safePresentationTime = clampPresentationTimeToSafeRange(presentationTimeInSeconds);
if (manifestType === ManifestType.DYNAMIC && safePresentationTime > getCurrentTime()) {
refreshManifestBeforeSeek(safePresentationTime);
} else {
const dvrTimeInSeconds = convertPresentationTimeToDVRTime(safePresentationTime);
mediaPlayer.seek(dvrTimeInSeconds);
}
}
function pause() {
mediaPlayer.pause();
if (manifestType === ManifestType.DYNAMIC && isSliding()) {
startAutoResumeTimeout();
}
}
function tearDown() {
cleanUpMediaPlayer();
lastError = undefined;
eventCallbacks = [];
errorCallback = undefined;
timeUpdateCallback = undefined;
isEnded = undefined;
dashMetrics = undefined;
playerMetadata = {
playbackBitrate: undefined,
bufferLength: undefined,
fragmentInfo: {
requestTime: undefined,
numDownloaded: undefined,
},
};
}
return {
transitions: {
canBePaused: () => true,
canBeginSeek: () => true,
},
addEventCallback,
removeEventCallback,
addErrorCallback: (thisArg, newErrorCallback) => {
errorCallback = (event) => newErrorCallback.call(thisArg, event);
},
addTimeUpdateCallback: (thisArg, newTimeUpdateCallback) => {
timeUpdateCallback = () => newTimeUpdateCallback.call(thisArg);
},
load,
getSeekableRange,
getCurrentTime,
isAudioDescribedAvailable,
isAudioDescribedEnabled,
setAudioDescribedOn,
setAudioDescribedOff,
getDuration,
getPlayerElement: () => mediaElement,
tearDown,
reset: () => {
if (window.bigscreenPlayer?.overrides?.resetMSEPlayer) {
cleanUpMediaPlayer();
}
},
isEnded: () => isEnded,
isPaused,
pause,
play: () => mediaPlayer.play(),
setCurrentTime,
setPlaybackRate: (rate) => mediaPlayer.setPlaybackRate(rate),
getPlaybackRate: () => mediaPlayer.getPlaybackRate(),
}
}
MSEStrategy.getLiveSupport = () => LiveSupport.SEEKABLE;
export { MSEStrategy as default };