dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
10 lines (9 loc) • 1.88 MB
HTML
<html>
<head>
</head>
<body style="background: transparent;">
<script src="scripts/docstrap.lib.js"></script>
<script src="scripts/lunr.min.js"></script>
<script src="scripts/fulltext-search.js"></script>
<script type="text/x-docstrap-searchdb">
{"streaming_models_AastLowLatencyThroughputModel.js.html":{"id":"streaming_models_AastLowLatencyThroughputModel.js.html","title":"Source: streaming/models/AastLowLatencyThroughputModel.js","body":" dash.js Modules DashAdapterDashMetricsMediaPlayerOfflineControllerProtectionControllerSettings Classes ErrorsMediaPlayerEventsMediaPlayerModelMetricsReportingEventsMssErrorsOfflineErrorsOfflineEventsProtectionErrorsProtectionEventsThroughputControllerThroughputModel Events MediaPlayerEvents#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIESMediaPlayerEvents#event:AST_IN_FUTUREMediaPlayerEvents#event:BASE_URLS_UPDATEDMediaPlayerEvents#event:BUFFER_EMPTYMediaPlayerEvents#event:BUFFER_LEVEL_STATE_CHANGEDMediaPlayerEvents#event:BUFFER_LEVEL_UPDATEDMediaPlayerEvents#event:BUFFER_LOADEDMediaPlayerEvents#event:CAN_PLAYMediaPlayerEvents#event:CAN_PLAY_THROUGHMediaPlayerEvents#event:CAPTION_CONTAINER_RESIZEMediaPlayerEvents#event:CAPTION_RENDEREDMediaPlayerEvents#event:CONFORMANCE_VIOLATIONMediaPlayerEvents#event:CONTENT_STEERING_REQUEST_COMPLETEDMediaPlayerEvents#event:DYNAMIC_TO_STATICMediaPlayerEvents#event:ERRORMediaPlayerEvents#event:EVENT_MODE_ON_RECEIVEMediaPlayerEvents#event:EVENT_MODE_ON_STARTMediaPlayerEvents#event:FRAGMENT_LOADING_ABANDONEDMediaPlayerEvents#event:FRAGMENT_LOADING_COMPLETEDMediaPlayerEvents#event:FRAGMENT_LOADING_PROGRESSMediaPlayerEvents#event:FRAGMENT_LOADING_STARTEDMediaPlayerEvents#event:INBAND_PRFTMediaPlayerEvents#event:LOGMediaPlayerEvents#event:MANIFEST_LOADEDMediaPlayerEvents#event:MANIFEST_LOADING_FINISHEDMediaPlayerEvents#event:MANIFEST_LOADING_STARTEDMediaPlayerEvents#event:MANIFEST_VALIDITY_CHANGEDMediaPlayerEvents#event:METRIC_ADDEDMediaPlayerEvents#event:METRIC_CHANGEDMediaPlayerEvents#event:METRIC_UPDATEDMediaPlayerEvents#event:METRICS_CHANGEDMediaPlayerEvents#event:PERIOD_SWITCH_COMPLETEDMediaPlayerEvents#event:PERIOD_SWITCH_STARTEDMediaPlayerEvents#event:PLAYBACK_ENDEDMediaPlayerEvents#event:PLAYBACK_ERRORMediaPlayerEvents#event:PLAYBACK_LOADED_DATAMediaPlayerEvents#event:PLAYBACK_METADATA_LOADEDMediaPlayerEvents#event:PLAYBACK_NOT_ALLOWEDMediaPlayerEvents#event:PLAYBACK_PAUSEDMediaPlayerEvents#event:PLAYBACK_PLAYINGMediaPlayerEvents#event:PLAYBACK_PROGRESSMediaPlayerEvents#event:PLAYBACK_RATE_CHANGEDMediaPlayerEvents#event:PLAYBACK_SEEKEDMediaPlayerEvents#event:PLAYBACK_SEEKINGMediaPlayerEvents#event:PLAYBACK_STALLEDMediaPlayerEvents#event:PLAYBACK_STARTEDMediaPlayerEvents#event:PLAYBACK_TIME_UPDATEDMediaPlayerEvents#event:PLAYBACK_VOLUME_CHANGEDMediaPlayerEvents#event:PLAYBACK_WAITINGMediaPlayerEvents#event:QUALITY_CHANGE_RENDEREDMediaPlayerEvents#event:QUALITY_CHANGE_REQUESTEDMediaPlayerEvents#event:REPRESENTATION_SWITCHMediaPlayerEvents#event:STREAM_ACTIVATEDMediaPlayerEvents#event:STREAM_DEACTIVATEDMediaPlayerEvents#event:STREAM_INITIALIZEDMediaPlayerEvents#event:STREAM_INITIALIZINGMediaPlayerEvents#event:STREAM_TEARDOWN_COMPLETEMediaPlayerEvents#event:STREAM_UPDATEDMediaPlayerEvents#event:TEXT_TRACK_ADDEDMediaPlayerEvents#event:TEXT_TRACKS_ADDEDMediaPlayerEvents#event:THROUGHPUT_MEASUREMENT_STOREDMediaPlayerEvents#event:TRACK_CHANGE_RENDEREDMediaPlayerEvents#event:TTML_PARSEDMediaPlayerEvents#event:TTML_TO_PARSEMetricsReportingEvents#event:CMCD_DATA_GENERATEDOfflineEvents#event:OFFLINE_RECORD_FINISHEDOfflineEvents#event:OFFLINE_RECORD_LOADEDMETADATAOfflineEvents#event:OFFLINE_RECORD_STARTEDOfflineEvents#event:OFFLINE_RECORD_STOPPEDProtectionEvents#event:KEY_ADDEDProtectionEvents#event:KEY_ERRORProtectionEvents#event:KEY_MESSAGEProtectionEvents#event:KEY_SESSION_CLOSEDProtectionEvents#event:KEY_SESSION_CREATEDProtectionEvents#event:KEY_SESSION_REMOVEDProtectionEvents#event:KEY_STATUSES_CHANGEDProtectionEvents#event:KEY_SYSTEM_ACCESS_COMPLETEProtectionEvents#event:KEY_SYSTEM_SELECTEDProtectionEvents#event:LICENSE_REQUEST_COMPLETEProtectionEvents#event:LICENSE_REQUEST_SENDINGProtectionEvents#event:PROTECTION_CREATEDProtectionEvents#event:PROTECTION_DESTROYED Global LICENSE_SERVER_MANIFEST_CONFIGURATIONS Source: streaming/models/AastLowLatencyThroughputModel.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. */ import Debug from '../../core/Debug.js'; import FactoryMaker from '../../core/FactoryMaker.js'; function AastLowLatencyThroughputModel() { const LLTM_MAX_MEASUREMENTS = 10; // factor (<1) is used to reduce the real needed download time when at very bleeding live edge const LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR = 0.8; const LLTM_OPTIMISTIC_ESTIMATE_FACTOR = 0.6; const LLTM_SLOW_SEGMENT_DOWNLOAD_TOLERANCE = 1.05; const LLTM_MAX_DELAY_MS = 250; const context = this.context; let instance; let logger; let measurements = {}; function setup() { logger = Debug(context).getInstance().getLogger(instance); } /** * Linear regression with least squares method to get a trend function for buffer lavel at chunk receive timestamps * @param {*} chunkMeasurements * @returns linear trend function */ function createBufferLevelTrendFunction(chunkMeasurements) { const result = {}; let sumX = 0 let sumY = 0 let sumXY = 0 let sumXSq = 0 const N = chunkMeasurements.length for (var i = 0; i < N; ++i) { sumX += chunkMeasurements[i].chunkDownloadTimeRelativeMS; sumY += chunkMeasurements[i].bufferLevel; sumXY += chunkMeasurements[i].chunkDownloadTimeRelativeMS * chunkMeasurements[i].bufferLevel; sumXSq += chunkMeasurements[i].chunkDownloadTimeRelativeMS * chunkMeasurements[i].chunkDownloadTimeRelativeMS } result.m = ((sumXY - sumX * sumY / N)) / (sumXSq - sumX * sumX / N) result.b = sumY / N - result.m * sumX / N return function (x) { return result.m * x + result.b } } function isBufferSafeAndStable(lastMeasurements) { let isBufferSafeAndStable = true; let lastBitrate; const aveBufferLevelLastSegements = lastMeasurements.reduce((prev, curr) => prev + curr.bufferLevelAtSegmentEnd, 0) / lastMeasurements.length; lastMeasurements.forEach(m => { // inner segment buffer stability if (Math.abs(m.bufferLevelAtSegmentEnd / m.bufferLevelAtSegmentStart) < 0.95) { isBufferSafeAndStable = false; } // inter segment buffer stability if (m.bufferLevelAtSegmentEnd / aveBufferLevelLastSegements < 0.8) { isBufferSafeAndStable = false; } // representation bitrate remained at least constant if (!lastBitrate) { lastBitrate = m.bitrate; } else if (lastBitrate > m.bitrate) { isBufferSafeAndStable = false; } }); return isBufferSafeAndStable; } /** * Based on the MPD, timing and buffer information of the last recent segments and their chunks * the most stable download time (in milliseconds) is calculated. * @param {*} request HTTPLoader request object * @returns download time in milliseconds of last fetched segment */ function getEstimatedDownloadDurationMS(request) { const lastMeasurement = measurements[request.mediaType].slice(-1).pop(); const lastThreeMeasurements = measurements[request.mediaType].slice(-3) // calculate and remember the buffer level trend during the last fetched segment const lastChunkRelativeTimeMS = lastMeasurement.chunkMeasurements.slice(-1).pop().chunkDownloadTimeRelativeMS; lastMeasurement.bufferLevelAtSegmentStart = lastMeasurement.getEstimatedBufferLevel(lastChunkRelativeTimeMS / 2); lastMeasurement.bufferLevelAtSegmentEnd = lastMeasurement.getEstimatedBufferLevel(lastChunkRelativeTimeMS); const isBufferStable = isBufferSafeAndStable(lastThreeMeasurements); const selectedOptimisticFactor = isBufferStable ? LLTM_OPTIMISTIC_ESTIMATE_FACTOR : LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR; // fetch duration was longer than segment duration, but buffer was stable if (lastMeasurement.isBufferStable && lastMeasurement.segDurationMS * LLTM_SLOW_SEGMENT_DOWNLOAD_TOLERANCE < lastMeasurement.fetchDownloadDurationMS) { return lastMeasurement.fetchDownloadDurationMS; } // buffer is drying or fetch took too long if (!isBufferStable || lastMeasurement.segDurationMS < lastMeasurement.fetchDownloadDurationMS) { return lastMeasurement.fetchDownloadDurationMS * LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR; } // did we requested a fully available segment? -> most accurate throughput calculation // we use adjusted availability start time to decide // Note: this \"download mode\" usually happens at startup and if requests are delayed artificially if (lastMeasurement.adjustedAvailabilityStartTimeMS <= (lastMeasurement.requestTimeMS + lastMeasurement.throughputCapacityDelayMS) - lastMeasurement.segDurationMS) { return lastMeasurement.fetchDownloadDurationMS * LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR; } // get all chunks that have been downloaded before fetch reached bleeding live edge // the remaining chunks loaded at production rate we will approximated const chunkAvailablePeriod = (lastMeasurement.requestTimeMS + lastMeasurement.throughputCapacityDelayMS) - lastMeasurement.adjustedAvailabilityStartTimeMS; let chunkBytesBBLE = 0; // BBLE -> Before bleeding live edge let chunkDownloadtimeMSBBLE = 0; let chunkCount = 0; for (let index = 0; index < lastMeasurement.chunkMeasurements.length; index++) { const chunk = lastMeasurement.chunkMeasurements[index]; if (chunkAvailablePeriod < chunkDownloadtimeMSBBLE + chunk.chunkDownloadDurationMS) { break; } chunkDownloadtimeMSBBLE += chunk.chunkDownloadDurationMS; chunkBytesBBLE += chunk.chunkBytes; chunkCount++; } if (chunkAvailablePeriod < 0) { logger.warn('request time was before adjusted availibitly start time'); } // there have to be some chunks available (20% of max count) // otherwise we are at bleeding live edge and the few chunks are insufficient to estimate correctly if (chunkBytesBBLE && chunkDownloadtimeMSBBLE && chunkCount > lastMeasurement.chunkMeasurements.length * 0.2) { const downloadThroughput = chunkBytesBBLE / chunkDownloadtimeMSBBLE; // bytes per millesecond const estimatedDownloadtimeMS = lastMeasurement.segmentBytes / downloadThroughput; // if real download was shorter then report this incl. semi optimistical estimate factor if (lastMeasurement.fetchDownloadDurationMS < estimatedDownloadtimeMS) { return lastMeasurement.fetchDownloadDurationMS * selectedOptimisticFactor; } return estimatedDownloadtimeMS * selectedOptimisticFactor; } // when we are to tight at live edge and it's stable then // we start to optimistically estimate download time // in such a way that a switch to next rep will be possible // optimistical estimate: assume download was fast enough for next higher rendition let nextHigherBitrate = lastMeasurement.bitrate; lastMeasurement.bitrateList.some(b => { if (b.bandwidth > lastMeasurement.bitrate) { nextHigherBitrate = b.bandwidth; return true; } }); // already highest bitrate? if (nextHigherBitrate === lastMeasurement.bitrate) { return lastMeasurement.fetchDownloadDurationMS * selectedOptimisticFactor; } return selectedOptimisticFactor * lastMeasurement.segmentBytes * 8 * 1000 / nextHigherBitrate; } /** * Get calculated value for a safe artificial delay of the next request to allow to accumulate some chunks. * This allows better line throughput measurement. * @param {*} request * @param {*} currentBufferLevel current buffer level in milliseconds * @returns delay in milliseconds */ function getThroughputCapacityDelayMS(request, currentBufferLevelMS) { const lastThreeMeasurements = measurements[request.mediaType] && measurements[request.mediaType].slice(-3); if (!lastThreeMeasurements || lastThreeMeasurements.length < 3) { return 0; } // in case not stable buffer, no artificially delay for the next request if (!isBufferSafeAndStable(lastThreeMeasurements)) { return 0; } // allowed artificial delay is the min of quater of buffer level in milliseconds and LLTM_MAX_DELAY_MS return currentBufferLevelMS / 4 > LLTM_MAX_DELAY_MS ? LLTM_MAX_DELAY_MS : currentBufferLevelMS / 4; } /** * Add some measurement data for bookkeeping and being able to derive decisions on estimated throughput. * @param {*} request HTTPLoader object to get MPD and media info from * @param {*} fetchDownloadDurationMS Duration how long the fetch actually took * @param {*} chunkMeasurements Array containing chunk timings and buffer levels * @param {*} requestTimeMS Timestamp at which the fetch was initiated * @param {*} throughputCapacityDelayMS An artificial delay that was used for this request */ function addMeasurement(request, fetchDownloadDurationMS, chunkMeasurements, requestTimeMS, throughputCapacityDelayMS) { if (request && request.mediaType && !measurements[request.mediaType]) { measurements[request.mediaType] = []; } const bitrateEntry = request.representation.mediaInfo.bitrateList.find(item => item.id === request.representation.id); measurements[request.mediaType].push({ index: request.index, repId: request.representationId, mediaType: request.mediaType, requestTimeMS, adjustedAvailabilityStartTimeMS: request.availabilityStartTime.getTime(), segDurationMS: request.duration * 1000, chunksDurationMS: chunkMeasurements.reduce((prev, curr) => prev + curr.chunkDownloadDurationMS, 0), segmentBytes: chunkMeasurements.reduce((prev, curr) => prev + curr.chunkBytes, 0), bitrate: bitrateEntry && bitrateEntry.bandwidth, bitrateList: request.representation.mediaInfo.bitrateList, chunkMeasurements, fetchDownloadDurationMS, throughputCapacityDelayMS, getEstimatedBufferLevel: createBufferLevelTrendFunction(chunkMeasurements.slice(1)) // don't use first chunk's buffer level }); // maintain only a maximum amount of most recent measurements if (measurements[request.mediaType].length > LLTM_MAX_MEASUREMENTS) { measurements[request.mediaType].shift(); } } instance = { addMeasurement, getEstimatedDownloadDurationMS, getThroughputCapacityDelayMS, setup, }; setup() return instance; } AastLowLatencyThroughputModel.__dashjs_factory_name = 'AastLowLatencyThroughputModel'; export default FactoryMaker.getSingletonFactory(AastLowLatencyThroughputModel); × Search results Close "},"streaming_controllers_AbrController.js.html":{"id":"streaming_controllers_AbrController.js.html","title":"Source: streaming/controllers/AbrController.js","body":" dash.js Modules DashAdapterDashMetricsMediaPlayerOfflineControllerProtectionControllerSettings Classes ErrorsMediaPlayerEventsMediaPlayerModelMetricsReportingEventsMssErrorsOfflineErrorsOfflineEventsProtectionErrorsProtectionEventsThroughputControllerThroughputModel Events MediaPlayerEvents#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIESMediaPlayerEvents#event:AST_IN_FUTUREMediaPlayerEvents#event:BASE_URLS_UPDATEDMediaPlayerEvents#event:BUFFER_EMPTYMediaPlayerEvents#event:BUFFER_LEVEL_STATE_CHANGEDMediaPlayerEvents#event:BUFFER_LEVEL_UPDATEDMediaPlayerEvents#event:BUFFER_LOADEDMediaPlayerEvents#event:CAN_PLAYMediaPlayerEvents#event:CAN_PLAY_THROUGHMediaPlayerEvents#event:CAPTION_CONTAINER_RESIZEMediaPlayerEvents#event:CAPTION_RENDEREDMediaPlayerEvents#event:CONFORMANCE_VIOLATIONMediaPlayerEvents#event:CONTENT_STEERING_REQUEST_COMPLETEDMediaPlayerEvents#event:DYNAMIC_TO_STATICMediaPlayerEvents#event:ERRORMediaPlayerEvents#event:EVENT_MODE_ON_RECEIVEMediaPlayerEvents#event:EVENT_MODE_ON_STARTMediaPlayerEvents#event:FRAGMENT_LOADING_ABANDONEDMediaPlayerEvents#event:FRAGMENT_LOADING_COMPLETEDMediaPlayerEvents#event:FRAGMENT_LOADING_PROGRESSMediaPlayerEvents#event:FRAGMENT_LOADING_STARTEDMediaPlayerEvents#event:INBAND_PRFTMediaPlayerEvents#event:LOGMediaPlayerEvents#event:MANIFEST_LOADEDMediaPlayerEvents#event:MANIFEST_LOADING_FINISHEDMediaPlayerEvents#event:MANIFEST_LOADING_STARTEDMediaPlayerEvents#event:MANIFEST_VALIDITY_CHANGEDMediaPlayerEvents#event:METRIC_ADDEDMediaPlayerEvents#event:METRIC_CHANGEDMediaPlayerEvents#event:METRIC_UPDATEDMediaPlayerEvents#event:METRICS_CHANGEDMediaPlayerEvents#event:PERIOD_SWITCH_COMPLETEDMediaPlayerEvents#event:PERIOD_SWITCH_STARTEDMediaPlayerEvents#event:PLAYBACK_ENDEDMediaPlayerEvents#event:PLAYBACK_ERRORMediaPlayerEvents#event:PLAYBACK_LOADED_DATAMediaPlayerEvents#event:PLAYBACK_METADATA_LOADEDMediaPlayerEvents#event:PLAYBACK_NOT_ALLOWEDMediaPlayerEvents#event:PLAYBACK_PAUSEDMediaPlayerEvents#event:PLAYBACK_PLAYINGMediaPlayerEvents#event:PLAYBACK_PROGRESSMediaPlayerEvents#event:PLAYBACK_RATE_CHANGEDMediaPlayerEvents#event:PLAYBACK_SEEKEDMediaPlayerEvents#event:PLAYBACK_SEEKINGMediaPlayerEvents#event:PLAYBACK_STALLEDMediaPlayerEvents#event:PLAYBACK_STARTEDMediaPlayerEvents#event:PLAYBACK_TIME_UPDATEDMediaPlayerEvents#event:PLAYBACK_VOLUME_CHANGEDMediaPlayerEvents#event:PLAYBACK_WAITINGMediaPlayerEvents#event:QUALITY_CHANGE_RENDEREDMediaPlayerEvents#event:QUALITY_CHANGE_REQUESTEDMediaPlayerEvents#event:REPRESENTATION_SWITCHMediaPlayerEvents#event:STREAM_ACTIVATEDMediaPlayerEvents#event:STREAM_DEACTIVATEDMediaPlayerEvents#event:STREAM_INITIALIZEDMediaPlayerEvents#event:STREAM_INITIALIZINGMediaPlayerEvents#event:STREAM_TEARDOWN_COMPLETEMediaPlayerEvents#event:STREAM_UPDATEDMediaPlayerEvents#event:TEXT_TRACK_ADDEDMediaPlayerEvents#event:TEXT_TRACKS_ADDEDMediaPlayerEvents#event:THROUGHPUT_MEASUREMENT_STOREDMediaPlayerEvents#event:TRACK_CHANGE_RENDEREDMediaPlayerEvents#event:TTML_PARSEDMediaPlayerEvents#event:TTML_TO_PARSEMetricsReportingEvents#event:CMCD_DATA_GENERATEDOfflineEvents#event:OFFLINE_RECORD_FINISHEDOfflineEvents#event:OFFLINE_RECORD_LOADEDMETADATAOfflineEvents#event:OFFLINE_RECORD_STARTEDOfflineEvents#event:OFFLINE_RECORD_STOPPEDProtectionEvents#event:KEY_ADDEDProtectionEvents#event:KEY_ERRORProtectionEvents#event:KEY_MESSAGEProtectionEvents#event:KEY_SESSION_CLOSEDProtectionEvents#event:KEY_SESSION_CREATEDProtectionEvents#event:KEY_SESSION_REMOVEDProtectionEvents#event:KEY_STATUSES_CHANGEDProtectionEvents#event:KEY_SYSTEM_ACCESS_COMPLETEProtectionEvents#event:KEY_SYSTEM_SELECTEDProtectionEvents#event:LICENSE_REQUEST_COMPLETEProtectionEvents#event:LICENSE_REQUEST_SENDINGProtectionEvents#event:PROTECTION_CREATEDProtectionEvents#event:PROTECTION_DESTROYED Global LICENSE_SERVER_MANIFEST_CONFIGURATIONS Source: streaming/controllers/AbrController.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. */ import ABRRulesCollection from '../rules/abr/ABRRulesCollection.js'; import Constants from '../constants/Constants.js'; import MetricsConstants from '../constants/MetricsConstants.js'; import FragmentModel from '../models/FragmentModel.js'; import EventBus from '../../core/EventBus.js'; import Events from '../../core/events/Events.js'; import FactoryMaker from '../../core/FactoryMaker.js'; import RulesContext from '../rules/RulesContext.js'; import SwitchRequest from '../rules/SwitchRequest.js'; import SwitchRequestHistory from '../rules/SwitchRequestHistory.js'; import DroppedFramesHistory from '../rules/DroppedFramesHistory.js'; import Debug from '../../core/Debug.js'; import MediaPlayerEvents from '../MediaPlayerEvents.js'; const DEFAULT_VIDEO_BITRATE = 1000; const DEFAULT_BITRATE = 100; function AbrController() { const context = this.context; const debug = Debug(context).getInstance(); const eventBus = EventBus(context).getInstance(); let instance, logger, abrRulesCollection, streamController, streamProcessorDict, abandonmentStateDict, abandonmentTimeout, windowResizeEventCalled, adapter, videoModel, mediaPlayerModel, customParametersModel, cmsdModel, domStorage, playbackRepresentationId, switchRequestHistory, droppedFramesHistory, throughputController, dashMetrics, settings; function setup() { logger = debug.getLogger(instance); resetInitialSettings(); } /** * Initialize everything that is not period specific. We only have one instance of the ABR Controller for all periods. */ function initialize() { droppedFramesHistory = DroppedFramesHistory(context).create(); switchRequestHistory = SwitchRequestHistory(context).create(); abrRulesCollection = ABRRulesCollection(context).create({ dashMetrics, customParametersModel, mediaPlayerModel, settings }); abrRulesCollection.initialize(); eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_RENDERED, _onQualityChangeRendered, instance); eventBus.on(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance); eventBus.on(Events.LOADING_PROGRESS, _onFragmentLoadProgress, instance); } /** * Whenever a StreamProcessor is created it is added to the list of streamProcessorDict * In addition, the corresponding objects for this object and its stream id are created * @param {object} type * @param {object} streamProcessor */ function registerStreamType(type, streamProcessor) { const streamId = streamProcessor.getStreamInfo().id; if (!streamProcessorDict[streamId]) { streamProcessorDict[streamId] = {}; } streamProcessorDict[streamId][type] = streamProcessor; if (!abandonmentStateDict[streamId]) { abandonmentStateDict[streamId] = {}; } abandonmentStateDict[streamId][type] = {}; abandonmentStateDict[streamId][type].state = MetricsConstants.ALLOW_LOAD; // Do not change current value if it has been set before const currentState = abrRulesCollection.getBolaState(type) if (currentState === undefined) { abrRulesCollection.setBolaState(type, settings.get().streaming.abr.rules.bolaRule.active && !_shouldApplyDynamicAbrStrategy()); } } /** * Remove all parameters that belong to a specific period * @param {string} streamId * @param {string} type */ function unRegisterStreamType(streamId, type) { try { if (streamProcessorDict[streamId] && streamProcessorDict[streamId][type]) { delete streamProcessorDict[streamId][type]; } if (abandonmentStateDict[streamId] && abandonmentStateDict[streamId][type]) { delete abandonmentStateDict[streamId][type]; } } catch (e) { } } function resetInitialSettings() { abandonmentStateDict = {}; streamProcessorDict = {}; if (windowResizeEventCalled === undefined) { windowResizeEventCalled = false; } if (droppedFramesHistory) { droppedFramesHistory.reset(); } if (switchRequestHistory) { switchRequestHistory.reset(); } playbackRepresentationId = undefined; droppedFramesHistory = undefined; switchRequestHistory = undefined; clearTimeout(abandonmentTimeout); abandonmentTimeout = null; } function reset() { resetInitialSettings(); eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_RENDERED, _onQualityChangeRendered, instance); eventBus.off(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance); eventBus.off(Events.LOADING_PROGRESS, _onFragmentLoadProgress, instance); if (abrRulesCollection) { abrRulesCollection.reset(); } } function setConfig(config) { if (!config) return; if (config.streamController) { streamController = config.streamController; } if (config.throughputController) { throughputController = config.throughputController; } if (config.domStorage) { domStorage = config.domStorage; } if (config.mediaPlayerModel) { mediaPlayerModel = config.mediaPlayerModel; } if (config.customParametersModel) { customParametersModel = config.customParametersModel; } if (config.cmsdModel) { cmsdModel = config.cmsdModel } if (config.dashMetrics) { dashMetrics = config.dashMetrics; } if (config.adapter) { adapter = config.adapter; } if (config.videoModel) { videoModel = config.videoModel; } if (config.settings) { settings = config.settings; } } function getOptimalRepresentationForBitrate(mediaInfo, bitrate, includeCompatibleMediaInfos = true) { const possibleVoRepresentations = getPossibleVoRepresentations(mediaInfo, includeCompatibleMediaInfos); if (!possibleVoRepresentations || possibleVoRepresentations.length === 0) { return null; } // If bitrate should be as small as possible return the Representation with the lowest bitrate if (bitrate <= 0) { return possibleVoRepresentations.sort((a, b) => { return a.bandwidth - b.bandwidth; })[0] } // Get all Representations that have lower or equal bitrate than our target bitrate const targetRepresentations = possibleVoRepresentations.filter((rep) => { return rep.bitrateInKbit <= bitrate }); if (!targetRepresentations || targetRepresentations.length === 0) { return possibleVoRepresentations[0]; } return targetRepresentations[targetRepresentations.length - 1]; } function getRepresentationByAbsoluteIndex(absoluteIndex, mediaInfo, includeCompatibleMediaInfos = true) { if (isNaN(absoluteIndex) || absoluteIndex < 0) { return null; } const possibleVoRepresentations = getPossibleVoRepresentations(mediaInfo, includeCompatibleMediaInfos); return possibleVoRepresentations.find((rep) => { return rep.absoluteIndex === absoluteIndex }) } function getPossibleVoRepresentations(mediaInfo, includeCompatibleMediaInfos = true) { let voRepresentations = []; if (!mediaInfo) { return voRepresentations; } const mediaInfos = _getPossibleMediaInfos(mediaInfo) mediaInfos.forEach((mediaInfo) => { let currentVoRepresentations = adapter.getVoRepresentations(mediaInfo); if (currentVoRepresentations && currentVoRepresentations.length > 0) { voRepresentations = voRepresentations.concat(currentVoRepresentations) } }) // Now sort by quality (usually simply by bitrate) voRepresentations = _sortByCalculatedQualityRank(voRepresentations); // Filter the list of options based on the provided settings voRepresentations = _filterByAllowedSettings(voRepresentations) // Add an absolute index after filtering voRepresentations.forEach((rep, index) => { rep.absoluteIndex = index }) // Filter the Representations in case we do not want to include compatible Media Infos // We can not apply the filter before otherwise the absolute index would be wrong if (!includeCompatibleMediaInfos) { voRepresentations = voRepresentations.filter((rep) => { return adapter.areMediaInfosEqual(rep.mediaInfo, mediaInfo); }) } return voRepresentations; } function _getPossibleMediaInfos(mediaInfo) { try { const possibleMediaInfos = []; if (mediaInfo) { possibleMediaInfos.push(mediaInfo); } // If AS switching is disabled return only the current MediaInfo if (!settings.get().streaming.abr.enableSupplementalPropertyAdaptationSetSwitching || !mediaInfo.adaptationSetSwitchingCompatibleIds || mediaInfo.adaptationSetSwitchingCompatibleIds.length === 0) { return possibleMediaInfos } // Otherwise add everything that is compatible const mediaInfoArr = streamProcessorDict[mediaInfo.streamInfo.id][mediaInfo.type].getAllMediaInfos() const compatibleMediaInfos = mediaInfoArr.filter((entry) => { return mediaInfo.adaptationSetSwitchingCompatibleIds.includes(entry.id) }) return possibleMediaInfos.concat(compatibleMediaInfos); } catch (e) { return [mediaInfo] } } /** * @param {Representation[]} voRepresentations * @return {Representation[]} */ function _filterByAllowedSettings(voRepresentations) { try { voRepresentations = _filterByPossibleBitrate(voRepresentations); voRepresentations = _filterByPortalSize(voRepresentations); voRepresentations = _filterByCmsdMaxBitrate(voRepresentations); return voRepresentations; } catch (e) { logger.error(e); return voRepresentations } } /** * Returns all RepresentationInfo objects that have at least one bitrate that fulfills the constraint * @param {Representation[]} voRepresentations * @return {Representation[]} */ function _filterByPossibleBitrate(voRepresentations) { try { const filteredArray = voRepresentations.filter((voRepresentation) => { const type = voRepresentation.mediaInfo.type; const representationBitrate = voRepresentation.bitrateInKbit; const maxBitrate = mediaPlayerModel.getAbrBitrateParameter('maxBitrate', type); const minBitrate = mediaPlayerModel.getAbrBitrateParameter('minBitrate', type); if (maxBitrate > -1 && representationBitrate > maxBitrate) { return false; } return !(minBitrate > -1 && representationBitrate < minBitrate); }) if (filteredArray.length > 0) { return filteredArray } return voRepresentations } catch (e) { logger.error(e); return voRepresentations } } /** * @param {Representation[]} voRepresentations * @return {Representation[]} * @private */ function _filterByPortalSize(voRepresentations) { try { if (!settings.get().streaming.abr.limitBitrateByPortal) { return voRepresentations; } const { elementWidth } = videoModel.getVideoElementSize(); const filteredArray = voRepresentations.filter((voRepresentation) => { return voRepresentation.mediaInfo.type !== Constants.VIDEO || voRepresentation.width <= elementWidth; }) if (filteredArray.length > 0) { return filteredArray } return voRepresentations } catch (e) { logger.error(e); return voRepresentations } } /** * @param {Representation[]} voRepresentations * @return {Representation[]} */ function _filterByCmsdMaxBitrate(voRepresentations) { try { // Check CMSD max suggested bitrate only for video segments if (!settings.get().streaming.cmsd.enabled || !settings.get().streaming.cmsd.abr.applyMb) { return voRepresentations } const filteredArray = voRepresentations.filter((voRepresentation) => { const type = voRepresentation.mediaInfo.type; let maxCmsdBitrate = cmsdModel.getMaxBitrate(type); if (type !== Constants.VIDEO || maxCmsdBitrate < 0) { return true } // Subtract audio bitrate const streamId = voRepresentation.mediaInfo.streamInfo.id; const streamProcessor = streamProcessorDict[streamId][Constants.AUDIO]; const representation = streamProcessor.getRepresentation(); const audioBitrate = representation.bitrateInKbit; maxCmsdBitrate -= audioBitrate ? audioBitrate : 0; return voRepresentation.bitrateInKbit <= maxCmsdBitrate }) if (filteredArray.length > 0) { return filteredArray } return voRepresentations } catch (e) { logger.error(e); return voRepresentations } } /** * Calculate a quality rank based on bandwidth, codec and qualityRanking. Lower value means better quality. * @param voRepresentations * @private */ function _sortByCalculatedQualityRank(voRepresentations) { // All Representations must have a qualityRanking otherwise we ignore it // QualityRanking only applies to Representations within one AS. If we merged multiple AS based on the adaptation-set-switching-2016 supplemental property we can not apply this logic let firstMediaInfo = null; const filteredRepresentations = voRepresentations.filter((rep) => { if (!firstMediaInfo) { firstMediaInfo = rep.mediaInfo; } return !isNaN(rep.qualityRanking) && adapter.areMediaInfosEqual(firstMediaInfo, rep.mediaInfo); }) if (filteredRepresentations.length === voRepresentations.length) { voRepresentations.sort((a, b) => { return b.qualityRanking - a.qualityRanking; }) } else { voRepresentations.sort((a, b) => { return a.bandwidth - b.bandwidth; }) } return voRepresentations } /** * While fragment loading is in progress we check if we might need to abort the request * @param {object} e * @private */ function _onFragmentLoadProgress(e) { const type = e.request.mediaType; const streamId = e.streamId; if (!type || !streamId || !streamProcessorDict[streamId] || !settings.get().streaming.abr.autoSwitchBitrate[type]) { return; } const streamProcessor = streamProcessorDict[streamId][type]; if (!streamProcessor) { return; } const rulesContext = RulesContext(context).create({ abrController: instance, streamProcessor, currentRequest: e.request, throughputController, videoModel }); const switchRequest = abrRulesCollection.shouldAbandonFragment(rulesContext); if (switchRequest && switchRequest.representation !== SwitchRequest.NO_CHANGE) { _onSegmentDownloadShouldBeAbandoned(e, streamId, type, streamProcessor, switchRequest); } } function _onSegmentDownloadShouldBeAbandoned(e, streamId, type, streamProcessor, switchRequest) { const fragmentModel = streamProcessor.getFragmentModel(); const request = fragmentModel.getRequests({ state: FragmentModel.FRAGMENT_MODEL_LOADING, index: e.request.index })[0]; if (request) { abandonmentStateDict[streamId][type].state = MetricsConstants.ABANDON_LOAD; switchRequestHistory.reset(); setPlaybackQuality(type, streamController.getActiveStreamInfo(), switchRequest.representation, switchRequest.reason); clearTimeout(abandonmentTimeout); abandonmentTimeout = setTimeout( () => { abandonmentStateDict[streamId][type].state = MetricsConstants.ALLOW_LOAD; abandonmentTimeout = null; }, settings.get().streaming.abandonLoadTimeout ); } } /** * Update dropped frames history when the quality was changed * @param {object} e * @private */ function _onQualityChangeRendered(e) { if (e.mediaType === Constants.VIDEO) { if (playbackRepresentationId !== undefined) { droppedFramesHistory.push(e.streamId, playbackRepresentationId, videoModel.getPlaybackQuality()); } playbackRepresentationId = e.newRepresentation.id; } } /** * When the buffer level is updated we check if we need to change the ABR strategy * @param e * @private */ function _onMetricAdded(e) { if (_shouldApplyDynamicAbrStrategy() && e.metric === MetricsConstants.BUFFER_LEVEL && (e.mediaType === Constants.AUDIO || e.mediaType === Constants.VIDEO)) { _updateDynamicAbrStrategy(e.mediaType, 0.001 * e.value.level); } } /** * Returns the initial bitrate for a specific media type * @param {string} type * @returns {number} A value of the initial bitrate, kbps * @memberof AbrController# */ function getInitialBitrateFor(type) { if (type === Constants.TEXT) { return NaN; } let configBitrate = mediaPlayerModel.getAbrBitrateParameter('initialBitrate', type); if (configBitrate > 0) { return configBitrate; } let savedBitrate = NaN; if (domStorage && domStorage.hasOwnProperty('getSavedBitrateSettings')) { savedBitrate = domStorage.getSavedBitrateSettings(type); } if (!isNaN(savedBitrate)) { return savedBitrate } const averageThroughput = throughputController.getAverageThroughput(type); if (!isNaN(averageThroughput) && averageThroughput > 0) { return averageThroughput } return (type === Constants.VIDEO) ? DEFAULT_VIDEO_BITRATE : DEFAULT_BITRATE; } /** * This function is called by the scheduleControllers to check if the quality should be changed. * Consider this the main entry point for the ABR decision logic * @param {string} type * @param {string} streamId */ function checkPlaybackQuality(type, streamId) { try { if (!type || !streamProcessorDict || !streamProcessorDict[streamId] || !streamProcessorDict[streamId][type]) { return false; } if (droppedFramesHistory) { const playbackQuality = videoModel.getPlaybackQuality(); if (playbackQuality) { droppedFramesHistory.push(streamId, playbackRepresentationId, playbackQuality); } } if (!settings.get().streaming.abr.autoSwitchBitrate[type]) { return false; } const streamProcessor = streamProcessorDict[streamId][type]; const currentRepresentation = streamProcessor.getRepresentation(); const rulesContext = RulesContext(context).create({ abrController: instance, throughputController, switchRequestHistory, droppedFramesHistory, streamProcessor, videoModel }); const switchRequest = abrRulesCollection.getBestPossibleSwitchRequest(rulesContext); if (!switchRequest || !switchRequest.representation) { return false; } let newRepresentation = switchRequest.representation; switchRequestHistory.push({ currentRepresentation, newRepresentation }); if (newRepresentation.id !== currentRepresentation.id && (abandonmentStateDict[streamId][type].state === MetricsConstants.ALLOW_LOAD || newRepresentation.absoluteIndex < currentRepresentation.absoluteIndex)) { _changeQuality(currentRepresentation, newRepresentation, switchRequest.reason); return true; } return false; } catch (e) { logger.error(e); return false; } } /** * Sets the new playback quality. Starts from index 0. * If the index of the new quality is the same as the old one changeQuality will not be called. * @param {string} type * @param {object} streamInfo * @param {Representation} representation * @param {string} reason * @param {string} rule */ function setPlaybackQuality(type, streamInfo, representation, reason = {}) { if (!streamInfo || !streamInfo.id || !type || !streamProcessorDict || !streamProcessorDict[streamInfo.id] || !streamProcessorDict[streamInfo.id][type] || !representation) { return; } const streamProcessor = streamProcessorDict[streamInfo.id][type]; const currentRepresentation = streamProcessor.getRepresentation(); if (!currentRepresentation || representation.id !== currentRepresentation.id) { _changeQuality(currentRepresentation, representation, reason); } } /** * * @param {string} streamId * @param {type} type * @return {*|null} */ function getAbandonmentStateFor(streamId, type) { return abandonmentStateDict[streamId] && abandonmentStateDict[streamId][type] ? abandonmentStateDict[streamId][type].state : null; } /** * Changes the internal qualityDict values according to the new quality * @param {Representation} oldRepresentation * @param {Representation} newRepresentation * @param {string} reason * @private */ function _changeQuality(oldRepresentation, newRepresentation, reason) { const streamId = newRepresentation.mediaInfo.streamInfo.id; const type = newRepresentation.mediaInfo.type; if (type && streamProcessorDict[streamId] && streamProcessorDict[streamId][type]) { const streamInfo = streamProcessorDict[streamId][type].getStreamInfo(); const bufferLevel = dashMetrics.getCurrentBufferLevel(type); const isAdaptationSetSwitch = oldRepresentation !== null && !adapter.areMediaInfosEqual(oldRepresentation.mediaInfo, newRepresentation.mediaInfo); const oldBitrate = oldRepresentation ? oldRepresentation.bitrateInKbit : 0; logger.info(`[AbrController]: Switching quality in period ${streamId} for media type ${type}. Switch from bitrate ${oldBitrate} to bitrate ${newRepresentation.bitrateInKbit}. Current buffer level: ${bufferLevel}. Reason:` + (reason ? JSON.stringify(reason) : '/')); eventBus.trigger(Events.QUALITY_CHANGE_REQUESTED, { oldRepresentation: oldRepresentation, newRepresentation: newRepresentation, reason, streamInfo, mediaType: type, isAdaptationSetSwitch }, { streamId: streamInfo.id, mediaType: type } ); const bitrate = throughputController.getAverageThroughput(type); if (!isNaN(bitrate)) { domStorage.setSavedBitrateSettings(type, bitrate); } } } /** * If both BOLA and Throughput Rule are active we switch dynamically between both of them * @returns {boolean} * @private */ function _shouldApplyDynamicAbrStrategy() { return settings.get().streaming.abr.rules.bolaRule.active && settings.get().streaming.abr.rules.throughputRule.active } /** * Switch between BOLA and ThroughputRule * @param mediaType * @param bufferLevel * @private */ function _updateDynamicAbrStrategy(mediaType, bufferLevel) { try { const bufferTimeDefault = mediaPlayerModel.getBufferTimeDefault(); const switchOnThreshold = bufferTimeDefault; const switchOffThreshold = 0.5 * bufferTimeDefault; const useBufferABR = abrRulesCollection.getBolaState(mediaType) const newUseBufferABR = bufferLevel > (useBufferABR ? switchOffThreshold : switchOnThreshold); // use hysteresis to avoid oscillating rules abrRulesCollection.setBolaState(mediaType, newUseBufferABR); if (newUseBufferABR !== useBufferABR) { if (newUseBufferABR) { logger.info('[' + mediaType + '] switching from throughput to buffer occupancy ABR rule (buffer: ' + bufferLevel.toFixed(3) + ').'); } else { logger.info('[' + mediaType + '] switching from buffer occupancy to throughput ABR rule (buffer: ' + bufferLevel.toFixed(3) + ').'); } } } catch (e) { logger.error(e); } } /** * Checks if the provided Representation has the lowest possible quality * @param representation * @returns {boolean} */ function isPlayingAtLowestQuality(representation) { const voRepresentations = getPossibleVoRepresentations(representation.mediaInfo, true); return voRepresentations[0].id === representation.id } /** * Checks if the provided Representation has the highest possible quality * @param representation * @returns {boolean} */ function isPlayingAtTopQuality(representation) { if (!representation) { return true; } const voRepresentations = getPossibleVoRepresentations(representation.mediaInfo, true); return voRepresentations[voRepresentations.length - 1].id === representation.id; } function setWindowResizeEventCalled(value) { windowResizeEventCalled = value; } function clearDataForStream(streamId) { if (droppedFramesHistory) { droppedFramesHistory.clearForStream(streamId); } if (streamProcessorDict[streamId]) { delete streamProcessorDict[streamId]; } if (switchRequestHistory) { switchRequestHistory.clearForStream(streamId); } if (abandonmentStateDict[streamId]) { delete abandonmentStateDict[streamId]; } } instance = { checkPlaybackQuality, clearDataForStream, getAbandonmentStateFor, getInitialBitrateFor, getOptimalRepresentationForBitrate, getPossibleVoRepresentations, getRepresentationByAbsoluteIndex, initialize, isPlayingAtLowestQuality, isPlayingAtTopQuality, registerStreamType, reset, setConfig, setPlaybackQuality, setWindowResizeEventCalled, unRegisterStreamType, }; setup(); return instance; } AbrController.__dashjs_factory_name = 'AbrController'; const factory = FactoryMaker.getSingletonFactory(AbrController); FactoryMaker.updateSingletonFactory(AbrController.__dashjs_factory_name, factory); export default factory; × Search results Close "},"streaming_rules_abr_ABRRulesCollection.js.html":{"id":"streaming_rules_abr_ABRRulesCollection.js.html","title":"Source: streaming/rules/abr/ABRRulesCollection.js","body":" dash.js Modules DashAdapterDashMetricsMediaPlayerOfflineControllerProtectionControllerSettings Classes ErrorsMediaPlayerEventsMediaPlayerModelMetricsReportingEventsMssErrorsOfflineErrorsOfflineEventsProtectionErrorsProtectionEventsThroughputControllerThroughputModel Events MediaPlayerEvents#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIESMediaPlayerEvents#event:AST_IN_FUTUREMediaPlayerEvents#event:BASE_URLS_UPDATEDMediaPlayerEvents#event:BUFFER_EMPTYMediaPlayerEvents#event:BUFFER_LEVEL_STATE_CHANGEDMediaPlayerEvents#event:BUFFER_LEVEL_UPDATEDMediaPlayerEvents#event:BUFFER_LOADEDMediaPlayerEvents#event:CAN_PLAYMediaPlayerEvents#event:CAN_PLAY_THROUGHMediaPlayerEvents#event:CAPTION_CONTAINER_RESIZEMediaPlayerEvents#event:CAPTION_RENDEREDMediaPlayerEvents#event:CONFORMANCE_VIOLATIONMediaPlayerEvents#event:CONTENT_STEERING_REQUEST_COMPLETEDMediaPlayerEvents#event:DYNAMIC_TO_STATICMediaPlayerEvents#event:ERRORMediaPlayerEvents#event:EVENT_MODE_ON_RECEIVEMediaPlayerEvents#event:EVENT_MODE_ON_STARTMediaPlayerEvents#event:FRAGMENT_LOADING_ABANDONEDMediaPlayerEvents#event:FRAGMENT_LOADING_COMPLETEDMediaPlayerEvents#event:FRAGMENT_LOADING_PROGRESSMediaPlayerEvents#event:FRAGMENT_LOADING_STARTEDMediaPlayerEvents#event:INBAND_PRFTMediaPlayerEvents#event:LOGMediaPlayerEvents#event:MANIFEST_LOADEDMediaPlayerEvents#event:MANIFEST_LOADING_FINISHEDMediaPlayerEvents#event:MANIFEST_LOADING_STARTEDMediaPlayerEvents#event:MANIFEST_VALIDITY_CHANGEDMediaPlayerEvents#event:METRIC_ADDEDMediaPlayerEvents#event:METRIC_CHANGEDMediaPlayerEvents#event:METRIC_UPDATEDMediaPlayerEvents#event:METRICS_CHANGEDMediaPlayerEvents#event:PERIOD_SWITCH_COMPLETEDMediaPlayerEvents#event:PERIOD_SWITCH_STARTEDMediaPlayerEvents#event:PLAYBACK_ENDEDMediaPlayerEvents#event:PLAYBACK_ERRORMediaPlayerEvents#event:PLAYBACK_LOADED_DATAMediaPlayerEvents#event:PLAYBACK_METADATA_LOADEDMediaPlayerEvents#event:PLAYBACK_NOT_ALLOWEDMediaPlayerEvents#event:PLAYBACK_PAUSEDMediaPlayerEvents#event:PLAYBACK_PLAYINGMediaPlayerEvents#event:PLAYBACK_PROGRESSMediaPlayerEvents#event:PLAYBACK_RATE_CHANGEDMediaPlayerEvents#event:PLAYBACK_SEEKEDMediaPlayerEvents#event:PLAYBACK_SEEKINGMediaPlayerEvents#event:PLAYBACK_STALLEDMediaPlayerEvents#event:PLAYBACK_STARTEDMediaPlayerEvents#event:PLAYBACK_TIME_UPDATEDMediaPlayerEvents#event:PLAYBACK_VOLUME_CHANGEDMediaPlayerEvents#event:PLAYBACK_WAITINGMediaPlayerEvents#event:QUALITY_CHANGE_RENDEREDMediaPlayerEvents#event:QUALITY_CHANGE_REQUESTEDMediaPlayerEvents#event:REPRESENTATION_SWITCHMediaPlayerEvents#event:STREAM_ACTIVATEDMediaPlayerEvents#event:STREAM_DEACTIVATEDMediaPlayerEvents#event:STREAM_INITIALIZEDMediaPlayerEvents#event:STREAM_INITIALIZINGMediaPlayerEvents#event:STREAM_TEARDOWN_COMPLETEMediaPlayerEvents#event:STREAM_UPDATEDMediaPlayerEvents#event:TEXT_TRACK_ADDEDMediaPlayerEvents#event:TEXT_TRACKS_ADDEDMediaPlayerEvents#event:THROUGHPUT_MEASUREMENT_STOREDMediaPlayerEvents#event:TRACK_CHANGE_RENDEREDMediaPlayerEvents#event:TTML_PARSEDMediaPlayerEvents#event:TTML_TO_PARSEMetricsReportingEvents#event:CMCD_DATA_GENERATEDOfflineEvents#event:OFFLINE_RECORD_FINISHEDOfflineEvents#event:OFFLINE_RECORD_LOADEDMETADATAOfflineEvents#event:OFFLINE_RECORD_STARTEDOfflineEvents#event:OFFLINE_RECORD_STOPPEDProtectionEvents#event:KEY_ADDEDProtectionEvents#event:KEY_ERRORProtectionEvents#event:KEY_MESSAGEProtectionEvents#event:KEY_SESSION_CLOSEDProtectionEvents#event:KEY_SESSION_CREATEDProtectionEvents#event:KEY_SESSION_REMOVEDProtectionEvents#event:KEY_STATUSES_CHANGEDProtectionEvents#event:KEY_SYSTEM_ACCESS_COMPLETEProtectionEvents#event:KEY_SYSTEM_SELECTEDProtectionEvents#event:LICENSE_REQUEST_COMPLETEProtectionEvents#event:LICENSE_REQUEST_SENDINGProtectionEvents#event:PROTECTION_CREATEDProtectionEvents#event:PROTECTION_DESTROYED Global LICENSE_SERVER_MANIFEST_CONFIGURATIONS Source: streaming/rules/abr/ABRRulesCollection.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 * PRO