UNPKG

dashjs

Version:

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

952 lines (811 loc) 67.9 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>dash.js Source: streaming/StreamProcessor.js</title> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/sunlight.default.css"> <link type="text/css" rel="stylesheet" href="styles/site.spacelab.css"> </head> <body> <div class="navbar navbar-default navbar-fixed-top navbar-inverse"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="index.html">dash.js</a> <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#topNavigation"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> </div> <div class="navbar-collapse collapse" id="topNavigation"> <ul class="nav navbar-nav"> <li class="dropdown"> <a href="modules.list.html" class="dropdown-toggle" data-toggle="dropdown">Modules<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="module-DashAdapter.html">DashAdapter</a></li><li><a href="module-DashMetrics.html">DashMetrics</a></li><li><a href="module-MediaPlayer.html">MediaPlayer</a></li><li><a href="module-OfflineController.html">OfflineController</a></li><li><a href="module-ProtectionController.html">ProtectionController</a></li><li><a href="module-Settings.html">Settings</a></li> </ul> </li> <li class="dropdown"> <a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="Errors.html">Errors</a></li><li><a href="MediaPlayerEvents.html">MediaPlayerEvents</a></li><li><a href="MediaPlayerModel.html">MediaPlayerModel</a></li><li><a href="MetricsReportingEvents.html">MetricsReportingEvents</a></li><li><a href="MssErrors.html">MssErrors</a></li><li><a href="OfflineErrors.html">OfflineErrors</a></li><li><a href="OfflineEvents.html">OfflineEvents</a></li><li><a href="ProtectionErrors.html">ProtectionErrors</a></li><li><a href="ProtectionEvents.html">ProtectionEvents</a></li> </ul> </li> <li class="dropdown"> <a href="events.list.html" class="dropdown-toggle" data-toggle="dropdown">Events<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="MediaPlayerEvents.html#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIES">MediaPlayerEvents#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIES</a></li><li><a href="MediaPlayerEvents.html#event:AST_IN_FUTURE">MediaPlayerEvents#event:AST_IN_FUTURE</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_EMPTY">MediaPlayerEvents#event:BUFFER_EMPTY</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_STATE_CHANGED">MediaPlayerEvents#event:BUFFER_LEVEL_STATE_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_UPDATED">MediaPlayerEvents#event:BUFFER_LEVEL_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LOADED">MediaPlayerEvents#event:BUFFER_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:CAN_PLAY">MediaPlayerEvents#event:CAN_PLAY</a></li><li><a href="MediaPlayerEvents.html#event:CAN_PLAY_THROUGH">MediaPlayerEvents#event:CAN_PLAY_THROUGH</a></li><li><a href="MediaPlayerEvents.html#event:CAPTION_CONTAINER_RESIZE">MediaPlayerEvents#event:CAPTION_CONTAINER_RESIZE</a></li><li><a href="MediaPlayerEvents.html#event:CAPTION_RENDERED">MediaPlayerEvents#event:CAPTION_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:CONFORMANCE_VIOLATION">MediaPlayerEvents#event:CONFORMANCE_VIOLATION</a></li><li><a href="MediaPlayerEvents.html#event:CONTENT_STEERING_REQUEST_COMPLETED">MediaPlayerEvents#event:CONTENT_STEERING_REQUEST_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:DYNAMIC_TO_STATIC">MediaPlayerEvents#event:DYNAMIC_TO_STATIC</a></li><li><a href="MediaPlayerEvents.html#event:ERROR">MediaPlayerEvents#event:ERROR</a></li><li><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_RECEIVE">MediaPlayerEvents#event:EVENT_MODE_ON_RECEIVE</a></li><li><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_START">MediaPlayerEvents#event:EVENT_MODE_ON_START</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_ABANDONED">MediaPlayerEvents#event:FRAGMENT_LOADING_ABANDONED</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_COMPLETED">MediaPlayerEvents#event:FRAGMENT_LOADING_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_PROGRESS">MediaPlayerEvents#event:FRAGMENT_LOADING_PROGRESS</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_STARTED">MediaPlayerEvents#event:FRAGMENT_LOADING_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:LOG">MediaPlayerEvents#event:LOG</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_LOADED">MediaPlayerEvents#event:MANIFEST_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_VALIDITY_CHANGED">MediaPlayerEvents#event:MANIFEST_VALIDITY_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_ADDED">MediaPlayerEvents#event:METRIC_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_CHANGED">MediaPlayerEvents#event:METRIC_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_UPDATED">MediaPlayerEvents#event:METRIC_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:METRICS_CHANGED">MediaPlayerEvents#event:METRICS_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_COMPLETED">MediaPlayerEvents#event:PERIOD_SWITCH_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_STARTED">MediaPlayerEvents#event:PERIOD_SWITCH_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_ENDED">MediaPlayerEvents#event:PLAYBACK_ENDED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_ERROR">MediaPlayerEvents#event:PLAYBACK_ERROR</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_LOADED_DATA">MediaPlayerEvents#event:PLAYBACK_LOADED_DATA</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_METADATA_LOADED">MediaPlayerEvents#event:PLAYBACK_METADATA_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_NOT_ALLOWED">MediaPlayerEvents#event:PLAYBACK_NOT_ALLOWED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PAUSED">MediaPlayerEvents#event:PLAYBACK_PAUSED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PLAYING">MediaPlayerEvents#event:PLAYBACK_PLAYING</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PROGRESS">MediaPlayerEvents#event:PLAYBACK_PROGRESS</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_RATE_CHANGED">MediaPlayerEvents#event:PLAYBACK_RATE_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKED">MediaPlayerEvents#event:PLAYBACK_SEEKED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKING">MediaPlayerEvents#event:PLAYBACK_SEEKING</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_STALLED">MediaPlayerEvents#event:PLAYBACK_STALLED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_STARTED">MediaPlayerEvents#event:PLAYBACK_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_TIME_UPDATED">MediaPlayerEvents#event:PLAYBACK_TIME_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_VOLUME_CHANGED">MediaPlayerEvents#event:PLAYBACK_VOLUME_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_WAITING">MediaPlayerEvents#event:PLAYBACK_WAITING</a></li><li><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_RENDERED">MediaPlayerEvents#event:QUALITY_CHANGE_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_REQUESTED">MediaPlayerEvents#event:QUALITY_CHANGE_REQUESTED</a></li><li><a href="MediaPlayerEvents.html#event:REPRESENTATION_SWITCH">MediaPlayerEvents#event:REPRESENTATION_SWITCH</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_ACTIVATED">MediaPlayerEvents#event:STREAM_ACTIVATED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_DEACTIVATED">MediaPlayerEvents#event:STREAM_DEACTIVATED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZED">MediaPlayerEvents#event:STREAM_INITIALIZED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZING">MediaPlayerEvents#event:STREAM_INITIALIZING</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_TEARDOWN_COMPLETE">MediaPlayerEvents#event:STREAM_TEARDOWN_COMPLETE</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_UPDATED">MediaPlayerEvents#event:STREAM_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:TEXT_TRACK_ADDED">MediaPlayerEvents#event:TEXT_TRACK_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:TEXT_TRACKS_ADDED">MediaPlayerEvents#event:TEXT_TRACKS_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:TRACK_CHANGE_RENDERED">MediaPlayerEvents#event:TRACK_CHANGE_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:TTML_PARSED">MediaPlayerEvents#event:TTML_PARSED</a></li><li><a href="MediaPlayerEvents.html#event:TTML_TO_PARSE">MediaPlayerEvents#event:TTML_TO_PARSE</a></li><li><a href="MetricsReportingEvents.html#event:CMCD_DATA_GENERATED">MetricsReportingEvents#event:CMCD_DATA_GENERATED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_FINISHED">OfflineEvents#event:OFFLINE_RECORD_FINISHED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_LOADEDMETADATA">OfflineEvents#event:OFFLINE_RECORD_LOADEDMETADATA</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_STARTED">OfflineEvents#event:OFFLINE_RECORD_STARTED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_STOPPED">OfflineEvents#event:OFFLINE_RECORD_STOPPED</a></li><li><a href="ProtectionEvents.html#event:KEY_ADDED">ProtectionEvents#event:KEY_ADDED</a></li><li><a href="ProtectionEvents.html#event:KEY_ERROR">ProtectionEvents#event:KEY_ERROR</a></li><li><a href="ProtectionEvents.html#event:KEY_MESSAGE">ProtectionEvents#event:KEY_MESSAGE</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_CLOSED">ProtectionEvents#event:KEY_SESSION_CLOSED</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_CREATED">ProtectionEvents#event:KEY_SESSION_CREATED</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_REMOVED">ProtectionEvents#event:KEY_SESSION_REMOVED</a></li><li><a href="ProtectionEvents.html#event:KEY_STATUSES_CHANGED">ProtectionEvents#event:KEY_STATUSES_CHANGED</a></li><li><a href="ProtectionEvents.html#event:KEY_SYSTEM_SELECTED">ProtectionEvents#event:KEY_SYSTEM_SELECTED</a></li><li><a href="ProtectionEvents.html#event:LICENSE_REQUEST_COMPLETE">ProtectionEvents#event:LICENSE_REQUEST_COMPLETE</a></li><li><a href="ProtectionEvents.html#event:LICENSE_REQUEST_SENDING">ProtectionEvents#event:LICENSE_REQUEST_SENDING</a></li><li><a href="ProtectionEvents.html#event:PROTECTION_CREATED">ProtectionEvents#event:PROTECTION_CREATED</a></li><li><a href="ProtectionEvents.html#event:PROTECTION_DESTROYED">ProtectionEvents#event:PROTECTION_DESTROYED</a></li> </ul> </li> <li class="dropdown"> <a href="global.html" class="dropdown-toggle" data-toggle="dropdown">Global<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="global.html#LICENSE_SERVER_MANIFEST_CONFIGURATIONS">LICENSE_SERVER_MANIFEST_CONFIGURATIONS</a></li> </ul> </li> </ul> <div class="col-sm-3 col-md-3"> <form class="navbar-form" role="search"> <div class="input-group"> <input type="text" class="form-control" placeholder="Search" name="q" id="search-input"> <div class="input-group-btn"> <button class="btn btn-default" id="search-submit"><i class="glyphicon glyphicon-search"></i></button> </div> </div> </form> </div> </div> </div> </div> <div class="container" id="toc-content"> <div class="row"> <div class="col-md-12"> <div id="main"> <h1 class="page-title">Source: streaming/StreamProcessor.js</h1> <section> <article> <pre class="sunlight-highlight-javascript linenums">/** * 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 Constants from './constants/Constants'; import DashConstants from '../dash/constants/DashConstants'; import MetricsConstants from './constants/MetricsConstants'; import FragmentModel from './models/FragmentModel'; import BufferController from './controllers/BufferController'; import NotFragmentedTextBufferController from './text/NotFragmentedTextBufferController'; import ScheduleController from './controllers/ScheduleController'; import RepresentationController from '../dash/controllers/RepresentationController'; import FactoryMaker from '../core/FactoryMaker'; import {checkInteger} from './utils/SupervisorTools'; import EventBus from '../core/EventBus'; import Events from '../core/events/Events'; import DashHandler from '../dash/DashHandler'; import Errors from '../core/errors/Errors'; import DashJSError from './vo/DashJSError'; import Debug from '../core/Debug'; import RequestModifier from './utils/RequestModifier'; import URLUtils from '../streaming/utils/URLUtils'; import BoxParser from './utils/BoxParser'; import {PlayListTrace} from './vo/metrics/PlayList'; import SegmentsController from '../dash/controllers/SegmentsController'; import {HTTPRequest} from './vo/metrics/HTTPRequest'; function StreamProcessor(config) { config = config || {}; let context = this.context; let eventBus = EventBus(context).getInstance(); let streamInfo = config.streamInfo; let type = config.type; let errHandler = config.errHandler; let mimeType = config.mimeType; let timelineConverter = config.timelineConverter; let adapter = config.adapter; let manifestModel = config.manifestModel; let mediaPlayerModel = config.mediaPlayerModel; let fragmentModel = config.fragmentModel; let abrController = config.abrController; let playbackController = config.playbackController; let mediaController = config.mediaController; let textController = config.textController; let dashMetrics = config.dashMetrics; let settings = config.settings; let boxParser = config.boxParser; let segmentBlacklistController = config.segmentBlacklistController; let instance, logger, isDynamic, mediaInfo, mediaInfoArr, bufferController, scheduleController, representationController, shouldUseExplicitTimeForRequest, qualityChangeInProgress, dashHandler, segmentsController, bufferingTime; function setup() { logger = Debug(context).getInstance().getLogger(instance); resetInitialSettings(); eventBus.on(Events.DATA_UPDATE_COMPLETED, _onDataUpdateCompleted, instance, { priority: EventBus.EVENT_PRIORITY_HIGH }); // High priority to be notified before Stream eventBus.on(Events.INIT_FRAGMENT_NEEDED, _onInitFragmentNeeded, instance); eventBus.on(Events.MEDIA_FRAGMENT_NEEDED, _onMediaFragmentNeeded, instance); eventBus.on(Events.MEDIA_FRAGMENT_LOADED, _onMediaFragmentLoaded, instance); eventBus.on(Events.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance); eventBus.on(Events.BUFFER_CLEARED, _onBufferCleared, instance); eventBus.on(Events.SEEK_TARGET, _onSeekTarget, instance); eventBus.on(Events.FRAGMENT_LOADING_ABANDONED, _onFragmentLoadingAbandoned, instance); eventBus.on(Events.FRAGMENT_LOADING_COMPLETED, _onFragmentLoadingCompleted, instance); eventBus.on(Events.QUOTA_EXCEEDED, _onQuotaExceeded, instance); eventBus.on(Events.SET_FRAGMENTED_TEXT_AFTER_DISABLED, _onSetFragmentedTextAfterDisabled, instance); eventBus.on(Events.SET_NON_FRAGMENTED_TEXT, _onSetNonFragmentedText, instance); eventBus.on(Events.SOURCE_BUFFER_ERROR, _onSourceBufferError, instance); } function initialize(mediaSource, hasVideoTrack, isFragmented) { segmentsController = SegmentsController(context).create({ events: Events, eventBus, streamInfo, timelineConverter, dashConstants: DashConstants, segmentBaseController: config.segmentBaseController, type }); dashHandler = DashHandler(context).create({ streamInfo, type, timelineConverter, dashMetrics, mediaPlayerModel, baseURLController: config.baseURLController, errHandler, segmentsController, settings, boxParser, events: Events, eventBus, errors: Errors, debug: Debug(context).getInstance(), requestModifier: RequestModifier(context).getInstance(), dashConstants: DashConstants, constants: Constants, urlUtils: URLUtils(context).getInstance() }); isDynamic = streamInfo.manifestInfo.isDynamic; // Create/initialize controllers dashHandler.initialize(isDynamic); abrController.registerStreamType(type, instance); representationController = RepresentationController(context).create({ streamInfo, type, abrController, dashMetrics, playbackController, timelineConverter, dashConstants: DashConstants, events: Events, eventBus, errors: Errors, isDynamic, segmentsController }); bufferController = _createBufferControllerForType(type, isFragmented); if (bufferController) { bufferController.initialize(mediaSource); } scheduleController = ScheduleController(context).create({ streamInfo, type, mimeType, adapter, dashMetrics, mediaPlayerModel, fragmentModel, abrController, playbackController, textController, mediaController, bufferController, settings }); scheduleController.initialize(hasVideoTrack); bufferingTime = 0; shouldUseExplicitTimeForRequest = false; } function getStreamId() { return streamInfo.id; } function getType() { return type; } function getIsTextTrack() { return adapter.getIsTextTrack(representationController.getData()); } function resetInitialSettings() { mediaInfoArr = []; mediaInfo = null; bufferingTime = 0; shouldUseExplicitTimeForRequest = false; qualityChangeInProgress = false; } function reset(errored, keepBuffers) { if (dashHandler) { dashHandler.reset(); } if (bufferController) { bufferController.reset(errored, keepBuffers); bufferController = null; } if (scheduleController) { scheduleController.reset(); scheduleController = null; } if (representationController) { representationController.reset(); representationController = null; } if (segmentsController) { segmentsController = null; } if (abrController) { abrController.unRegisterStreamType(getStreamId(), type); } eventBus.off(Events.DATA_UPDATE_COMPLETED, _onDataUpdateCompleted, instance); eventBus.off(Events.INIT_FRAGMENT_NEEDED, _onInitFragmentNeeded, instance); eventBus.off(Events.MEDIA_FRAGMENT_NEEDED, _onMediaFragmentNeeded, instance); eventBus.off(Events.MEDIA_FRAGMENT_LOADED, _onMediaFragmentLoaded, instance); eventBus.off(Events.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance); eventBus.off(Events.BUFFER_CLEARED, _onBufferCleared, instance); eventBus.off(Events.SEEK_TARGET, _onSeekTarget, instance); eventBus.off(Events.FRAGMENT_LOADING_ABANDONED, _onFragmentLoadingAbandoned, instance); eventBus.off(Events.FRAGMENT_LOADING_COMPLETED, _onFragmentLoadingCompleted, instance); eventBus.off(Events.SET_FRAGMENTED_TEXT_AFTER_DISABLED, _onSetFragmentedTextAfterDisabled, instance); eventBus.off(Events.SET_NON_FRAGMENTED_TEXT, _onSetNonFragmentedText, instance); eventBus.off(Events.QUOTA_EXCEEDED, _onQuotaExceeded, instance); eventBus.off(Events.SOURCE_BUFFER_ERROR, _onSourceBufferError, instance); resetInitialSettings(); type = null; streamInfo = null; } function isUpdating() { return representationController ? representationController.isUpdating() : false; } /** * When a seek within the corresponding period occurs this function initiates the clearing of the buffer and sets the correct buffering time. * @param {object} e * @private */ function prepareInnerPeriodPlaybackSeeking(e) { return new Promise((resolve) => { // Stop segment requests until we have figured out for which time we need to request a segment. We don't want to replace existing segments. scheduleController.clearScheduleTimer(); fragmentModel.abortRequests(); // Abort operations to the SourceBuffer Sink and reset the BufferControllers isBufferingCompleted state. bufferController.prepareForPlaybackSeek() .then(() => { // Clear the buffer. We need to prune everything which is not in the target interval. const clearRanges = bufferController.getAllRangesWithSafetyFactor(e.seekTime); // When everything has been pruned go on return bufferController.clearBuffers(clearRanges); }) .then(() => { // Figure out the correct segment request time. const continuousBufferTime = bufferController.getContinuousBufferTimeForTargetTime(e.seekTime); // If the buffer is continuous and exceeds the duration of the period we are still done buffering. We need to trigger the buffering completed event in order to start prebuffering upcoming periods again if (!isNaN(continuousBufferTime) &amp;&amp; !isNaN(streamInfo.duration) &amp;&amp; isFinite(streamInfo.duration) &amp;&amp; continuousBufferTime >= streamInfo.start + streamInfo.duration) { bufferController.setIsBufferingCompleted(true); resolve(); } else { const targetTime = isNaN(continuousBufferTime) ? e.seekTime : continuousBufferTime; setExplicitBufferingTime(targetTime); bufferController.setSeekTarget(targetTime); const promises = []; // append window has been reset by abort() operation. Set the correct values again promises.push(bufferController.updateAppendWindow()); // Timestamp offset couldve been changed by preloading period const representationInfo = getRepresentationInfo(); promises.push(bufferController.updateBufferTimestampOffset(representationInfo)); Promise.all(promises) .then(() => { // We might have aborted the append operation of an init segment. Append init segment again. scheduleController.setInitSegmentRequired(true); // Right after a seek we should not immediately check the playback quality scheduleController.setCheckPlaybackQuality(false); scheduleController.startScheduleTimer(); resolve(); }); } }) .catch((e) => { logger.error(e); }); }); } /** * Seek outside of the current period. * @return {Promise&lt;unknown>} */ function prepareOuterPeriodPlaybackSeeking() { return new Promise((resolve, reject) => { try { // Stop scheduling scheduleController.clearScheduleTimer(); // Abort all ongoing requests fragmentModel.abortRequests(); // buffering not complete anymore and abort current append operation to SourceBuffer bufferController.prepareForPlaybackSeek() .then(() => { // Clear the buffers completely. return bufferController.pruneAllSafely(); }) .then(() => { resolve(); }); } catch (e) { reject(e); } }); } /** * ScheduleController indicates that an init segment needs to be fetched. * @param {object} e * @param {boolean} rescheduleIfNoRequest - Defines whether we reschedule in case no valid request could be generated * @private */ function _onInitFragmentNeeded(e, rescheduleIfNoRequest = true) { // Event propagation may have been stopped (see MssHandler) if (!e.sender) return; if (playbackController.getIsManifestUpdateInProgress()) { _noValidRequest(); return; } if (getIsTextTrack() &amp;&amp; !textController.isTextEnabled()) return; if (bufferController &amp;&amp; e.representationId) { if (!bufferController.appendInitSegmentFromCache(e.representationId)) { const rep = representationController.getCurrentRepresentation(); // Dummy init segment (fragmented tracks without initialization segment) if (rep.range === 0) { _onMediaFragmentNeeded(); return; } // Init segment not in cache, send new request const request = dashHandler ? dashHandler.getInitRequest(mediaInfo, rep) : null; if (request) { fragmentModel.executeRequest(request); } else if (rescheduleIfNoRequest) { scheduleController.setInitSegmentRequired(true); _noValidRequest(); } } } } /** * ScheduleController indicates that a media segment is needed * @param {boolean} rescheduleIfNoRequest - Defines whether we reschedule in case no valid request could be generated * @private */ function _onMediaFragmentNeeded(e, rescheduleIfNoRequest = true) { // Don't schedule next fragments while updating manifest or pruning to avoid buffer inconsistencies if (playbackController.getIsManifestUpdateInProgress() || bufferController.getIsPruningInProgress()) { _noValidRequest(); return; } let request = _getFragmentRequest(); if (request) { shouldUseExplicitTimeForRequest = false; _mediaRequestGenerated(request); } else { _noMediaRequestGenerated(rescheduleIfNoRequest); } } /** * If we generated a valid media request we can execute the request. In some cases the segment might be blacklisted. * @param {object} request * @private */ function _mediaRequestGenerated(request) { if (!isNaN(request.startTime + request.duration)) { bufferingTime = request.startTime + request.duration; } request.delayLoadingTime = new Date().getTime() + scheduleController.getTimeToLoadDelay(); scheduleController.setTimeToLoadDelay(0); if (!_shouldIgnoreRequest(request)) { logger.debug(`Next fragment request url for stream id ${streamInfo.id} and media type ${type} is ${request.url}`); fragmentModel.executeRequest(request); } else { logger.warn(`Fragment request url ${request.url} for stream id ${streamInfo.id} and media type ${type} is on the ignore list and will be skipped`); _noValidRequest(); } } /** * We could not generate a valid request. Check if the media is finished, we are stuck in a gap or simply need to wait for the next segment to be available. * @param {boolean} rescheduleIfNoRequest * @private */ function _noMediaRequestGenerated(rescheduleIfNoRequest) { const representation = representationController.getCurrentRepresentation(); // If this statement is true we might be stuck. A static manifest does not change and we did not find a valid request for the target time // There is no point in trying again. We need to adjust the time in order to find a valid request. This can happen if the user/app seeked into a gap. // For dynamic manifests this can also happen especially if we jump over the gap in the previous period and are using SegmentTimeline and in case there is a positive eptDelta at the beginning of the period we are stuck. if (settings.get().streaming.gaps.enableSeekFix &amp;&amp; (shouldUseExplicitTimeForRequest || playbackController.getTime() === 0)) { let adjustedTime; if (!isDynamic) { adjustedTime = dashHandler.getValidTimeAheadOfTargetTime(bufferingTime, mediaInfo, representation, settings.get().streaming.gaps.threshold); } else if (isDynamic &amp;&amp; representation.segmentInfoType === DashConstants.SEGMENT_TIMELINE) { // If we find a valid request ahead of the current time then we are in a gap. Segments are only added at the end of the timeline adjustedTime = dashHandler.getValidTimeAheadOfTargetTime(bufferingTime, mediaInfo, representation, settings.get().streaming.gaps.threshold,); } if (!isNaN(adjustedTime) &amp;&amp; adjustedTime !== bufferingTime) { if (playbackController.isSeeking() || playbackController.getTime() === 0) { // If we are seeking then playback is stalled. Do a seek to get out of this situation logger.warn(`Adjusting playback time ${adjustedTime} because of gap in the manifest. Seeking by ${adjustedTime - bufferingTime}`); playbackController.seek(adjustedTime, false, false); } else { // If we are not seeking we should still be playing but we cant find anything to buffer. So we adjust the buffering time and leave the gap jump to the GapController logger.warn(`Adjusting buffering time ${adjustedTime} because of gap in the manifest. Adjusting time by ${adjustedTime - bufferingTime}`); setExplicitBufferingTime(adjustedTime) if (rescheduleIfNoRequest) { _noValidRequest(); } } return; } } // Check if the media is finished. If so, no need to schedule another request const isLastSegmentRequested = dashHandler.isLastSegmentRequested(representation, bufferingTime); if (isLastSegmentRequested) { const segmentIndex = dashHandler.getCurrentIndex(); logger.debug(`Segment requesting for stream ${streamInfo.id} has finished`); eventBus.trigger(Events.STREAM_REQUESTING_COMPLETED, { segmentIndex }, { streamId: streamInfo.id, mediaType: type }); bufferController.segmentRequestingCompleted(segmentIndex); scheduleController.clearScheduleTimer(); return; } if (rescheduleIfNoRequest) { _noValidRequest(); } } /** * In certain situations we need to ignore a request. For instance, if a segment is blacklisted because it caused an MSE error. * @private */ function _shouldIgnoreRequest(request) { let blacklistUrl = request.url; if (request.range) { blacklistUrl = blacklistUrl.concat('_', request.range); } return segmentBlacklistController.contains(blacklistUrl) } /** * Get the init or media segment request using the DashHandler. * @return {null|FragmentRequest|null} * @private */ function _getFragmentRequest() { const representationInfo = getRepresentationInfo(); let request; if (isNaN(bufferingTime) || (getType() === Constants.TEXT &amp;&amp; !textController.isTextEnabled())) { return null; } // Use time just whenever is strictly needed const useTime = shouldUseExplicitTimeForRequest; if (dashHandler) { const representation = representationController &amp;&amp; representationInfo ? representationController.getRepresentationForQuality(representationInfo.quality) : null; if (useTime) { request = dashHandler.getSegmentRequestForTime(mediaInfo, representation, bufferingTime); } else { request = dashHandler.getNextSegmentRequest(mediaInfo, representation); } } return request; } /** * Whenever we can not generate a valid request we restart scheduling according to the timeouts defined in the settings. * @private */ function _noValidRequest() { scheduleController.startScheduleTimer(playbackController.getLowLatencyModeEnabled() ? settings.get().streaming.scheduling.lowLatencyTimeout : settings.get().streaming.scheduling.defaultTimeout); } function _onDataUpdateCompleted(e) { if (!e.error) { // Update representation if no error scheduleController.setCurrentRepresentation(adapter.convertRepresentationToRepresentationInfo(e.currentRepresentation)); if (!bufferController.getIsBufferingCompleted()) { bufferController.updateBufferTimestampOffset(e.currentRepresentation); } } } function _onBufferLevelStateChanged(e) { dashMetrics.addBufferState(type, e.state, scheduleController.getBufferTarget()); if (e.state === MetricsConstants.BUFFER_EMPTY &amp;&amp; !playbackController.isSeeking()) { logger.info('Buffer is empty! Stalling!'); dashMetrics.pushPlayListTraceMetrics(new Date(), PlayListTrace.REBUFFERING_REASON); } } function _onBufferCleared(e) { // Remove executed requests not buffered anymore fragmentModel.syncExecutedRequestsWithBufferedRange( bufferController.getBuffer().getAllBufferRanges(), streamInfo.duration); // If buffer removed ahead current time (QuotaExceededError or automatic buffer pruning) then adjust current index handler time if (e.quotaExceeded &amp;&amp; e.from > playbackController.getTime()) { setExplicitBufferingTime(e.from); } // (Re)start schedule once buffer has been pruned after a QuotaExceededError if (e.hasEnoughSpaceToAppend &amp;&amp; e.quotaExceeded) { scheduleController.startScheduleTimer(); } } /** * This function is called when the corresponding SourceBuffer encountered an error. * We blacklist the last segment assuming it caused the error * @param {object} e * @private */ function _onSourceBufferError(e) { if (!e || !e.lastRequestAppended || !e.lastRequestAppended.url) { return; } let blacklistUrl = e.lastRequestAppended.url; if (e.lastRequestAppended.range) { blacklistUrl = blacklistUrl.concat('_', e.lastRequestAppended.range); } logger.warn(`Blacklisting segment with url ${blacklistUrl}`); segmentBlacklistController.add(blacklistUrl); } /** * The quality has changed which means we have switched to a different representation. * If we want to aggressively replace existing parts in the buffer we need to make sure that the new quality is higher than the already buffered one. * @param {object} e * @private */ function prepareQualityChange(e) { logger.debug(`Preparing quality switch for type ${type}`); const newQuality = e.newQuality; qualityChangeInProgress = true; // Stop scheduling until we are done with preparing the quality switch scheduleController.clearScheduleTimer(); const representationInfo = getRepresentationInfo(newQuality); scheduleController.setCurrentRepresentation(representationInfo); representationController.prepareQualityChange(newQuality); // Abort the current request to avoid inconsistencies and in case a rule such as AbandonRequestRule has forced a quality switch. A quality switch can also be triggered manually by the application. // If we update the buffer values now, or initialize a request to the new init segment, the currently downloading media segment might "work" with wrong values. // Everything that is already in the buffer queue is ok and will be handled by the corresponding function below depending on the switch mode. fragmentModel.abortRequests(); // In any case we need to update the MSE.timeOffset bufferController.updateBufferTimestampOffset(representationInfo) .then(() => { // If the switch should occur immediately we need to replace existing stuff in the buffer if (e.reason &amp;&amp; e.reason.forceReplace) { _prepareReplacementQualitySwitch(); } // If fast switch is enabled we check if we are supposed to replace existing stuff in the buffer else if (settings.get().streaming.buffer.fastSwitchEnabled) { _prepareForFastQualitySwitch(representationInfo); } // Default quality switch. We append the new quality to the already buffered stuff else { _prepareForDefaultQualitySwitch(); } dashMetrics.pushPlayListTraceMetrics(new Date(), PlayListTrace.REPRESENTATION_SWITCH_STOP_REASON); dashMetrics.createPlaylistTraceMetrics(representationInfo.id, playbackController.getTime() * 1000, playbackController.getPlaybackRate()); }) } function _prepareReplacementQualitySwitch() { // Inform other classes like the GapController that we are replacing existing stuff eventBus.trigger(Events.BUFFER_REPLACEMENT_STARTED, { mediaType: type, streamId: streamInfo.id }, { mediaType: type, streamId: streamInfo.id }); // Abort appending segments to the buffer. Also adjust the appendWindow as we might have been in the progress of prebuffering stuff. bufferController.prepareForReplacementQualitySwitch() .then(() => { _bufferClearedForReplacement(); qualityChangeInProgress = false; }) .catch(() => { _bufferClearedForReplacement(); qualityChangeInProgress = false; }); } function _prepareForFastQualitySwitch(representationInfo) { // if we switch up in quality and need to replace existing parts in the buffer we need to adjust the buffer target const time = playbackController.getTime(); let safeBufferLevel = 1.5 * (!isNaN(representationInfo.fragmentDuration) ? representationInfo.fragmentDuration : 1); const request = fragmentModel.getRequests({ state: FragmentModel.FRAGMENT_MODEL_EXECUTED, time: time + safeBufferLevel, threshold: 0 })[0]; if (request &amp;&amp; !getIsTextTrack()) { const bufferLevel = bufferController.getBufferLevel(); const abandonmentState = abrController.getAbandonmentStateFor(streamInfo.id, type); // The quality we originally requested was lower than the new quality if (request.quality &lt; representationInfo.quality &amp;&amp; bufferLevel >= safeBufferLevel &amp;&amp; abandonmentState !== MetricsConstants.ABANDON_LOAD) { const targetTime = time + safeBufferLevel; setExplicitBufferingTime(targetTime); scheduleController.setCheckPlaybackQuality(false); scheduleController.startScheduleTimer(); } else { _prepareForDefaultQualitySwitch(); } } else { scheduleController.startScheduleTimer(); } qualityChangeInProgress = false; } function _prepareForDefaultQualitySwitch() { // We might have aborted the current request. We need to set an explicit buffer time based on what we already have in the buffer. _bufferClearedForNonReplacement() qualityChangeInProgress = false; } /** * We have canceled the download of a fragment and need to adjust the buffer time or reload an init segment * @param {object} e */ function _onFragmentLoadingAbandoned(e) { logger.info('onFragmentLoadingAbandoned request: ' + e.request.url + ' has been aborted'); // we only need to handle this if we are not seeking, not switching the tracks and not switching the quality if (!playbackController.isSeeking() &amp;&amp; !scheduleController.getSwitchStrack() &amp;&amp; !qualityChangeInProgress) { logger.info('onFragmentLoadingAbandoned request: ' + e.request.url + ' has to be downloaded again, origin is not seeking process or switch track call'); // in case of an init segment we force the download of an init segment if (e.request &amp;&amp; e.request.isInitializationRequest()) { scheduleController.setInitSegmentRequired(true); } // in case of a media segment we reset the buffering time else { setExplicitBufferingTime(e.request.startTime + (e.request.duration / 2)); } // In case of a seek the schedule controller was stopped and will be started once the buffer has been pruned. scheduleController.startScheduleTimer(0); } } /** * When a fragment has been loaded we need to start the schedule timer again in case of an error. * @param {object} e */ function _onFragmentLoadingCompleted(e) { logger.info('OnFragmentLoadingCompleted for stream id ' + streamInfo.id + ' and media type ' + type + ' - Url:', e.request ? e.request.url : 'undefined', e.request.range ? ', Range:' + e.request.range : ''); if (getIsTextTrack()) { scheduleController.startScheduleTimer(0); } if (e.error &amp;&amp; e.request.serviceLocation) { _handleFragmentLoadingError(e); } } /** * If we encountered an error when loading the fragment we need to handle it according to the segment type * @private */ function _handleFragmentLoadingError(e) { logger.info(`Fragment loading completed with an error`); if (!e || !e.request || !e.request.type) { return; } // In case there are baseUrls that can still be tried a valid request can be generated. If no valid request can be generated we ran out of baseUrls. // Consequently, we need to signal that we dont want to retry in case no valid request could be generated otherwise we keep trying with the same url infinitely. // Init segment could not be loaded. If we have multiple baseUrls we still have a chance to get a valid segment. if (e.request.type === HTTPRequest.INIT_SEGMENT_TYPE) { _onInitFragmentNeeded({ representationId: e.request.representationId, sender: {} }, false) } // Media segment could not be loaded else if (e.request.type === HTTPRequest.MEDIA_SEGMENT_TYPE) { setExplicitBufferingTime(e.request.startTime + (e.request.duration / 2)); _onMediaFragmentNeeded({}, false); } } /** * Callback function triggered by the TextController whenever a track is changed for fragmented text. Will only be triggered if textracks have previously been disabled. * @private */ function _onSetFragmentedTextAfterDisabled() { setExplicitBufferingTime(playbackController.getTime()); getScheduleController().startScheduleTimer(); } /** * Callback function triggered by the TextController whenever a track is changed for non fragmented text * @param {object} e * @private */ function _onSetNonFragmentedText(e) { const currentTrackInfo = e.currentTrackInfo; if (!currentTrackInfo) { return; } const mInfo = mediaInfoArr.find((info) => { return info.index === currentTrackInfo.index &amp;&amp; info.lang === currentTrackInfo.lang; }); if (mInfo) { selectMediaInfo(mInfo) .then(() => { bufferController.setIsBufferingCompleted(false); setExplicitBufferingTime(playbackController.getTime()); scheduleController.setInitSegmentRequired(true); scheduleController.startScheduleTimer(); }); } } function _onQuotaExceeded(e) { // Stop scheduler (will be restarted once buffer is pruned) setExplicitBufferingTime(e.quotaExceededTime); scheduleController.clearScheduleTimer(); } function getRepresentationController() { return representationController; } function getBuffer() { return bufferController ? bufferController.getBuffer() : null; } function getBufferController() { return bufferController; } function getFragmentModel() { return fragmentModel; } function updateStreamInfo(newStreamInfo) { streamInfo = newStreamInfo; if (!isBufferingCompleted()) { return bufferController.updateAppendWindow(); } return Promise.resolve(); } function getStreamInfo() { return streamInfo; } /** * Called once the StreamProcessor is initialized and when the track is switched. We only have one StreamProcessor per media type. So we need to adjust the mediaInfo once we switch/select a track. * @param {object} newMediaInfo */ function selectMediaInfo(newMediaInfo) { if (newMediaInfo !== mediaInfo &amp;&amp; (!newMediaInfo || !mediaInfo || (newMediaInfo.type === mediaInfo.type))) { mediaInfo = newMediaInfo; } const newRealAdaptation = adapter.getRealAdaptation(streamInfo, mediaInfo); const voRepresentations = adapter.getVoRepresentations(mediaInfo); if (representationController) { const realAdaptation = representationController.getData(); const maxQuality = abrController.getMaxAllowedIndexFor(type, streamInfo.id); const minIdx = abrController.getMinAllowedIndexFor(type, streamInfo.id); let quality, averageThroughput; let bitrate = null; if ((realAdaptation === null || (realAdaptation.id !== newRealAdaptation.id)) &amp;&amp; type !== Constants.TEXT) { averageThroughput = abrController.getThroughputHistory().getAverageThroughput(type, isDynamic); bitrate = averageThroughput || abrController.getInitialBitrateFor(type, st