UNPKG

dashjs

Version:

A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.

1,215 lines (1,072 loc) 491 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["dashjs"] = factory(); else root["dashjs"] = factory(); })(self, function() { return /******/ (function() { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./src/core/Debug.js": /*!***************************!*\ !*** ./src/core/Debug.js ***! \***************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _EventBus__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./EventBus */ "./src/core/EventBus.js"); /* harmony import */ var _events_Events__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./events/Events */ "./src/core/events/Events.js"); /* harmony import */ var _FactoryMaker__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./FactoryMaker */ "./src/core/FactoryMaker.js"); /** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ var LOG_LEVEL_NONE = 0; var LOG_LEVEL_FATAL = 1; var LOG_LEVEL_ERROR = 2; var LOG_LEVEL_WARNING = 3; var LOG_LEVEL_INFO = 4; var LOG_LEVEL_DEBUG = 5; /** * @module Debug * @param {object} config * @ignore */ function Debug(config) { config = config || {}; var context = this.context; var eventBus = (0,_EventBus__WEBPACK_IMPORTED_MODULE_0__["default"])(context).getInstance(); var settings = config.settings; var logFn = []; var instance, showLogTimestamp, showCalleeName, startTime; function setup() { showLogTimestamp = true; showCalleeName = true; startTime = new Date().getTime(); if (typeof window !== 'undefined' && window.console) { logFn[LOG_LEVEL_FATAL] = getLogFn(window.console.error); logFn[LOG_LEVEL_ERROR] = getLogFn(window.console.error); logFn[LOG_LEVEL_WARNING] = getLogFn(window.console.warn); logFn[LOG_LEVEL_INFO] = getLogFn(window.console.info); logFn[LOG_LEVEL_DEBUG] = getLogFn(window.console.debug); } } function getLogFn(fn) { if (fn && fn.bind) { return fn.bind(window.console); } // if not define, return the default function for reporting logs return window.console.log.bind(window.console); } /** * Retrieves a logger which can be used to write logging information in browser console. * @param {object} instance Object for which the logger is created. It is used * to include calle object information in log messages. * @memberof module:Debug * @returns {Logger} * @instance */ function getLogger(instance) { return { fatal: fatal.bind(instance), error: error.bind(instance), warn: warn.bind(instance), info: info.bind(instance), debug: debug.bind(instance) }; } /** * Prepends a timestamp in milliseconds to each log message. * @param {boolean} value Set to true if you want to see a timestamp in each log message. * @default LOG_LEVEL_WARNING * @memberof module:Debug * @instance */ function setLogTimestampVisible(value) { showLogTimestamp = value; } /** * Prepends the callee object name, and media type if available, to each log message. * @param {boolean} value Set to true if you want to see the callee object name and media type in each log message. * @default true * @memberof module:Debug * @instance */ function setCalleeNameVisible(value) { showCalleeName = value; } function fatal() { for (var _len = arguments.length, params = new Array(_len), _key = 0; _key < _len; _key++) { params[_key] = arguments[_key]; } doLog.apply(void 0, [LOG_LEVEL_FATAL, this].concat(params)); } function error() { for (var _len2 = arguments.length, params = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { params[_key2] = arguments[_key2]; } doLog.apply(void 0, [LOG_LEVEL_ERROR, this].concat(params)); } function warn() { for (var _len3 = arguments.length, params = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { params[_key3] = arguments[_key3]; } doLog.apply(void 0, [LOG_LEVEL_WARNING, this].concat(params)); } function info() { for (var _len4 = arguments.length, params = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { params[_key4] = arguments[_key4]; } doLog.apply(void 0, [LOG_LEVEL_INFO, this].concat(params)); } function debug() { for (var _len5 = arguments.length, params = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { params[_key5] = arguments[_key5]; } doLog.apply(void 0, [LOG_LEVEL_DEBUG, this].concat(params)); } function doLog(level, _this) { var message = ''; var logTime = null; if (showLogTimestamp) { logTime = new Date().getTime(); message += '[' + (logTime - startTime) + ']'; } if (showCalleeName && _this && _this.getClassName) { message += '[' + _this.getClassName() + ']'; if (_this.getType) { message += '[' + _this.getType() + ']'; } } if (message.length > 0) { message += ' '; } for (var _len6 = arguments.length, params = new Array(_len6 > 2 ? _len6 - 2 : 0), _key6 = 2; _key6 < _len6; _key6++) { params[_key6 - 2] = arguments[_key6]; } Array.apply(null, params).forEach(function (item) { message += item + ' '; }); // log to console if the log level is high enough if (logFn[level] && settings && settings.get().debug.logLevel >= level) { logFn[level](message); } // send log event regardless of log level if (settings && settings.get().debug.dispatchEvent) { eventBus.trigger(_events_Events__WEBPACK_IMPORTED_MODULE_1__["default"].LOG, { message: message, level: level }); } } instance = { getLogger: getLogger, setLogTimestampVisible: setLogTimestampVisible, setCalleeNameVisible: setCalleeNameVisible }; setup(); return instance; } Debug.__dashjs_factory_name = 'Debug'; var factory = _FactoryMaker__WEBPACK_IMPORTED_MODULE_2__["default"].getSingletonFactory(Debug); factory.LOG_LEVEL_NONE = LOG_LEVEL_NONE; factory.LOG_LEVEL_FATAL = LOG_LEVEL_FATAL; factory.LOG_LEVEL_ERROR = LOG_LEVEL_ERROR; factory.LOG_LEVEL_WARNING = LOG_LEVEL_WARNING; factory.LOG_LEVEL_INFO = LOG_LEVEL_INFO; factory.LOG_LEVEL_DEBUG = LOG_LEVEL_DEBUG; _FactoryMaker__WEBPACK_IMPORTED_MODULE_2__["default"].updateSingletonFactory(Debug.__dashjs_factory_name, factory); /* harmony default export */ __webpack_exports__["default"] = (factory); /***/ }), /***/ "./src/core/EventBus.js": /*!******************************!*\ !*** ./src/core/EventBus.js ***! \******************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _FactoryMaker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./FactoryMaker */ "./src/core/FactoryMaker.js"); /* harmony import */ var _streaming_MediaPlayerEvents__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../streaming/MediaPlayerEvents */ "./src/streaming/MediaPlayerEvents.js"); /** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ var EVENT_PRIORITY_LOW = 0; var EVENT_PRIORITY_HIGH = 5000; function EventBus() { var handlers = {}; function on(type, listener, scope) { var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; if (!type) { throw new Error('event type cannot be null or undefined'); } if (!listener || typeof listener !== 'function') { throw new Error('listener must be a function: ' + listener); } var priority = options.priority || EVENT_PRIORITY_LOW; if (getHandlerIdx(type, listener, scope) >= 0) return; handlers[type] = handlers[type] || []; var handler = { callback: listener, scope: scope, priority: priority }; if (scope && scope.getStreamId) { handler.streamId = scope.getStreamId(); } if (scope && scope.getType) { handler.mediaType = scope.getType(); } if (options && options.mode) { handler.mode = options.mode; } var inserted = handlers[type].some(function (item, idx) { if (item && priority > item.priority) { handlers[type].splice(idx, 0, handler); return true; } }); if (!inserted) { handlers[type].push(handler); } } function off(type, listener, scope) { if (!type || !listener || !handlers[type]) return; var idx = getHandlerIdx(type, listener, scope); if (idx < 0) return; handlers[type][idx] = null; } function trigger(type) { var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var filters = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (!type || !handlers[type]) return; payload = payload || {}; if (payload.hasOwnProperty('type')) throw new Error('\'type\' is a reserved word for event dispatching'); payload.type = type; if (filters.streamId) { payload.streamId = filters.streamId; } if (filters.mediaType) { payload.mediaType = filters.mediaType; } handlers[type].filter(function (handler) { if (!handler) { return false; } if (filters.streamId && handler.streamId && handler.streamId !== filters.streamId) { return false; } if (filters.mediaType && handler.mediaType && handler.mediaType !== filters.mediaType) { return false; } // This is used for dispatching DASH events. By default we use the onStart mode. Consequently we filter everything that has a non matching mode and the onReceive events for handlers that did not specify a mode. if (filters.mode && handler.mode && handler.mode !== filters.mode || !handler.mode && filters.mode && filters.mode === _streaming_MediaPlayerEvents__WEBPACK_IMPORTED_MODULE_1__["default"].EVENT_MODE_ON_RECEIVE) { return false; } return true; }).forEach(function (handler) { return handler && handler.callback.call(handler.scope, payload); }); } function getHandlerIdx(type, listener, scope) { var idx = -1; if (!handlers[type]) return idx; handlers[type].some(function (item, index) { if (item && item.callback === listener && (!scope || scope === item.scope)) { idx = index; return true; } }); return idx; } function reset() { handlers = {}; } var instance = { on: on, off: off, trigger: trigger, reset: reset }; return instance; } EventBus.__dashjs_factory_name = 'EventBus'; var factory = _FactoryMaker__WEBPACK_IMPORTED_MODULE_0__["default"].getSingletonFactory(EventBus); factory.EVENT_PRIORITY_LOW = EVENT_PRIORITY_LOW; factory.EVENT_PRIORITY_HIGH = EVENT_PRIORITY_HIGH; _FactoryMaker__WEBPACK_IMPORTED_MODULE_0__["default"].updateSingletonFactory(EventBus.__dashjs_factory_name, factory); /* harmony default export */ __webpack_exports__["default"] = (factory); /***/ }), /***/ "./src/core/FactoryMaker.js": /*!**********************************!*\ !*** ./src/core/FactoryMaker.js ***! \**********************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** * @module FactoryMaker * @ignore */ var FactoryMaker = function () { var instance; var singletonContexts = []; var singletonFactories = {}; var classFactories = {}; function extend(name, childInstance, override, context) { if (!context[name] && childInstance) { context[name] = { instance: childInstance, override: override }; } } /** * Use this method from your extended object. this.factory is injected into your object. * this.factory.getSingletonInstance(this.context, 'VideoModel') * will return the video model for use in the extended object. * * @param {Object} context - injected into extended object as this.context * @param {string} className - string name found in all dash.js objects * with name __dashjs_factory_name Will be at the bottom. Will be the same as the object's name. * @returns {*} Context aware instance of specified singleton name. * @memberof module:FactoryMaker * @instance */ function getSingletonInstance(context, className) { for (var i in singletonContexts) { var obj = singletonContexts[i]; if (obj.context === context && obj.name === className) { return obj.instance; } } return null; } /** * Use this method to add an singleton instance to the system. Useful for unit testing to mock objects etc. * * @param {Object} context * @param {string} className * @param {Object} instance * @memberof module:FactoryMaker * @instance */ function setSingletonInstance(context, className, instance) { for (var i in singletonContexts) { var obj = singletonContexts[i]; if (obj.context === context && obj.name === className) { singletonContexts[i].instance = instance; return; } } singletonContexts.push({ name: className, context: context, instance: instance }); } /** * Use this method to remove all singleton instances associated with a particular context. * * @param {Object} context * @memberof module:FactoryMaker * @instance */ function deleteSingletonInstances(context) { singletonContexts = singletonContexts.filter(function (x) { return x.context !== context; }); } /*------------------------------------------------------------------------------------------*/ // Factories storage Management /*------------------------------------------------------------------------------------------*/ function getFactoryByName(name, factoriesArray) { return factoriesArray[name]; } function updateFactory(name, factory, factoriesArray) { if (name in factoriesArray) { factoriesArray[name] = factory; } } /*------------------------------------------------------------------------------------------*/ // Class Factories Management /*------------------------------------------------------------------------------------------*/ function updateClassFactory(name, factory) { updateFactory(name, factory, classFactories); } function getClassFactoryByName(name) { return getFactoryByName(name, classFactories); } function getClassFactory(classConstructor) { var factory = getFactoryByName(classConstructor.__dashjs_factory_name, classFactories); if (!factory) { factory = function factory(context) { if (context === undefined) { context = {}; } return { create: function create() { return merge(classConstructor, context, arguments); } }; }; classFactories[classConstructor.__dashjs_factory_name] = factory; // store factory } return factory; } /*------------------------------------------------------------------------------------------*/ // Singleton Factory MAangement /*------------------------------------------------------------------------------------------*/ function updateSingletonFactory(name, factory) { updateFactory(name, factory, singletonFactories); } function getSingletonFactoryByName(name) { return getFactoryByName(name, singletonFactories); } function getSingletonFactory(classConstructor) { var factory = getFactoryByName(classConstructor.__dashjs_factory_name, singletonFactories); if (!factory) { factory = function factory(context) { var instance; if (context === undefined) { context = {}; } return { getInstance: function getInstance() { // If we don't have an instance yet check for one on the context if (!instance) { instance = getSingletonInstance(context, classConstructor.__dashjs_factory_name); } // If there's no instance on the context then create one if (!instance) { instance = merge(classConstructor, context, arguments); singletonContexts.push({ name: classConstructor.__dashjs_factory_name, context: context, instance: instance }); } return instance; } }; }; singletonFactories[classConstructor.__dashjs_factory_name] = factory; // store factory } return factory; } function merge(classConstructor, context, args) { var classInstance; var className = classConstructor.__dashjs_factory_name; var extensionObject = context[className]; if (extensionObject) { var extension = extensionObject.instance; if (extensionObject.override) { //Override public methods in parent but keep parent. classInstance = classConstructor.apply({ context: context }, args); extension = extension.apply({ context: context, factory: instance, parent: classInstance }, args); for (var prop in extension) { if (classInstance.hasOwnProperty(prop)) { classInstance[prop] = extension[prop]; } } } else { //replace parent object completely with new object. Same as dijon. return extension.apply({ context: context, factory: instance }, args); } } else { // Create new instance of the class classInstance = classConstructor.apply({ context: context }, args); } // Add getClassName function to class instance prototype (used by Debug) classInstance.getClassName = function () { return className; }; return classInstance; } instance = { extend: extend, getSingletonInstance: getSingletonInstance, setSingletonInstance: setSingletonInstance, deleteSingletonInstances: deleteSingletonInstances, getSingletonFactory: getSingletonFactory, getSingletonFactoryByName: getSingletonFactoryByName, updateSingletonFactory: updateSingletonFactory, getClassFactory: getClassFactory, getClassFactoryByName: getClassFactoryByName, updateClassFactory: updateClassFactory }; return instance; }(); /* harmony default export */ __webpack_exports__["default"] = (FactoryMaker); /***/ }), /***/ "./src/core/Settings.js": /*!******************************!*\ !*** ./src/core/Settings.js ***! \******************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _FactoryMaker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./FactoryMaker */ "./src/core/FactoryMaker.js"); /* harmony import */ var _Utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Utils.js */ "./src/core/Utils.js"); /* harmony import */ var _core_Debug__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../core/Debug */ "./src/core/Debug.js"); /* harmony import */ var _streaming_constants_Constants__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../streaming/constants/Constants */ "./src/streaming/constants/Constants.js"); /* harmony import */ var _streaming_vo_metrics_HTTPRequest__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../streaming/vo/metrics/HTTPRequest */ "./src/streaming/vo/metrics/HTTPRequest.js"); /* harmony import */ var _EventBus__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./EventBus */ "./src/core/EventBus.js"); /* harmony import */ var _events_Events__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./events/Events */ "./src/core/events/Events.js"); function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** @module Settings * @description Define the configuration parameters of Dash.js MediaPlayer. * @see {@link module:Settings~PlayerSettings PlayerSettings} for further information about the supported configuration properties. */ /** * @typedef {Object} PlayerSettings * @property {module:Settings~DebugSettings} [debug] * Debug related settings. * @property {module:Settings~ErrorSettings} [errors] * Error related settings * @property {module:Settings~StreamingSettings} [streaming] * Streaming related settings. * @example * * // Full settings object * settings = { * debug: { * logLevel: Debug.LOG_LEVEL_WARNING, * dispatchEvent: false * }, * streaming: { * abandonLoadTimeout: 10000, * wallclockTimeUpdateInterval: 100, * manifestUpdateRetryInterval: 100, * liveUpdateTimeThresholdInMilliseconds: 0, * cacheInitSegments: false, * applyServiceDescription: true, * applyProducerReferenceTime: true, * applyContentSteering: true, * eventControllerRefreshDelay: 100, * enableManifestDurationMismatchFix: true, * enableManifestTimescaleMismatchFix: false, * parseInbandPrft: false, * capabilities: { * filterUnsupportedEssentialProperties: true, * useMediaCapabilitiesApi: false * }, * timeShiftBuffer: { * calcFromSegmentTimeline: false, * fallbackToSegmentTimeline: true * }, * metrics: { * maxListDepth: 100 * }, * delay: { * liveDelayFragmentCount: NaN, * liveDelay: NaN, * useSuggestedPresentationDelay: true * }, * protection: { * keepProtectionMediaKeys: false, * ignoreEmeEncryptedEvent: false, * detectPlayreadyMessageFormat: true, * }, * buffer: { * enableSeekDecorrelationFix: false, * fastSwitchEnabled: true, * flushBufferAtTrackSwitch: false, * reuseExistingSourceBuffers: true, * bufferPruningInterval: 10, * bufferToKeep: 20, * bufferTimeAtTopQuality: 30, * bufferTimeAtTopQualityLongForm: 60, * initialBufferLevel: NaN, * stableBufferTime: 12, * longFormContentDurationThreshold: 600, * stallThreshold: 0.3, * useAppendWindow: true, * setStallState: true, * avoidCurrentTimeRangePruning: false, * useChangeTypeForTrackSwitch: true, * mediaSourceDurationInfinity: true, * resetSourceBuffersForTrackSwitch: false * }, * gaps: { * jumpGaps: true, * jumpLargeGaps: true, * smallGapLimit: 1.5, * threshold: 0.3, * enableSeekFix: true, * enableStallFix: false, * stallSeek: 0.1 * }, * utcSynchronization: { * enabled: true, * useManifestDateHeaderTimeSource: true, * backgroundAttempts: 2, * timeBetweenSyncAttempts: 30, * maximumTimeBetweenSyncAttempts: 600, * minimumTimeBetweenSyncAttempts: 2, * timeBetweenSyncAttemptsAdjustmentFactor: 2, * maximumAllowedDrift: 100, * enableBackgroundSyncAfterSegmentDownloadError: true, * defaultTimingSource: { * scheme: 'urn:mpeg:dash:utc:http-xsdate:2014', * value: 'http://time.akamai.com/?iso&ms' * } * }, * scheduling: { * defaultTimeout: 500, * lowLatencyTimeout: 0, * scheduleWhilePaused: true * }, * text: { * defaultEnabled: true, * dispatchForManualRendering: false, * extendSegmentedCues: true, * imsc: { * displayForcedOnlyMode: false, * enableRollUp: true * }, * webvtt: { * customRenderingEnabled: false * } * }, * liveCatchup: { * maxDrift: NaN, * playbackRate: {min: NaN, max: NaN}, * playbackBufferMin: 0.5, * enabled: null, * mode: Constants.LIVE_CATCHUP_MODE_DEFAULT * }, * lastBitrateCachingInfo: { enabled: true, ttl: 360000 }, * lastMediaSettingsCachingInfo: { enabled: true, ttl: 360000 }, * saveLastMediaSettingsForCurrentStreamingSession: true, * cacheLoadThresholds: { video: 50, audio: 5 }, * trackSwitchMode: { * audio: Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE, * video: Constants.TRACK_SWITCH_MODE_NEVER_REPLACE * }, * selectionModeForInitialTrack: Constants.TRACK_SELECTION_MODE_HIGHEST_SELECTION_PRIORITY, * fragmentRequestTimeout: 20000, * fragmentRequestProgressTimeout: -1, * manifestRequestTimeout: 10000, * retryIntervals: { * [HTTPRequest.MPD_TYPE]: 500, * [HTTPRequest.XLINK_EXPANSION_TYPE]: 500, * [HTTPRequest.MEDIA_SEGMENT_TYPE]: 1000, * [HTTPRequest.INIT_SEGMENT_TYPE]: 1000, * [HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE]: 1000, * [HTTPRequest.INDEX_SEGMENT_TYPE]: 1000, * [HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE]: 1000, * [HTTPRequest.LICENSE]: 1000, * [HTTPRequest.OTHER_TYPE]: 1000, * lowLatencyReductionFactor: 10 * }, * retryAttempts: { * [HTTPRequest.MPD_TYPE]: 3, * [HTTPRequest.XLINK_EXPANSION_TYPE]: 1, * [HTTPRequest.MEDIA_SEGMENT_TYPE]: 3, * [HTTPRequest.INIT_SEGMENT_TYPE]: 3, * [HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE]: 3, * [HTTPRequest.INDEX_SEGMENT_TYPE]: 3, * [HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE]: 3, * [HTTPRequest.LICENSE]: 3, * [HTTPRequest.OTHER_TYPE]: 3, * lowLatencyMultiplyFactor: 5 * }, * abr: { * movingAverageMethod: Constants.MOVING_AVERAGE_SLIDING_WINDOW, * ABRStrategy: Constants.ABR_STRATEGY_DYNAMIC, * additionalAbrRules: { * insufficientBufferRule: true, * switchHistoryRule: true, * droppedFramesRule: true, * abandonRequestsRule: true * }, * abrRulesParameters: { * abandonRequestsRule: { * graceTimeThreshold: 500, * abandonMultiplier: 1.8, * minLengthToAverage: 5 * } * }, * bandwidthSafetyFactor: 0.9, * useDefaultABRRules: true, * useDeadTimeLatency: true, * limitBitrateByPortal: false, * usePixelRatioInLimitBitrateByPortal: false, * maxBitrate: { audio: -1, video: -1 }, * minBitrate: { audio: -1, video: -1 }, * maxRepresentationRatio: { audio: 1, video: 1 }, * initialBitrate: { audio: -1, video: -1 }, * initialRepresentationRatio: { audio: -1, video: -1 }, * autoSwitchBitrate: { audio: true, video: true }, * fetchThroughputCalculationMode: Constants.ABR_FETCH_THROUGHPUT_CALCULATION_DOWNLOADED_DATA * }, * cmcd: { * enabled: false, * sid: null, * cid: null, * rtp: null, * rtpSafetyFactor: 5, * mode: Constants.CMCD_MODE_QUERY, * enabledKeys: ['br', 'd', 'ot', 'tb' , 'bl', 'dl', 'mtp', 'nor', 'nrr', 'su' , 'bs', 'rtp' , 'cid', 'pr', 'sf', 'sid', 'st', 'v'] * }, * cmsd: { * enabled: false, * abr: { * applyMb: false, * etpWeightRatio: 0 * } * } * }, * errors: { * recoverAttempts: { * mediaErrorDecode: 5 * } * } * } */ /** * @typedef {Object} TimeShiftBuffer * @property {boolean} [calcFromSegmentTimeline=false] * Enable calculation of the DVR window for SegmentTimeline manifests based on the entries in \<SegmentTimeline\>. * * @property {boolean} [fallbackToSegmentTimeline=true] * In case the MPD uses \<SegmentTimeline\ and no segment is found within the DVR window the DVR window is calculated based on the entries in \<SegmentTimeline\>. */ /** * @typedef {Object} LiveDelay * @property {number} [liveDelayFragmentCount=NaN] * Changing this value will lower or increase live stream latency. * * The detected segment duration will be multiplied by this value to define a time in seconds to delay a live stream from the live edge. * * Lowering this value will lower latency but may decrease the player's ability to build a stable buffer. * @property {number} [liveDelay=NaN] * Equivalent in seconds of setLiveDelayFragmentCount. * * Lowering this value will lower latency but may decrease the player's ability to build a stable buffer. * * This value should be less than the manifest duration by a couple of segment durations to avoid playback issues. * * If set, this parameter will take precedence over setLiveDelayFragmentCount and manifest info. * @property {boolean} [useSuggestedPresentationDelay=true] * Set to true if you would like to overwrite the default live delay and honor the SuggestedPresentationDelay attribute in by the manifest. */ /** * @typedef {Object} Buffer * @property {boolean} [enableSeekDecorrelationFix=false] * Enables a workaround for playback start on some devices, e.g. WebOS 4.9. * It is necessary because some browsers do not support setting currentTime on video element to a value that is outside of current buffer. * * If you experience unexpected seeking triggered by BufferController, you can try setting this value to false. * @property {boolean} [fastSwitchEnabled=true] * When enabled, after an ABR up-switch in quality, instead of requesting and appending the next fragment at the end of the current buffer range it is requested and appended closer to the current time. * * When enabled, The maximum time to render a higher quality is current time + (1.5 * fragment duration). * * Note, When ABR down-switch is detected, we appended the lower quality at the end of the buffer range to preserve the * higher quality media for as long as possible. * * If enabled, it should be noted there are a few cases when the client will not replace inside buffer range but rather just append at the end. * 1. When the buffer level is less than one fragment duration. * 2. The client is in an Abandonment State due to recent fragment abandonment event. * * Known issues: * 1. In IE11 with auto switching off, if a user switches to a quality they can not download in time the fragment may be appended in the same range as the playhead or even in the past, in IE11 it may cause a stutter or stall in playback. * @property {boolean} [flushBufferAtTrackSwitch=false] * When enabled, after a track switch and in case buffer is being replaced, the video element is flushed (seek at current playback time) once a segment of the new track is appended in buffer in order to force video decoder to play new track. * * This can be required on some devices like GoogleCast devices to make track switching functional. * * Otherwise track switching will be effective only once after previous buffered track is fully consumed. * @property {boolean} [reuseExistingSourceBuffers=true] * Enable reuse of existing MediaSource Sourcebuffers during period transition. * @property {number} [bufferPruningInterval=10] * The interval of pruning buffer in seconds. * @property {number} [bufferToKeep=20] * This value influences the buffer pruning logic. * * Allows you to modify the buffer that is kept in source buffer in seconds. * 0|-----------bufferToPrune-----------|-----bufferToKeep-----|currentTime| * @property {number} [bufferTimeAtTopQuality=30] * The time that the internal buffer target will be set to once playing the top quality. * * If there are multiple bitrates in your adaptation, and the media is playing at the highest bitrate, then we try to build a larger buffer at the top quality to increase stability and to maintain media quality. * @property {number} [bufferTimeAtTopQualityLongForm=60] * The time that the internal buffer target will be set to once playing the top quality for long form content. * @property {number} [longFormContentDurationThreshold=600] * The threshold which defines if the media is considered long form content. * * This will directly affect the buffer targets when playing back at the top quality. * @property {number} [initialBufferLevel=NaN] * Initial buffer level before playback starts * @property {number} [stableBufferTime=12] * The time that the internal buffer target will be set to post startup/seeks (NOT top quality). * * When the time is set higher than the default you will have to wait longer to see automatic bitrate switches but will have a larger buffer which will increase stability. * @property {number} [stallThreshold=0.3] * Stall threshold used in BufferController.js to determine whether a track should still be changed and which buffer range to prune. * @property {boolean} [useAppendWindow=true] * Specifies if the appendWindow attributes of the MSE SourceBuffers should be set according to content duration from manifest. * @property {boolean} [setStallState=true] * Specifies if we fire manual waiting events once the stall threshold is reached * @property {boolean} [avoidCurrentTimeRangePruning=false] * Avoids pruning of the buffered range that contains the current playback time. * * That buffered range is likely to have been enqueued for playback. Pruning it causes a flush and reenqueue in WPE and WebKitGTK based browsers. This stresses the video decoder and can cause stuttering on embedded platforms. * @property {boolean} [useChangeTypeForTrackSwitch=true] * If this flag is set to true then dash.js will use the MSE v.2 API call "changeType()" before switching to a different track. * Note that some platforms might not implement the changeType functio. dash.js is checking for the availability before trying to call it. * @property {boolean} [mediaSourceDurationInfinity=true] * If this flag is set to true then dash.js will allow `Infinity` to be set as the MediaSource duration otherwise the duration will be set to `Math.pow(2,32)` instead of `Infinity` to allow appending segments indefinitely. * Some platforms such as WebOS 4.x have issues with seeking when duration is set to `Infinity`, setting this flag to false resolve this. * @property {boolean} [resetSourceBuffersForTrackSwitch=false] * When switching to a track that is not compatible with the currently active MSE SourceBuffers, MSE will be reset. This happens when we switch codecs on a system * that does not properly implement "changeType()", such as webOS 4.0 and before. */ /** * @typedef {Object} module:Settings~AudioVideoSettings * @property {number|boolean|string} [audio] * Configuration for audio media type of tracks. * @property {number|boolean|string} [video] * Configuration for video media type of tracks. */ /** * @typedef {Object} DebugSettings * @property {number} [logLevel=dashjs.Debug.LOG_LEVEL_WARNING] * Sets up the log level. The levels are cumulative. * * For example, if you set the log level to dashjs.Debug.LOG_LEVEL_WARNING all warnings, errors and fatals will be logged. * * Possible values. * * - dashjs.Debug.LOG_LEVEL_NONE * No message is written in the browser console. * * - dashjs.Debug.LOG_LEVEL_FATAL * Log fatal errors. * An error is considered fatal when it causes playback to fail completely. * * - dashjs.Debug.LOG_LEVEL_ERROR * Log error messages. * * - dashjs.Debug.LOG_LEVEL_WARNING * Log warning messages. * * - dashjs.Debug.LOG_LEVEL_INFO * Log info messages. * * - dashjs.Debug.LOG_LEVEL_DEBUG * Log debug messages. * @property {boolean} [dispatchEvent=false] * Enable to trigger a Events.LOG event whenever log output is generated. * * Note this will be dispatched regardless of log level. */ /** * @typedef {Object} module:Settings~ErrorSettings * @property {object} [recoverAttempts={mediaErrorDecode: 5}] * Defines the maximum number of recover attempts for specific media errors. * * For mediaErrorDecode the player will reset the MSE and skip the blacklisted segment that caused the decode error. The resulting gap will be handled by the GapController. */ /** * @typedef {Object} CachingInfoSettings * @property {boolean} [enable] * Enable or disable the caching feature. * @property {number} [ttl] * Time to live. * * A value defined in milliseconds representing how log to cache the settings for. */ /** * @typedef {Object} Gaps * @property {boolean} [jumpGaps=true] * Sets whether player should jump small gaps (discontinuities) in the buffer. * @property {boolean} [jumpLargeGaps=true] * Sets whether player should jump large gaps (discontinuities) in the buffer. * @property {number} [smallGapLimit=1.5] * Time in seconds for a gap to be considered small. * @property {number} [threshold=0.3] * Threshold at which the gap handling is executed. If currentRangeEnd - currentTime < threshold the gap jump will be triggered. * For live stream the jump might be delayed to keep a consistent live edge. * Note that the amount of buffer at which platforms automatically stall might differ. * @property {boolean} [enableSeekFix=true] * Enables the adjustment of the seek target once no valid segment request could be generated for a specific seek time. This can happen if the user seeks to a position for which there is a gap in the timeline. * @property {boolean} [enableStallFix=false] * If playback stalled in a buffered range this fix will perform a seek by the value defined in stallSeek to trigger playback again. * @property {number} [stallSeek=0.1] * Value to be used in case enableStallFix is set to true */ /** * @typedef {Object} UtcSynchronizationSettings * @property {boolean} [enabled=true] * Enables or disables the UTC clock synchronization * @property {boolean} [useManifestDateHeaderTimeSource=true] * Allows you to enable the use of the Date Header, if exposed with CORS, as a timing source for live edge detection. * * The use of the date header will happen only after the other timing source that take precedence fail or are omitted as described. * @property {number} [backgroundAttempts=2] * Number of synchronization attempts to perform in the background after an initial synchronization request has been done. This is used to verify that the derived client-server offset is correct. * * The background requests are async and done in parallel to the start of the playback. * * This value is also used to perform a resync after 404 errors on segments. * @property {number} [timeBetweenSyncAttempts=30] * The time in seconds between two consecutive sync attempts. * * Note: This value is used as an initial starting value. The internal value of the TimeSyncController is adjusted during playback based on the drift between two consecutive synchronization attempts. * * Note: A sync is only performed after an MPD update. In case the @minimumUpdatePeriod is larger than this value the sync will be delayed until the next MPD update. * @property {number} [maximumTimeBetweenSyncAttempts=600] * The maximum time in seconds between two consecutive sync attempts. * * @property {number} [minimumTimeBetweenSyncAttempts=2] * The minimum time in seconds between two consecutive sync attempts. * * @property {number} [timeBetweenSyncAttemptsAdjustmentFactor=2] * The factor used to multiply or divide the timeBetweenSyncAttempts parameter after a sync. The maximumAllowedDrift defines whether this value is used as a factor or a dividend. * * @property {number} [maximumAllowedDrift=100] * The maximum allowed drift specified in milliseconds between two consecutive synchronization attempts. * * @property {boolean} [enableBackgroundSyncAfterSegmentDownloadError=true] * Enables or disables the background sync after the player ran into a segment download error. * * @property {object} [defaultTimingSource={scheme:'urn:mpeg:dash:utc:http-xsdate:2014',value: 'http://time.akamai.com/?iso&ms'}] * The default timing source to be used. The timing sources in the MPD take precedence over this one. */ /** * @typedef {Object} Scheduling * @property {number} [defaultTimeout=500] * Default timeout between two consecutive segment scheduling attempts * @property {number} [lowLatencyTimeout=0] * Default timeout between two consecutive low-latency segment scheduling attempts * @property {boolean} [scheduleWhilePaused=true] * Set to true if you would like dash.js to keep downloading fragments in the background when the video element is paused. */ /** * @typedef {Object} Text * @property {boolean} [defaultEnabled=true] * Enable/disable subtitle rendering by default. * @property {boolean} [dispatchForManualRend