cloudinary-video-player
Version:
Cloudinary Video Player
1,418 lines (1,314 loc) • 1.32 MB
JavaScript
/*!
* Cloudinary Video Player v3.0.1
* Built on 2025-06-09T18:04:09.938Z
* https://github.com/cloudinary/cloudinary-video-player
*/
(self["cloudinaryVideoPlayerChunkLoading"] = self["cloudinaryVideoPlayerChunkLoading"] || []).push([["adaptive-streaming"],{
/***/ "./plugins/adaptive-streaming/abr-strategies.js":
/*!******************************************************!*\
!*** ./plugins/adaptive-streaming/abr-strategies.js ***!
\******************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ ADAPTIVE_STREAMING_STRATEGY: () => (/* binding */ ADAPTIVE_STREAMING_STRATEGY),
/* harmony export */ abrStrategies: () => (/* binding */ abrStrategies),
/* harmony export */ hdrSupported: () => (/* binding */ hdrSupported)
/* harmony export */ });
const abrStrategies = {
fastStart: {
capLevelToPlayerSize: true,
ignoreDevicePixelRatio: true,
maxDevicePixelRatio: 2,
abrEwmaDefaultEstimate: 4194304,
abrEwmaDefaultEstimateMax: 4194304,
enableWorker: false,
startLevel: 0
},
balanced: {
capLevelToPlayerSize: true,
ignoreDevicePixelRatio: true,
maxDevicePixelRatio: 2,
abrEwmaDefaultEstimate: 4194304,
abrEwmaDefaultEstimateMax: 4194304,
enableWorker: false
},
highQuality: {
capLevelToPlayerSize: true,
ignoreDevicePixelRatio: false,
maxDevicePixelRatio: 2,
abrEwmaDefaultEstimate: 4194304,
abrEwmaDefaultEstimateMax: 4194304,
enableWorker: false
}
};
const ADAPTIVE_STREAMING_STRATEGY = Object.keys(abrStrategies);
const hdrSupported = window.matchMedia && window.matchMedia('(dynamic-range: high)').matches;
/***/ }),
/***/ "./plugins/adaptive-streaming/adaptive-streaming.js":
/*!**********************************************************!*\
!*** ./plugins/adaptive-streaming/adaptive-streaming.js ***!
\**********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ adaptiveStreamingPlugin)
/* harmony export */ });
/* harmony import */ var hls_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! hls.js */ "../node_modules/hls.js/dist/hls.mjs");
/* harmony import */ var videojs_contrib_quality_levels__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! videojs-contrib-quality-levels */ "../node_modules/videojs-contrib-quality-levels/dist/videojs-contrib-quality-levels.js");
/* harmony import */ var videojs_contrib_quality_levels__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(videojs_contrib_quality_levels__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var videojs_contrib_quality_menu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! videojs-contrib-quality-menu */ "../node_modules/videojs-contrib-quality-menu/dist/videojs-contrib-quality-menu.es.js");
/* harmony import */ var _videojs_contrib_hlsjs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./videojs-contrib-hlsjs */ "./plugins/adaptive-streaming/videojs-contrib-hlsjs.js");
/* harmony import */ var _quality_levels__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./quality-levels */ "./plugins/adaptive-streaming/quality-levels.js");
/* harmony import */ var _abr_strategies__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./abr-strategies */ "./plugins/adaptive-streaming/abr-strategies.js");
async function adaptiveStreamingPlugin(player, options) {
const config = {
..._abr_strategies__WEBPACK_IMPORTED_MODULE_5__.abrStrategies[options.strategy],
videoPreference: _abr_strategies__WEBPACK_IMPORTED_MODULE_5__.hdrSupported ? {
preferHDR: true
} : undefined
};
player.tech_.options_.hlsjsConfig = config;
player.on('loadstart', () => (0,_quality_levels__WEBPACK_IMPORTED_MODULE_4__.qualityLevels)(player, options).init());
player.qualityMenu();
player.adaptiveStreamingLoaded = true;
}
/***/ }),
/***/ "./plugins/adaptive-streaming/quality-levels.js":
/*!******************************************************!*\
!*** ./plugins/adaptive-streaming/quality-levels.js ***!
\******************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ qualityLevels: () => (/* binding */ qualityLevels)
/* harmony export */ });
/* harmony import */ var video_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! video.js */ "../node_modules/video.js/dist/alt/video.core-exposed.js");
/* harmony import */ var video_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(video_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var hls_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! hls.js */ "../node_modules/hls.js/dist/hls.mjs");
const qualityLevels = (player, options) => {
const levelToRenditionHls = level => {
let levelUrl = Array.isArray(level.url) && level.url.length > 1 ? level.url[level.urlId] : level.url;
let rendition = {
id: levelUrl,
width: level.width,
height: level.height,
bandwidth: level.bitrate,
// bitrate => bandwidth
frameRate: 0,
enabled: enableRendition => {
var tech = player.tech({
IWillNotUseThisInPlugins: true
});
if (typeof tech.sourceHandler_ != 'undefined' && typeof tech.sourceHandler_.hls != 'undefined' && tech.sourceHandler_.hls != null) {
const hls = tech.sourceHandler_.hls;
const levelIndex = hls.levels.findIndex(l => (Array.isArray(l.url) && l.url.length > 1 ? l.url[l.urlId] : l.url) === levelUrl);
if (levelIndex >= 0 && enableRendition) {
hls.currentLevel = levelIndex;
}
}
return enableRendition;
}
};
return rendition;
};
const levelToRenditionDash = level => ({
id: level.id,
width: level.width,
height: level.height,
bandwidth: level.bandwidth,
enabled: enableRendition => {
const dash = player.dash;
if (dash && dash.mediaPlayer) {
if (enableRendition) {
dash.mediaPlayer.updateSettings({
streaming: {
abr: {
autoSwitchBitrate: {
video: false,
audio: false
}
}
}
});
// Find the correct quality index by resolution
const targetQualityIndex = findDashQualityIndex(level.width, level.height);
if (targetQualityIndex >= 0) {
dash.mediaPlayer.setQualityFor('video', targetQualityIndex);
// Set audio quality if mapping exists
if (dash.audioMapper && dash.audioMapper[targetQualityIndex] !== undefined) {
dash.mediaPlayer.setQualityFor('audio', dash.audioMapper[targetQualityIndex]);
}
}
}
}
return enableRendition;
}
});
const getRenditionsDash = () => {
var dash = player.dash;
if (typeof dash != 'undefined' && dash != null && typeof dash.mediaPlayer != 'undefined' && dash.mediaPlayer != null) {
var streamInfo = dash.mediaPlayer.getActiveStream().getStreamInfo();
var dashAdapter = dash.mediaPlayer.getDashAdapter();
if (dashAdapter && streamInfo) {
const periodIdx = streamInfo.index;
var adaptation = dashAdapter.getAdaptationForType(periodIdx, 'video', streamInfo);
}
return adaptation.Representation_asArray;
}
return [];
};
// Helper function to find DASH quality index by resolution
const findDashQualityIndex = (targetWidth, targetHeight) => {
var dash = player.dash;
if (dash && dash.mediaPlayer) {
const availableQualities = dash.mediaPlayer.getBitrateInfoListFor('video');
const targetQuality = availableQualities.find(q => q.width === targetWidth && q.height === targetHeight);
return targetQuality ? targetQuality.qualityIndex : -1;
}
return -1;
};
// Clean up quality levels and reset state for new source
const cleanupQualityLevels = () => {
let qualityLevels = player.qualityLevels;
if (typeof qualityLevels === 'function') {
qualityLevels = player.qualityLevels();
// Clear all existing quality levels
qualityLevels.dispose();
debugLog('Quality levels cleaned up for new source');
}
// Clean up audio tracks from previous source
const audioTrackList = player.audioTracks();
if (audioTrackList && audioTrackList.length > 0) {
// Remove all existing audio tracks
for (let i = audioTrackList.length - 1; i >= 0; i--) {
audioTrackList.removeTrack(audioTrackList[i]);
}
debugLog('Audio tracks cleaned up for new source');
}
// Clean up DASH-specific state
const dash = player.dash;
if (dash && dash.audioMapper) {
delete dash.audioMapper;
debugLog('DASH audio mapper cleaned up for new source');
}
// Reset previous resolution for qualitychanged events
previousResolution = null;
};
// Update the QualityLevels list of renditions
const populateLevels = (levels, abrType) => {
// Clean up existing quality levels before adding new ones
cleanupQualityLevels();
let qualityLevels = player.qualityLevels;
if (typeof qualityLevels === 'function') {
qualityLevels = player.qualityLevels();
debugLog('QualityLevels', qualityLevels);
switch (abrType) {
case 'hls':
for (let l = 0; l < levels.length; l++) {
let level = levels[l];
let rendition = levelToRenditionHls(level);
qualityLevels.addQualityLevel(rendition);
}
break;
case 'dash':
{
// Set up audio mapping for DASH
const dash = player.dash;
if (!dash) break;
const videoRates = levels;
const audioRates = dash.mediaPlayer.getBitrateInfoListFor('audio') || [];
const normalizeFactor = videoRates.length > 0 ? videoRates[videoRates.length - 1].bandwidth : 1;
dash.audioMapper = videoRates.map(rate => Math.round(rate.bandwidth / normalizeFactor * (audioRates.length - 1)));
for (let l = 0; l < levels.length; l++) {
let level = levels[l];
let rendition = levelToRenditionDash(level);
qualityLevels.addQualityLevel(rendition);
}
break;
}
default:
return;
}
} else {
console.warn('QualityLevels not supported');
}
};
let previousResolution = null; // for qualitychanged event data
// Update the selected rendition
const populateQualityLevelsChange = currentLevel => {
let qualityLevels = player.qualityLevels;
if (typeof qualityLevels === 'function') {
qualityLevels = player.qualityLevels();
if (qualityLevels.length == 0) {
console.warn('ERROR - no quality levels found! Patching populate levels first');
var tech = player.tech({
IWillNotUseThisInPlugins: true
});
if (typeof tech.sourceHandler_ != 'undefined' && typeof tech.sourceHandler_.hls != 'undefined' && tech.sourceHandler_.hls != null) {
const hls = tech.sourceHandler_.hls;
populateLevels(hls.levels, 'hls');
}
}
qualityLevels.selectedIndex_ = currentLevel;
qualityLevels.trigger({
type: 'change',
selectedIndex: currentLevel
});
}
};
// Custom 'qualitychanged' event
const populateQualityChangedEvent = currentLevel => {
if (currentLevel < 0) {
return;
}
let qualityLevels = player.qualityLevels;
let level = null;
// Using videojs-contrib-quality-levels
if (typeof qualityLevels === 'function') {
qualityLevels = player.qualityLevels();
level = qualityLevels.levels_[currentLevel];
debugLog('Custom qualitychanged', 'using videojs-contrib-quality-levels');
} else {
// hls.js directly
var tech = player.tech({
IWillNotUseThisInPlugins: true
});
if (typeof tech.sourceHandler_ != 'undefined' && typeof tech.sourceHandler_.hls != 'undefined' && tech.sourceHandler_.hls != null) {
const hls = tech.sourceHandler_.hls;
level = hls.levels[currentLevel];
debugLog('Custom qualitychanged', 'using hls.js directly');
if (currentLevel !== hls.currentLevel) {
debugLog('ERROR - new level differs from hls.js');
}
} else {
// dash.js directly
var dash = player.dash;
if (typeof dash != 'undefined' && dash != null && typeof dash.mediaPlayer != 'undefined' && dash.mediaPlayer != null) {
let renditions = getRenditionsDash();
level = renditions[currentLevel];
debugLog('Custom qualitychanged', 'using dash.js directly');
}
}
}
// Add null check for level
if (!level) {
debugLog('Warning: Level is undefined in populateQualityChangedEvent', {
currentLevel
});
return;
}
let currentRes = {
width: level.width,
height: level.height
};
if (previousResolution !== currentRes) {
let data = {
from: previousResolution,
to: currentRes
};
// Trigger custom 'qualitychanged' event on videojs
player.trigger({
type: 'qualitychanged',
eventData: data
});
}
previousResolution = currentRes;
// Add detailed logging of current rendition
debugLog('Current Rendition', {
index: currentLevel,
resolution: `${level.width}x${level.height}`,
bitrate: `${Math.round(level.bitrate / 1000)} kbps`,
url: Array.isArray(level.url) ? level.url[level.urlId] : level.url,
details: level
});
};
const debugLog = (label, data) => {
if (options.debug) {
console.log(`%c ${label}`, 'background: #3498db; color: white; padding: 2px 4px; border-radius: 2px;', data);
}
};
const logAudioTrackInfo = () => {
var tech = player.tech({
IWillNotUseThisInPlugins: true
});
if (typeof tech.sourceHandler_ != 'undefined' && typeof tech.sourceHandler_.hls != 'undefined' && tech.sourceHandler_.hls != null) {
const hls = tech.sourceHandler_.hls;
const audioTrackId = hls.audioTrack;
const len = hls.audioTracks.length;
for (let i = 0; i < len; i++) {
if (audioTrackId === i) {
debugLog(`audio track [${i}] ${hls.audioTracks[i].name} - enabled`, hls.audioTracks[i]);
} else {
debugLog(`audio track [${i}] ${hls.audioTracks[i].name} - disabled`, hls.audioTracks[i]);
}
}
}
};
// Audio track handling
const addAudioTrackVideojs = track => {
var vjsTrack = new (video_js__WEBPACK_IMPORTED_MODULE_0___default().AudioTrack)({
id: `${track.type}-id_${track.id}-groupId_${track.groupId}-${track.name}`,
kind: 'translation',
label: track.name,
language: track.lang,
enabled: track.enabled,
default: track.default
});
// Add the track to the player's audio track list.
player.audioTracks().addTrack(vjsTrack);
};
const initAudioTrackInfo = () => {
var tech = player.tech({
IWillNotUseThisInPlugins: true
});
if (typeof tech.sourceHandler_ != 'undefined' && typeof tech.sourceHandler_.hls != 'undefined' && tech.sourceHandler_.hls != null) {
const hls = tech.sourceHandler_.hls;
const len = hls.audioTracks.length;
for (let i = 0; i < len; i++) {
addAudioTrackVideojs(hls.audioTracks[i]);
}
}
// Listen to the "change" event.
var audioTrackList = player.audioTracks();
audioTrackList.addEventListener('change', function () {
var tech = player.tech({
IWillNotUseThisInPlugins: true
});
if (typeof tech.sourceHandler_ != 'undefined' && typeof tech.sourceHandler_.hls != 'undefined' && tech.sourceHandler_.hls != null) {
const hls = tech.sourceHandler_.hls;
for (var i = 0; i < audioTrackList.length; i++) {
var track = audioTrackList[i];
if (track.enabled) {
hls.audioTrack = i;
return;
}
}
}
});
};
// Map hls.js events to QualityLevels
const initQualityLevels = () => {
var tech = player.tech({
IWillNotUseThisInPlugins: true
});
if (typeof tech == 'undefined') {
console.warn('ERROR - tech not found!');
}
// HLS
if (typeof tech.sourceHandler_ != 'undefined' && typeof tech.sourceHandler_.hls != 'undefined' && tech.sourceHandler_.hls != null) {
const hls = tech.sourceHandler_.hls;
hls.on(hls_js__WEBPACK_IMPORTED_MODULE_1__["default"].Events.MANIFEST_LOADED, (eventName, data) => {
debugLog(`HLS event: ${eventName}`, data);
populateLevels(hls.levels, 'hls');
});
hls.on(hls_js__WEBPACK_IMPORTED_MODULE_1__["default"].Events.LEVEL_SWITCHED, (eventName, data) => {
debugLog(`HLS event: ${eventName}`, data);
populateQualityLevelsChange(data.level);
populateQualityChangedEvent(data.level);
});
hls.on(hls_js__WEBPACK_IMPORTED_MODULE_1__["default"].Events.AUDIO_TRACKS_UPDATED, (eventName, data) => {
debugLog(`HLS event: ${eventName}`, data);
initAudioTrackInfo();
});
hls.on(hls_js__WEBPACK_IMPORTED_MODULE_1__["default"].Events.AUDIO_TRACK_SWITCHED, (eventName, data) => {
debugLog(`HLS event: ${eventName}`, data);
logAudioTrackInfo();
});
hls.on(hls_js__WEBPACK_IMPORTED_MODULE_1__["default"].Events.ERROR, (eventName, data) => {
debugLog(`HLS event: ${eventName}`, data);
if (data.fatal) {
player.trigger({
type: 'error',
eventData: data
});
}
});
} else {
// DASH
var dash = player.dash;
if (typeof dash != 'undefined' && dash != null && typeof dash.mediaPlayer != 'undefined' && dash.mediaPlayer != null) {
let renditions = getRenditionsDash();
populateLevels(renditions, 'dash');
dash.mediaPlayer.on('qualityChangeRendered', evt => {
const currentVideoQuality = dash.mediaPlayer.getQualityFor('video');
const availableQualities = dash.mediaPlayer.getBitrateInfoListFor('video');
const currentQualityInfo = availableQualities[currentVideoQuality];
const renditionIndex = findDashQualityIndex(currentQualityInfo.width, currentQualityInfo.height);
if (renditionIndex >= 0) {
debugLog(`DASH event: ${evt.type}`, evt, renditions[renditionIndex]);
populateQualityLevelsChange(renditionIndex);
populateQualityChangedEvent(renditionIndex);
} else {
console.warn('Could not find matching rendition for DASH quality change');
}
});
}
}
};
return {
init: initQualityLevels
};
};
/***/ }),
/***/ "./plugins/adaptive-streaming/videojs-contrib-hlsjs.js":
/*!*************************************************************!*\
!*** ./plugins/adaptive-streaming/videojs-contrib-hlsjs.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var hls_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! hls.js */ "../node_modules/hls.js/dist/hls.mjs");
/**
* hls.js source handler
* Source: https://github.com/Peer5/videojs-contrib-hls.js
* @param source
* @param tech
* @constructor
*/
function Html5HlsJS(source, tech) {
var options = tech.options_;
var el = tech.el();
var duration = null;
var hls = this.hls = new hls_js__WEBPACK_IMPORTED_MODULE_0__["default"](options.hlsjsConfig);
/**
* creates an error handler function
* @returns {Function}
*/
function errorHandlerFactory() {
var _recoverDecodingErrorDate = null;
var _recoverAudioCodecErrorDate = null;
return function () {
var now = Date.now();
if (!_recoverDecodingErrorDate || now - _recoverDecodingErrorDate > 2000) {
_recoverDecodingErrorDate = now;
hls.recoverMediaError();
} else if (!_recoverAudioCodecErrorDate || now - _recoverAudioCodecErrorDate > 2000) {
_recoverAudioCodecErrorDate = now;
hls.swapAudioCodec();
hls.recoverMediaError();
} else {
console.error('Error loading media: File could not be played');
}
};
}
// create separate error handlers for hlsjs and the video tag
var hlsjsErrorHandler = errorHandlerFactory();
var videoTagErrorHandler = errorHandlerFactory();
// listen to error events coming from the video tag
el.addEventListener('error', function (e) {
var mediaError = e.currentTarget.error;
if (mediaError.code === mediaError.MEDIA_ERR_DECODE) {
videoTagErrorHandler();
} else {
console.error('Error loading media: File could not be played');
}
});
/**
* Destroys the Hls instance
*/
this.dispose = function () {
hls.destroy();
};
/**
* returns the duration of the stream, or Infinity if live video
* @returns {Infinity|number}
*/
this.duration = function () {
return duration || el.duration || 0;
};
// update live status on level load
hls.on(hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].Events.LEVEL_LOADED, function (event, data) {
duration = data.details.live ? Infinity : data.details.totalduration;
});
// try to recover on fatal errors
hls.on(hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].Events.ERROR, function (event, data) {
if (data.fatal) {
switch (data.type) {
case hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].ErrorTypes.NETWORK_ERROR:
hls.startLoad();
break;
case hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].ErrorTypes.MEDIA_ERROR:
hlsjsErrorHandler();
break;
default:
console.error('Error loading media: File could not be played');
break;
}
}
});
Object.keys(hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].Events).forEach(function (key) {
var eventName = hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].Events[key];
hls.on(eventName, function (event, data) {
tech.trigger(eventName, data);
});
});
// Intercept native TextTrack calls and route to video.js directly only
// if native text tracks are not supported on this browser.
if (!tech.featuresNativeTextTracks) {
Object.defineProperty(el, 'textTracks', {
value: tech.textTracks,
writable: false
});
el.addTextTrack = function () {
return tech.addTextTrack.apply(tech, arguments);
};
}
// attach hlsjs to videotag
hls.attachMedia(el);
hls.loadSource(source.src);
}
var hlsTypeRE = /^application\/(x-mpegURL|vnd\.apple\.mpegURL)$/i;
var hlsExtRE = /\.m3u8/i;
var HlsSourceHandler = {
canHandleSource: function (source) {
if (source.skipContribHlsJs) {
return '';
} else if (hlsTypeRE.test(source.type)) {
return 'probably';
} else if (hlsExtRE.test(source.src)) {
return 'maybe';
} else {
return '';
}
},
handleSource: function (source, tech) {
return new Html5HlsJS(source, tech);
},
canPlayType: function (type) {
if (hlsTypeRE.test(type)) {
return 'probably';
}
return '';
}
};
if (hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].isSupported()) {
var videojs = window.videojs;
// support es6 style import
videojs = videojs && videojs.default || videojs;
if (videojs) {
var html5Tech = videojs.getTech && videojs.getTech('Html5'); // videojs6 (partially on videojs5 too)
html5Tech = html5Tech || videojs.getComponent && videojs.getComponent('Html5'); // videojs5
if (html5Tech) {
html5Tech.registerSourceHandler(HlsSourceHandler, 0);
}
} else {
console.warn('videojs-contrib-hls.js: Couldn\'t find find window.videojs nor require(\'video.js\')');
}
}
/***/ }),
/***/ "../node_modules/videojs-contrib-quality-levels/dist/videojs-contrib-quality-levels.js":
/*!*********************************************************************************************!*\
!*** ../node_modules/videojs-contrib-quality-levels/dist/videojs-contrib-quality-levels.js ***!
\*********************************************************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
/*! @name videojs-contrib-quality-levels @version 4.1.0 @license Apache-2.0 */
(function (global, factory) {
true ? module.exports = factory(__webpack_require__(/*! video.js */ "../node_modules/video.js/dist/alt/video.core-exposed.js")) :
0;
})(this, (function (videojs) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
/**
* A single QualityLevel.
*
* interface QualityLevel {
* readonly attribute DOMString id;
* attribute DOMString label;
* readonly attribute long width;
* readonly attribute long height;
* readonly attribute long bitrate;
* attribute boolean enabled;
* };
*
* @class QualityLevel
*/
class QualityLevel {
/**
* Creates a QualityLevel
*
* @param {Representation|Object} representation The representation of the quality level
* @param {string} representation.id Unique id of the QualityLevel
* @param {number=} representation.width Resolution width of the QualityLevel
* @param {number=} representation.height Resolution height of the QualityLevel
* @param {number} representation.bandwidth Bitrate of the QualityLevel
* @param {number=} representation.frameRate Frame-rate of the QualityLevel
* @param {Function} representation.enabled Callback to enable/disable QualityLevel
*/
constructor(representation) {
let level = this; // eslint-disable-line
level.id = representation.id;
level.label = level.id;
level.width = representation.width;
level.height = representation.height;
level.bitrate = representation.bandwidth;
level.frameRate = representation.frameRate;
level.enabled_ = representation.enabled;
Object.defineProperty(level, 'enabled', {
/**
* Get whether the QualityLevel is enabled.
*
* @return {boolean} True if the QualityLevel is enabled.
*/
get() {
return level.enabled_();
},
/**
* Enable or disable the QualityLevel.
*
* @param {boolean} enable true to enable QualityLevel, false to disable.
*/
set(enable) {
level.enabled_(enable);
}
});
return level;
}
}
/**
* A list of QualityLevels.
*
* interface QualityLevelList : EventTarget {
* getter QualityLevel (unsigned long index);
* readonly attribute unsigned long length;
* readonly attribute long selectedIndex;
*
* void addQualityLevel(QualityLevel qualityLevel)
* void removeQualityLevel(QualityLevel remove)
* QualityLevel? getQualityLevelById(DOMString id);
*
* attribute EventHandler onchange;
* attribute EventHandler onaddqualitylevel;
* attribute EventHandler onremovequalitylevel;
* };
*
* @extends videojs.EventTarget
* @class QualityLevelList
*/
class QualityLevelList extends videojs__default["default"].EventTarget {
/**
* Creates a QualityLevelList.
*/
constructor() {
super();
let list = this; // eslint-disable-line
list.levels_ = [];
list.selectedIndex_ = -1;
/**
* Get the index of the currently selected QualityLevel.
*
* @returns {number} The index of the selected QualityLevel. -1 if none selected.
* @readonly
*/
Object.defineProperty(list, 'selectedIndex', {
get() {
return list.selectedIndex_;
}
});
/**
* Get the length of the list of QualityLevels.
*
* @returns {number} The length of the list.
* @readonly
*/
Object.defineProperty(list, 'length', {
get() {
return list.levels_.length;
}
});
list[Symbol.iterator] = () => list.levels_.values();
return list;
}
/**
* Adds a quality level to the list.
*
* @param {Representation|Object} representation The representation of the quality level
* @param {string} representation.id Unique id of the QualityLevel
* @param {number=} representation.width Resolution width of the QualityLevel
* @param {number=} representation.height Resolution height of the QualityLevel
* @param {number} representation.bandwidth Bitrate of the QualityLevel
* @param {number=} representation.frameRate Frame-rate of the QualityLevel
* @param {Function} representation.enabled Callback to enable/disable QualityLevel
* @return {QualityLevel} the QualityLevel added to the list
* @method addQualityLevel
*/
addQualityLevel(representation) {
let qualityLevel = this.getQualityLevelById(representation.id);
// Do not add duplicate quality levels
if (qualityLevel) {
return qualityLevel;
}
const index = this.levels_.length;
qualityLevel = new QualityLevel(representation);
if (!('' + index in this)) {
Object.defineProperty(this, index, {
get() {
return this.levels_[index];
}
});
}
this.levels_.push(qualityLevel);
this.trigger({
qualityLevel,
type: 'addqualitylevel'
});
return qualityLevel;
}
/**
* Removes a quality level from the list.
*
* @param {QualityLevel} qualityLevel The QualityLevel to remove from the list.
* @return {QualityLevel|null} the QualityLevel removed or null if nothing removed
* @method removeQualityLevel
*/
removeQualityLevel(qualityLevel) {
let removed = null;
for (let i = 0, l = this.length; i < l; i++) {
if (this[i] === qualityLevel) {
removed = this.levels_.splice(i, 1)[0];
if (this.selectedIndex_ === i) {
this.selectedIndex_ = -1;
} else if (this.selectedIndex_ > i) {
this.selectedIndex_--;
}
break;
}
}
if (removed) {
this.trigger({
qualityLevel,
type: 'removequalitylevel'
});
}
return removed;
}
/**
* Searches for a QualityLevel with the given id.
*
* @param {string} id The id of the QualityLevel to find.
* @return {QualityLevel|null} The QualityLevel with id, or null if not found.
* @method getQualityLevelById
*/
getQualityLevelById(id) {
for (let i = 0, l = this.length; i < l; i++) {
const level = this[i];
if (level.id === id) {
return level;
}
}
return null;
}
/**
* Resets the list of QualityLevels to empty
*
* @method dispose
*/
dispose() {
this.selectedIndex_ = -1;
this.levels_.length = 0;
}
}
/**
* change - The selected QualityLevel has changed.
* addqualitylevel - A QualityLevel has been added to the QualityLevelList.
* removequalitylevel - A QualityLevel has been removed from the QualityLevelList.
*/
QualityLevelList.prototype.allowedEvents_ = {
change: 'change',
addqualitylevel: 'addqualitylevel',
removequalitylevel: 'removequalitylevel'
};
// emulate attribute EventHandler support to allow for feature detection
for (const event in QualityLevelList.prototype.allowedEvents_) {
QualityLevelList.prototype['on' + event] = null;
}
var version = "4.1.0";
/**
* Initialization function for the qualityLevels plugin. Sets up the QualityLevelList and
* event handlers.
*
* @param {Player} player Player object.
* @param {Object} options Plugin options object.
* @return {QualityLevelList} a list of QualityLevels
*/
const initPlugin = function (player, options) {
const originalPluginFn = player.qualityLevels;
const qualityLevelList = new QualityLevelList();
const disposeHandler = function () {
qualityLevelList.dispose();
player.qualityLevels = originalPluginFn;
player.off('dispose', disposeHandler);
};
player.on('dispose', disposeHandler);
player.qualityLevels = () => qualityLevelList;
player.qualityLevels.VERSION = version;
return qualityLevelList;
};
/**
* A video.js plugin.
*
* In the plugin function, the value of `this` is a video.js `Player`
* instance. You cannot rely on the player being in a "ready" state here,
* depending on how the plugin is invoked. This may or may not be important
* to you; if not, remove the wait for "ready"!
*
* @param {Object} options Plugin options object
* @return {QualityLevelList} a list of QualityLevels
*/
const qualityLevels = function (options) {
return initPlugin(this, videojs__default["default"].obj.merge({}, options));
};
// Register the plugin with video.js.
videojs__default["default"].registerPlugin('qualityLevels', qualityLevels);
// Include the version number.
qualityLevels.VERSION = version;
return qualityLevels;
}));
/***/ }),
/***/ "../node_modules/videojs-contrib-quality-menu/dist/videojs-contrib-quality-menu.es.js":
/*!********************************************************************************************!*\
!*** ../node_modules/videojs-contrib-quality-menu/dist/videojs-contrib-quality-menu.es.js ***!
\********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ qualityMenu)
/* harmony export */ });
/* harmony import */ var video_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! video.js */ "../node_modules/video.js/dist/alt/video.core-exposed.js");
/* harmony import */ var video_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(video_js__WEBPACK_IMPORTED_MODULE_0__);
/*! @name videojs-contrib-quality-menu @version 1.0.4 @license Apache-2.0 */
/**
* @file quality-menu-item.js
*/
const MenuItem = video_js__WEBPACK_IMPORTED_MODULE_0___default().getComponent('MenuItem');
const dom = (video_js__WEBPACK_IMPORTED_MODULE_0___default().dom) || (video_js__WEBPACK_IMPORTED_MODULE_0___default());
/**
* The quality level menu quality
*
* @extends MenuItem
* @class QualityMenuItem
*/
class QualityMenuItem extends MenuItem {
/**
* Creates a QualityMenuItem
*
* @param {Player|Object} player
* Main Player
* @param {Object} [options]
* Options for menu item
* @param {number[]} options.levels
* Array of indices mapping to QualityLevels in the QualityLevelList for
* this menu item
* @param {string} options.label
* Label for this menu item
* @param {string} options.controlText
* control text for this menu item
* @param {string} options.subLabel
* sub label text for this menu item
* @param {boolean} options.active
* True if the QualityLevelList.selectedIndex is contained in the levels list
* for this menu
* @param {boolean} options.selected
* True if this menu item is the selected item in the UI
* @param {boolean} options.selectable
* True if this menu item should be selectable in the UI
*/
constructor(player, options = {}) {
const selectedOption = options.selected;
// We need to change options.seleted to options.active because the call to super
// causes us to run MenuItem's constructor which calls this.selected(options.selected)
// However, for QualityMenuItem, we change the meaning of the parameter to
// this.selected() to be what we mean for 'active' which is True if the
// QualityLevelList.selectedIndex is contained in the levels list for this menu
options.selected = options.active;
super(player, options);
const qualityLevels = player.qualityLevels();
this.levels_ = options.levels;
this.selected_ = selectedOption;
this.handleQualityChange = this.handleQualityChange.bind(this);
this.controlText(options.controlText);
this.on(qualityLevels, 'change', this.handleQualityChange);
this.on('dispose', () => {
this.off(qualityLevels, 'change', this.handleQualityChange);
});
}
/**
* Create the component's DOM element
*
* @param {string} [type]
* Element type
* @param {Object} [props]
* Element properties
* @param {Object} [attrs]
* An object of attributes that should be set on the element
* @return {Element}
* The DOM element
* @method createEl
*/
createEl(type, props, attrs) {
const el = super.createEl(type, props, attrs);
const subLabel = dom.createEl('span', {
className: 'vjs-quality-menu-item-sub-label',
innerHTML: this.localize(this.options_.subLabel || '')
});
this.subLabel_ = subLabel;
if (el) {
el.appendChild(subLabel);
}
return el;
}
/**
* Handle a click on the menu item, and set it to selected
*
* @method handleClick
*/
handleClick() {
this.updateSiblings_();
const qualityLevels = this.player().qualityLevels();
const currentlySelected = qualityLevels.selectedIndex;
for (let i = 0, l = qualityLevels.length; i < l; i++) {
// do not disable the currently selected quality until the end to prevent
// playlist selection from selecting something new until we've enabled/disabled
// all the quality levels
if (i !== currentlySelected) {
qualityLevels[i].enabled = false;
}
}
for (let i = 0, l = this.levels_.length; i < l; i++) {
qualityLevels[this.levels_[i]].enabled = true;
}
// Disable the quality level that was selected before the click if it is not
// associated with this menu item
if (currentlySelected !== -1 && this.levels_.indexOf(currentlySelected) === -1) {
qualityLevels[currentlySelected].enabled = false;
}
}
/**
* Handle a change event from the QualityLevelList
*
* @method handleQualityChange
*/
handleQualityChange() {
const qualityLevels = this.player().qualityLevels();
const active = this.levels_.indexOf(qualityLevels.selectedIndex) > -1;
this.selected(active);
}
/**
* Set this menu item as selected or not
*
* @param {boolean} active
* True if the active quality level is controlled by this item
* @method selected
*/
selected(active) {
if (!this.selectable) {
return;
}
if (this.selected_) {
const hasSubLabel = this.options_.subLabel;
this.addClass('vjs-selected');
this.el_.setAttribute('aria-checked', 'true');
// aria-checked isn't fully supported by browsers/screen readers,
// so indicate selected state to screen reader in the control text.
this.controlText(this.localize(hasSubLabel ? 'selected,' : 'selected'));
const controlBar = this.player().controlBar;
const menuButton = controlBar.getChild('QualityMenuButton');
if (!active) {
// This menu item is manually selected but the current playing quality level
// is NOT associated with this menu item. This can happen if the quality hasnt
// changed yet or something went wrong with rendition selection such as failed
// server responses for playlists
menuButton.addClass('vjs-quality-menu-button-waiting');
} else {
menuButton.removeClass('vjs-quality-menu-button-waiting');
}
} else {
this.removeClass('vjs-selected');
this.el_.setAttribute('aria-checked', 'false');
// Indicate un-selected state to screen reader
// Note that a space clears out the selected state text
this.controlText(this.options_.controlText);
}
}
/**
* Sets this QualityMenuItem to be selected and deselects the other items
*
* @method updateSiblings_
*/
updateSiblings_() {
const qualityLevels = this.player().qualityLevels();
const controlBar = this.player().controlBar;
const menuItems = controlBar.getChild('QualityMenuButton').items;
for (let i = 0, l = menuItems.length; i < l; i++) {
const item = menuItems[i];
const active = item.levels_.indexOf(qualityLevels.selectedIndex) > -1;
item.selected_ = item === this;
item.selected(active);
}
}
}
/**
* @file quality-menu-button.js
*/
const MenuButton = video_js__WEBPACK_IMPORTED_MODULE_0___default().getComponent('MenuButton');
/**
* Checks whether any of the QualityLevels in a QualityLevelList have resolution information
*
* @param {QualityLevelList} qualityLevelList
* The list of QualityLevels
* @return {boolean}
* True if any levels have resolution information, false if none have
* @function hasResolutionInfo
*/
const hasResolutionInfo = function (qualityLevelList) {
return Array.from(qualityLevelList).some(level => level.height);
};
/**
* Determines the appropriate sub label for the given lines of resolution
*
* @param {number} lines
* The horizontal lines of resolution
* @return {string}
* sub label for given resolution
* @function getSubLabel
*/
const getSubLabel = function (lines) {
if (lines >= 2160) {
return '4K';
}
if (lines >= 720) {
return 'HD';
}
return '';
};
/**
* The component for controlling the quality menu
*
* @extends MenuButton
* @class QualityMenuButton
*/
class QualityMenuButton extends MenuButton {
/**
* Creates a QualityMenuButton
*
* @param {Player|Object} player
* Main Player
* @param {Object} [options]
* Options for QualityMenuButton
*/
constructor(player, options = {}) {
super(player, options);
this.el_.setAttribute('aria-label', this.localize('Quality Levels'));
this.controlText('Quality Levels');
if (!player.options().experimentalSvgIcons) {
this.$('.vjs-icon-placeholder').classList.add('vjs-icon-cog');
}
this.setIcon('cog');
this.qualityLevels_ = player.qualityLevels();
this.update = this.update.bind(this);
this.hide = this.hide.bind(this);
this.handleQualityChange_ = this.handleQualityChange_.bind(this);
this.firstChangeHandler_ = this.firstChangeHandler_.bind(this);
this.enableDefaultResolution_ = this.enableDefaultResolution_.bind(this);
this.on(this.qualityLevels_, 'addqualitylevel', this.update);
this.on(this.qualityLevels_, 'removequalitylevel', this.update);
this.on(this.qualityLevels_, 'change', this.handleQualityChange_);
// TODO: Remove this and the `defaultResolution` option once videojs/http-streaming supports comparable functionality
this.one(this.qualityLevels_, 'change', this.firstChangeHandler_);
player.on('adstart', this.hide);
player.on(['adend', 'adtimeout'], this.update);
this.update();
this.on('dispose', () => {
this.off(this.qualityLevels_, 'addqualitylevel', this.update);
this.off(this.qualityLevels_, 'removequalitylevel', this.update);
this.off(this.qualityLevels_, 'change', this.handleQualityChange_);
this.off(this.qualityLevels_, 'change', this.firstChangeHandler_);
player.off('adstart', this.hide);
player.off(['adend', 'adtimeout'], this.update);
player.off('loadedmetadata', this.enableDefaultResolution_);
});
}
/**
* Allow sub components to stack CSS class names
*
* @return {string}
* The constructed class name
* @method buildWrapperCSSClass
*/
buildWrapperCSSClass() {
return `vjs-quality-menu-wrapper ${super.buildWrapperCSSClass()}`;
}
/**
* Allow sub components to stack CSS class names
*
* @return {string}
* The constructed class name
* @method buildCSSClass
*/
buildCSSClass() {
return `vjs-quality-menu-button ${super.buildCSSClass()}`;
}
/**
* Create the list of menu items.
*
* @return {Array}
* The list of menu items
* @method createItems
*/
createItems() {
const items = [];
if (!(this.qualityLevels_ && this.qualityLevels_.length)) {
return items;
}
let groups;
if (this.options_.useResolutionLabels && hasResolutionInfo(this.qualityLevels_)) {
groups = this.groupByResolution_();
this.addClass('vjs-quality-menu-button-use-resolution');
} else {
groups = this.groupByBitrate_();
this.removeClass('vjs-quality-menu-button-use-resolution');
}
// if there is only 1 or 0 menu items, we should just return an empty list so
// the ui does not appear when there are no options. We consider 1 to be no options
// since Auto will have the same behavior as selecting the only other option,
// so it is as effective as not having any options.
if (groups.length <= 1) {
return [];
}
groups.forEach(group => {
if (group.levels.length) {
group.selectable = true;
items.push(new QualityMenuItem(this.player(), group));
}
});
// Add the Auto menu item
const auto = new QualityMenuItem(this.player(), {
levels: Array.prototype.map.call(this.qualityLevels_, (level, i) => i),
label: this.localize('Auto'),
controlText: '',
active: true,
selected: true,
selectable: true
});
this.autoMenuItem_ = auto;
items.push(auto);
return items;
}
/**
* Group quality levels by lines of resolution
*
* @return {Array}
* Array of each group
* @method groupByResolution_
*/
groupByResolution_() {
const groups = {};
const order = [];
for (let i = 0, l = this.qualityLevels_.length; i < l; i++) {
const level = this.qualityLevels_[i];
const active = this.qualityLevels_.selectedIndex === i;
const lines = level.height;
// Do not include an audio-only level
if (!lines) {
continue;
}
let label;
if (this.options_.resolutionLabelBitrates) {
const kbRate = Math.round(level.bitrate / 1000);
label = `${lines}p @ ${kbRate} kbps`;
} else {
label = lines + 'p';
}
if (!groups[label]) {
const subLabel = getSubLabel(lines);
groups[label] = {
levels: [],
label,
controlText: '',
subLabel
};
order.push({
label,
lines
});
}
if (active) {
groups[label].active = true;
}
groups[label].levels.push(i);
}
// Sort from High to Low
order.sort((a, b) => b.lines - a.lines);
const sortedGroups = [];
order.forEach(group => {
sortedGroups.push(groups[group.label]);
});
return sortedGroups;
}
/**
* Group quality levels by bitrate into SD and HD buckets
*
* @return {Array}
* Array of each group
* @method groupByBitrate_
*/
groupByBitrate_() {
// groups[0] for HD, groups[1] for SD, since we want sorting from high to low\
const groups = [{
levels: [],
label: 'HD',
controlText: 'High Definition'
}, {
levels: [],
label: 'SD',
controlText: 'Standard Definition'
}];
for (let i = 0, l = this.qualityLevels_.length; i < l; i++) {
const level = this.qualityLevels_[i];
const active = this.qualityLevels_.selectedIndex === i;
let group;
if (level.bitrate < this.options_.sdBitrateLimit) {
group = groups[1];
} else {
group = groups[0];
}
if (active) {
group.active = true;
}
group.levels.push(i);
}
if (!groups[0].levels.length || !groups[1].levels.length) {
// Either HD or SD do not have any quality levels, we should just return an empty
// list so the ui does not appear when there are no options. We consider 1
// to be no options since Auto will have the same behavior as selecting the only
// other option, so it is as effective as not having any options.
return [];
}
return groups;
}
/**
* Handle a change event from the QualityLevelList
*
* @method handleQualityChange_
*/
handleQualityChange_() {
const selected = this.qualityLevels_[this.qualityLevels_.selectedIndex];
const useResolution = this.options_.useResolutionLabels && hasResolutionInfo(this.qualityLevels_);
let subLabel = '';
if (selected) {
if (useResolution) {
subLabel = getSubLabel(selected.height);
} else if (selected.bitrate >= this.options_.sdBitrateLimit) {
subLabel = 'HD';
}
}
if (subLabel === 'HD') {
this.addClass('vjs-quality-menu-button-HD-flag');
this.removeClass('vjs-quality-menu-button-4K-flag');
} else if (subLabel === '4K') {
this.removeClass('vjs-quality-menu-button-HD-flag');
this.addClass('vjs-quality-menu-b