import ExtendedMediaError from '../util/ExtendedMediaError.js';
let DashJs = window.dashjs,
dash5 = DashJs?.Version.startsWith('5.');
/**
* The Dash component integrates dash.js into the player's plugin architecture, allowing DASH streaming with optional DRM (Widevine/PlayReady).
* Supports Subtitles, Quality and Language selection.
* @exports module:src/streaming/Dash
* @requires src/util/ExtendedMediaError
* @author Frank Kudermann - alphanull
* @version 1.0.0
* @license MIT
*/
export default class Dash {
/**
* The dash configuration, containing debug and optional DRM details.
* @type {Object}
* @property {boolean} [lazyLoadLib=true] If `true`, the Dash.js library is only loaded when loading the first media item.
* @property {string} [libUrl] Custom URL for the Dash.js library. Defaults to CDN URL if not specified.
* @property {Object|boolean} [debug=false] Debug settings or boolean to enable debug logs.
* @property {boolean} [debug.enabled] Whether debugging is enabled.
* @property {boolean} [debug.drm] Whether to log DRM-specific events if dash protection is present.
* @property {string} [debug.level] DashJs debug level (e.g., LOG_LEVEL_WARNING).
*/
#config = {
lazyLoadLib: true,
libUrl: 'https://cdn.jsdelivr.net/npm/dashjs@4.7.4/dist/dash.all.min.js',
debug: {
enabled: false,
drm: false,
level: 'LOG_LEVEL_WARNING'
}
};
/**
* Reference to the main player instance.
* @type {module:src/core/Player}
*/
#player;
/**
* Subscriptions to various player events that we handle specifically for dash.
* @type {number[]}
*/
#subscriptions;
/**
* Secret key only known to the player instance and initialized components.
* Used to be able to restrict access to API methods in conjunction with secure mode.
* @type {symbol}
*/
#apiKey;
/**
* Reference to the engine (Video) instance.
* @type {module:src/core/Media}
*/
#mediaComponent;
/**
* Reference to the Dash MediaPlayer Instance.
* @type {Object}
*/
#dash;
/**
* Holds currently available audio and video tracks.
* @type {Object}
*/
#availableTracks;
/**
* Reference to the Async Task instance. Used to handle async tasks, which can be cancelled, resolved or rejected.
* @type {module:lib/util/AsyncTask}
*/
#loadTask;
/**
* Holds metadata information provided by media.load and loaded metadata.
* @type {module:src/core/Media~metaData}
*/
#metaData;
/**
* Promise awaiting the loading of the dash lib.
* @type {Promise}
*/
#loadDashPromise;
/**
* Creates an instance of the Dash plugin.
* @param {module:src/core/Player} player Reference to the player instance.
* @param {module:src/core/Media} mediaComponent Reference to the engine (video) instance.
* @param {symbol} apiKey Token for extended access to the player API.
*/
constructor(player, mediaComponent, { apiKey }) {
this.#config = player.initConfig('dash', this.#config);
if (!this.#config) return [false];
this.#player = player;
this.#apiKey = apiKey;
DashJs = window.dashjs;
if (!DashJs && !this.#config.lazyLoadLib) this.#loadDashJs();
// Add 'mpd' format to the recognized formats list
this.#player.constructor.addFormat({
extensions: ['mpd'],
mimeTypeAudio: ['application/dash+xml'],
mimeTypeVideo: ['application/dash+xml']
});
this.#player.subscribe('data/ready', this.#removeDash);
this.#mediaComponent = mediaComponent;
this.#mediaComponent.registerPlugin(this); // Register dash plugin to the video component
}
/**
* Tells whether this plugin can handle the given mimeType and optional DRM system.
* @param {module:src/core/Media~metaData} metaData The data to test.
* @param {string} metaData.mimeType The mime type to test.
* @param {string} [metaData.drmSystem] Optional DRM system info.
* @returns {'probably'|'maybe'|''} Indicates if stream can be played.
*/
canPlay({ mimeType, drmSystem } = {}) {
const result = mimeType === 'application/dash+xml' ? 'maybe' : '';
switch (drmSystem) {
case 'Widevine':
return !(this.#player.getClient('edge') || this.#player.getClient('safari')) && result ? result : '';
case 'PlayReady':
return this.#player.getClient('edge') ? result : '';
default:
return result;
}
}
/**
* Initializes the dash.js MediaPlayer with the given source.
* @param {module:src/core/Media~metaData} metaData Source Object (currentSource).
* @param {string} metaData.src Actual HLS URL to load.
* @param {Object} [options] Additional options.
* @param {module:lib/util/AsyncTask} [options.loadTask] If a source task instance is provided, handle errors using this object.
* @returns {Promise|undefined} If a source task instance is provided, returns a promise that rejects with a resulting media error.
* @throws {Error} If drm scheme is unknown.
*/
async load({ src }, options = {}) {
if (!DashJs) {
await this.#loadDashJs();
if (!DashJs) DashJs = window.dashjs;
dash5 = DashJs?.Version.startsWith('5.');
}
this.#dash = DashJs.MediaPlayer().create();
this.#dash.updateSettings({
streaming: {
abr: {
autoSwitchBitrate: { audio: true, video: true },
limitBitrateByPortal: true,
usePixelRatioInLimitBitrateByPortal: true
},
text: {
defaultEnabled: true,
dispatchForManualRendering: true
},
buffer: {
fastSwitchEnabled: true
}
}
});
this.#dash.initialize(this.#player.media.getElement(this.#apiKey), src, this.#player.getConfig('media.autoPlay'));
if (this.#config.debug === true || this.#config.debug?.enabled) {
// If debug is enabled, attach all dash events for logging
if (this.#config.debug.level) {
this.#dash.updateSettings({
debug: {
// LOG_LEVEL_NONE, LOG_LEVEL_FATAL, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_INFO or LOG_LEVEL_DEBUG
logLevel: DashJs.Debug[this.#config.debug.level]
}
});
}
Object.values(DashJs.MediaPlayer.events).forEach(value => this.#dash.on(value, this.#onDashEvent));
if (DashJs.Protection) {
Dash.#dashProtectionEventNames.forEach(name => this.#dash.on(DashJs.Protection.events[name], this.#onProtectionEvent));
}
}
const { drm } = this.#player.data.getMediaData();
if (DashJs.Protection && drm && (drm.Widevine || drm.PlayReady)) {
this.#dash.on(DashJs.Protection.events.INTERNAL_KEY_STATUS_CHANGED, this.#onDrmKeyStatus);
const drmConfig = {};
if (drm.Widevine && !this.#player.getClient('edge')) {
drmConfig['com.widevine.alpha'] = {
serverURL: drm.Widevine.licenseUrl,
httpRequestHeaders: drm.Widevine.header,
priority: 0
};
}
if (drm.PlayReady && this.#player.getClient('edge')) {
drmConfig['com.microsoft.playready'] = {
serverURL: drm.PlayReady.licenseUrl,
httpRequestHeaders: drm.PlayReady.header,
priority: 1
};
}
this.#dash.setProtectionData(drmConfig);
this.#dash.getProtectionController().setRobustnessLevel('SW_SECURE_CRYPTO');
}
this.#dash.on(DashJs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, this.#onQualityChangeRendered);
this.#dash.on(DashJs.MediaPlayer.events.TRACK_CHANGE_RENDERED, this.#onTrackChangeRendered);
this.#dash.on(DashJs.MediaPlayer.events.PLAYBACK_METADATA_LOADED, this.#onMetaDataLoaded);
this.#dash.on(DashJs.MediaPlayer.events.ERROR, this.#onDashError);
if (options.loadTask) {
this.#loadTask = options.loadTask;
return options.loadTask.promise;
}
}
/**
* Loads dash.js via CDN if not present.
* Ensures only one load attempt per instance.
* @returns {Promise<void>}
*/
#loadDashJs() {
if (window.dashjs) return Promise.resolve();
if (this.#loadDashPromise) return this.#loadDashPromise;
this.#loadDashPromise = new Promise((resolve, reject) => {
// Falls schon ein Script-Tag existiert (z.B. durch anderen Player), warte auf dessen Load
const existing = document.querySelector(`script[src="${this.#config.libUrl}"]`);
if (existing) {
if (existing.dataset.loaded) {
resolve();
} else {
existing.addEventListener('load', resolve, { once: true });
existing.addEventListener('error', reject, { once: true });
}
return;
}
const script = document.createElement('script');
script.src = this.#config.libUrl;
script.async = true;
script.dataset.loaded = '';
script.onload = () => {
script.dataset.loaded = '1';
resolve();
};
script.onerror = error => {
this.#loadDashPromise = null;
script.remove();
this.#player.dom.getElement(this.#apiKey).classList.add('has-no-media');
this.#player.publish('data/nomedia', this.#apiKey);
this.#player.publish('notification', {
type: 'error',
title: this.#player.locale.t('errors.library.header'),
message: this.#player.locale.t('errors.library.dashLoadError')
}, this.#apiKey);
reject(new Error('[VisionPlayer] Failed to load dash.js', { cause: error }));
};
document.head.appendChild(script);
});
return this.#loadDashPromise;
}
/**
* Called by the media component when metadata has loaded, but before the `media/ready` event has been sent.
* Allows the plugin to add additional metadata to the mediaSource object.
* @param {module:src/core/Media~metaData} metaData The updated meta data object.
*/
onLoaded(metaData) {
this.#metaData = metaData;
this.#availableTracks = {
audio: this.#dash.getTracksFor('audio'),
video: this.#dash.getTracksFor('video')
};
const idx = this.#dash.getQualityFor(metaData.mediaType), // current video index
qualities = dash5 ? this.#dash?.getRepresentationsByType(metaData.mediaType) : this.#dash?.getBitrateInfoListFor(metaData.mediaType),
currentQuality = qualities[idx], // current representations object
currentAudioTrack = this.#dash.getCurrentTrackFor('audio'),
qualityDataRaw = qualities.map(bitRate => bitRate.height),
qualityData = [...new Set(qualityDataRaw)], // filter duplicates
currentHeight = dash5 ? this.#dash.getCurrentRepresentationForType('video').height : this.#dash.getVideoElement().videoHeight,
languagesRaw = this.#availableTracks.audio.map(track => ({ language: track.lang })), // get available languages
uniqueLanguages = [],
seen = new Set();
qualityData.unshift(null);
this.#player.publish('quality/update', {
qualityData,
current: {
value: currentHeight
}
}, this.#apiKey);
for (const entry of languagesRaw) {
if (!seen.has(entry.language)) {
seen.add(entry.language);
uniqueLanguages.push(entry);
}
}
if (uniqueLanguages.length) {
this.#player.publish('language/update', {
languages: uniqueLanguages,
current: { value: currentAudioTrack.lang }
}, this.#apiKey);
}
const duration = this.#dash.duration(),
isLive = this.#dash.isDynamic();
this.#player.media.getElement(this.#apiKey).dataset.isLive = isLive ? 'true' : 'false';
metaData.isLive = isLive;
metaData.frameRate = currentQuality.frameRate;
metaData.bitRate = currentQuality.bitrate;
metaData.language = currentAudioTrack.lang;
metaData.duration = duration === Infinity ? 0 : duration;
}
/**
* Handler for dash.js PLAYBACK_METADATA_LOADED event. Sets up track lists, languages, qualities.
* @fires module:src/settings/Quality#quality/update
* @fires module:src/settings/Language#language/update
* @fires module:src/text/Subtitles#subtitles/update
* @listens dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED
*/
#onMetaDataLoaded = () => {
this.#player.publish('subtitles/update', this.#apiKey);
this.#subscriptions = [
this.#player.subscribe('subtitles/selected', this.#onSubtitlesSelected),
this.#player.subscribe('language/selected', this.#onLanguageSelected),
this.#player.subscribe('quality/selected', this.#onQualitySelected),
this.#player.subscribe('quality/resize', this.#onQualityResize)
];
};
/**
* Handler for 'subtitles/selected' event. Matches the track to dash, calls dash.setTextTrack.
* @param {Object} selected Object containing information about the selected Subtitle.
* @param {string} selected.language The newly selected subtitle language code.
* @listens module:src/text/Subtitles#subtitles/selected
*/
#onSubtitlesSelected = selected => {
// find matching subtitle
const trackList = Array.from(this.#player.media.getElement(this.#apiKey).textTracks),
trackIndex = trackList.findIndex(track => track.language === selected.language);
this.#dash.setTextTrack(trackIndex);
};
/**
* Handler for 'language/selected' event. Chooses the matching audio track in dash.
* @param {module:src/settings/Language~langObj} langObj Object containing selected language information.
* @param {string} langObj.lang The newly selected audio language code.
* @listens module:src/settings/Language#language/selected
*/
#onLanguageSelected = langObj => {
const allAudioTracks = this.#dash.getTracksFor('audio') || [],
candidates = allAudioTracks.filter(track => track && track.lang === langObj.value),
currentTrack = this.#dash.getCurrentTrackFor('audio');
if (!candidates.length) return;
// Try to determine the current bitrate using the current audio track's bitrateList
let currentBitrate = null;
if (currentTrack && Array.isArray(currentTrack.bitrateList)) {
const currentQualityIdx = this.#dash.getQualityFor('audio');
if (typeof currentQualityIdx === 'number' && currentTrack.bitrateList[currentQualityIdx]) {
currentBitrate = currentTrack.bitrateList[currentQualityIdx].bandwidth;
}
}
if (typeof currentBitrate !== 'number') return;
// Find the candidate track and bitrate index with the closest bandwidth to the current bitrate
let best = { track: candidates[0], bitrateIdx: 0, diff: Infinity };
candidates.forEach(track => {
if (!Array.isArray(track.bitrateList) || !track.bitrateList.length) return;
track.bitrateList.forEach((b, idx) => {
if (typeof b.bandwidth !== 'number') return;
const diff = Math.abs(b.bandwidth - currentBitrate);
if (diff < best.diff) {
best = { track, bitrateIdx: idx, diff };
}
});
});
this.#metaData.language = langObj.value;
this.#metaData.langId = langObj.langId;
this.#metaData.langName = langObj.langName;
// Umschalten auf bestes Matching
// this.#dash.setQualityFor('audio', best.bitrateIdx);
this.#dash.setCurrentTrack(best.track);
};
/**
* Handler for 'quality/selected' event. If the user picks a specific resolution, use it; else 'auto'.
* @param {string} selected The chosen resolution in the format '<height>p', or unknown => auto.
* @listens module:src/settings/Quality#quality/selected
*/
#onQualitySelected = ({ quality }) => {
const representations = dash5 ? this.#dash?.getRepresentationsByType('video') : this.#dash?.getBitrateInfoListFor('video'),
newRep = representations.find(rep => rep.height === quality);
this.#dash.updateSettings({
streaming: {
abr: {
maxBitrate: { video: -1 },
autoSwitchBitrate: {
video: typeof newRep === 'undefined'
}
}
}
});
if (newRep) {
if (dash5 && typeof newRep.index !== 'undefined') {
this.#dash.setRepresentationForTypeByIndex('video', newRep.index, true);
} else if (typeof newRep.qualityIndex !== 'undefined') {
this.#dash.setQualityFor('video', newRep.qualityIndex, true);
}
}
};
/**
* Handler for 'quality/resize' event. Caps the max bitrate if display size is small.
* @param {Object} size Object containing size information.
* @param {number} size.height The new container height in px.
* @listens module:src/settings/Quality#quality/resize
*/
#onQualityResize = ({ height }) => {
// this.#dash.updatePortalSize(); -> seems to do nothing, so we have to use another way
// we need to find a suitable size and set the max bitrate to cap dash
// sort by height, then bitrate
const bitRates = dash5 ? this.#dash?.getRepresentationsByType('video') : this.#dash?.getBitrateInfoListFor('video');
const newCap = bitRates
.sort((a, b) => a.height - b.height || a.bitrate - b.bitrate)
.reduce((acc, rate) => rate.height < height * 1.2 ? rate : acc, {});
this.#dash.updateSettings({
streaming: {
abr: {
maxBitrate: { video: newCap.bitrate }
}
}
});
};
/**
* Handler for dash.js TRACK_CHANGE_RENDERED event. If it's audio, update the player's language state.
* @param {Object} mediaInfo The media info object with the changed settings.
* @param {string} mediaInfo.mediaType The media type of the changed object.
* @param {Object} mediaInfo.newMediaInfo The new media info with language information.
* @fires module:src/settings/Language#language/active
* @listens dashjs.MediaPlayer.events.TRACK_CHANGE_RENDERED
*/
#onTrackChangeRendered = ({ mediaType, newMediaInfo }) => {
if (mediaType !== 'audio') return;
if (newMediaInfo.lang) this.#player.publish('language/active', { language: newMediaInfo.lang, langId: newMediaInfo.index }, this.#apiKey);
};
/**
* Handler for dash.js QUALITY_CHANGE_RENDERED event. Publishes 'quality/active' with the current resolution.
* @param {Object} mediaInfo The media info object with the changed settings.
* @fires module:src/settings/Quality#quality/active
* @listens dashjs.MediaPlayer.events.QUALITY_CHANGE_RENDERED
*/
#onQualityChangeRendered = mediaInfo => {
if (mediaInfo.mediaType !== 'video') return;
const idx = this.#dash.getQualityFor('video'), // current video index
qualities = dash5 ? this.#dash?.getRepresentationsByType('video') : this.#dash?.getBitrateInfoListFor('video'),
currentQuality = qualities[idx], // current representations object
width = dash5 ? mediaInfo.newRepresentation.width : this.#dash.getBitrateInfoListFor('video')[mediaInfo.newQuality].width,
height = dash5 ? mediaInfo.newRepresentation.height : this.#dash.getBitrateInfoListFor('video')[mediaInfo.newQuality].height;
const duration = this.#dash.duration(),
isLive = this.#dash.isDynamic();
this.#metaData.duration = duration === Infinity ? 0 : duration;
this.#metaData.isLive = isLive;
this.#metaData.frameRate = currentQuality.frameRate;
this.#metaData.bitRate = currentQuality.bitrate;
this.#metaData.width = width;
this.#metaData.height = height;
this.#player.media.getElement(this.#apiKey).dataset.isLive = isLive ? 'true' : 'false';
this.#player.publish('quality/active', { value: height }, this.#apiKey);
};
/**
* Handler for dash.js 'INTERNAL_KEY_STATUS_CHANGED' event. If a key is restricted or expired, send a media error.
* @param {Object} event The event coming from dash.
* @listens dashjs.MediaPlayer.events.INTERNAL_KEY_STATUS_CHANGED
*/
#onDrmKeyStatus = event => {
if (event.type === 'internalkeyStatusChanged' && ['output-restricted', 'internal-error', 'expired'].includes(event.status)) {
this.#player.publish('media/error', { error: new ExtendedMediaError(99) }, this.#apiKey);
}
};
/**
* Handler for dash.js ERROR event. Maps dash error codes to simulated media errors, publishes 'media/error'.
* @param {Object} event The dahs event object.
* @param {Object} event.error The event error code.
* @fires module:src/core/Media#media/error
*/
#onDashError = event => {
let { error } = event;
const { message } = error;
switch (error.code) {
case DashJs.MediaPlayer.errors.MANIFEST_LOADER_LOADING_FAILURE_ERROR_CODE:
case DashJs.MediaPlayer.errors.DOWNLOAD_ERROR_ID_MANIFEST_CODE:
case DashJs.MediaPlayer.errors.DOWNLOAD_ERROR_ID_SIDX_CODE:
case DashJs.MediaPlayer.errors.DOWNLOAD_ERROR_ID_CONTENT_CODE:
case DashJs.MediaPlayer.errors.DOWNLOAD_ERROR_ID_INITIALIZATION_CODE:
case DashJs.MediaPlayer.errors.DOWNLOAD_ERROR_ID_XLINK_CODE:
error = new ExtendedMediaError(2);
break;
case DashJs.MediaPlayer.errors.MANIFEST_LOADER_PARSING_FAILURE_ERROR_CODE:
case DashJs.MediaPlayer.errors.MANIFEST_ERROR_ID_NOSTREAMS_CODE:
case DashJs.MediaPlayer.errors.TIMED_TEXT_ERROR_ID_PARSE_CODE:
error = new ExtendedMediaError(3);
break;
case DashJs.MediaPlayer.errors.CAPABILITY_MEDIASOURCE_ERROR_CODE:
case DashJs.MediaPlayer.errors.MANIFEST_ERROR_ID_MULTIPLEXED_CODE:
case DashJs.MediaPlayer.errors.MEDIASOURCE_TYPE_UNSUPPORTED_CODE:
error = new ExtendedMediaError(4);
break;
case DashJs.MediaPlayer.errors.MEDIA_KEYERR_CODE:
case DashJs.MediaPlayer.errors.KEY_SESSION_CREATED_ERROR_CODE:
case DashJs.MediaPlayer.errors.KEY_STATUS_CHANGED_EXPIRED_ERROR_CODE:
case DashJs.MediaPlayer.errors.KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_ERROR_CODE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_ERROR_MESSAGE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_MESSAGE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_CODE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_MESSAGE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_NO_LICENSE_SERVER_URL_ERROR_CODE:
case DashJs.MediaPlayer.errors.MEDIA_KEY_MESSAGE_NO_LICENSE_SERVER_URL_ERROR_MESSAGE:
error = new ExtendedMediaError(99);
break;
// no default
default:
error = new ExtendedMediaError(error.code);
}
error.message = message;
this.#loadTask.reject(error);
this.#player.media.pause();
this.#player.publish('media/error', { error }, this.#apiKey);
};
/**
* Handler for dash protection events if debug is enabled.
* @param {Object} event Event data from dash.js protection.
*/
#onProtectionEvent = event => { // eslint-disable-line class-methods-use-this
console.log(`[DRM] Event received: ${event.type}`, event); // eslint-disable-line no-console
};
/**
* Generic handler for dash.js events when debug mode is enabled.
* Filters out noisy or frequent events and logs others to the console.
* This helps with development and troubleshooting by surfacing meaningful events only.
* @param {Object} event The event object emitted by dash.js.
* @param {string} event.type The type name of the dash event.
*/
#onDashEvent = event => { // eslint-disable-line class-methods-use-this
if (['fragmentLoading', 'playbackTimeUpdated', 'bufferLevelUpdated', 'throughputMeasurementStored', 'playbackProgress'].includes(event.type)
|| event.type.startsWith('metric') || event.type.startsWith('fragment')) return;
console.log(`[Dash] Event received: ${event.type}`, event); // eslint-disable-line no-console
};
/**
* Handler for removing dash when 'data/ready' triggers a new media load. Cleans up subscriptions and dash instance.
* @listens module:src/core/Data#data/ready
*/
#removeDash = () => {
if (this.#dash) {
if (this.#config.debug === true || this.#config.debug?.enabled) {
Object.values(DashJs.MediaPlayer.events).forEach(value => { this.#dash.off(value, this.#onDashEvent); });
if (DashJs.Protection) {
Dash.#dashProtectionEventNames.forEach(name => {
this.#dash.off(DashJs.Protection.events[name], this.#onProtectionEvent);
});
}
}
const { drm } = this.#player.data.getMediaData();
if (DashJs.Protection && drm && (drm.Widevine || drm.PlayReady)) {
this.#dash.off(DashJs.Protection.events.INTERNAL_KEY_STATUS_CHANGED, this.#onDrmKeyStatus);
}
this.#dash.off(DashJs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, this.#onQualityChangeRendered);
this.#dash.off(DashJs.MediaPlayer.events.TRACK_CHANGE_RENDERED, this.#onTrackChangeRendered);
this.#dash.off(DashJs.MediaPlayer.events.PLAYBACK_METADATA_LOADED, this.#onMetaDataLoaded);
this.#dash.off(DashJs.MediaPlayer.events.ERROR, this.#onDashError);
this.#dash.destroy();
this.#dash = null;
this.#player.unsubscribe(this.#subscriptions);
}
};
/**
* This method removes all events, subscriptions and DOM nodes created by this component.
*/
destroy() {
this.#mediaComponent.unregisterPlugin(this);
this.#player.unsubscribe('data/ready', this.#removeDash);
this.#removeDash();
this.#loadDashPromise = null;
this.#player = this.#dash = this.#mediaComponent = this.#apiKey = null;
}
/**
* The list of protection events from dash.js (v5 or vX).
* @private
* @memberof module:src/streaming/Dash
* @type {string[]}
*/
static #dashProtectionEventNames = [
'INTERNAL_KEY_MESSAGE',
'INTERNAL_KEY_STATUS_CHANGED',
'KEY_ADDED',
'KEY_ERROR',
'KEY_MESSAGE',
'KEY_SESSION_CLOSED',
'KEY_SESSION_CREATED',
'KEY_SESSION_REMOVED',
'KEY_STATUSES_CHANGED',
'KEY_SYSTEM_ACCESS_COMPLETE',
'KEY_SYSTEM_SELECTED',
'LICENSE_REQUEST_COMPLETE',
'LICENSE_REQUEST_SENDING',
'NEED_KEY',
'PROTECTION_CREATED',
'PROTECTION_DESTROYED',
'SERVER_CERTIFICATE_UPDATED',
'TEARDOWN_COMPLETE',
'VIDEO_ELEMENT_SELECTED',
'KEY_SESSION_UPDATED'
];
}