UNPKG

dashjs

Version:

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

1,023 lines (886 loc) 79.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>dash.js Source: streaming/controllers/StreamController.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/controllers/StreamController.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 MetricsConstants from '../constants/MetricsConstants'; import Stream from '../Stream'; import ManifestUpdater from '../ManifestUpdater'; import EventBus from '../../core/EventBus'; import Events from '../../core/events/Events'; import FactoryMaker from '../../core/FactoryMaker'; import { PlayList, PlayListTrace } from '../vo/metrics/PlayList'; import Debug from '../../core/Debug'; import InitCache from '../utils/InitCache'; import URLUtils from '../utils/URLUtils'; import MediaPlayerEvents from '../MediaPlayerEvents'; import TimeSyncController from './TimeSyncController'; import MediaSourceController from './MediaSourceController'; import DashJSError from '../vo/DashJSError'; import Errors from '../../core/errors/Errors'; import EventController from './EventController'; import ConformanceViolationConstants from '../constants/ConformanceViolationConstants'; const PLAYBACK_ENDED_TIMER_INTERVAL = 200; const DVR_WAITING_OFFSET = 2; function StreamController() { const context = this.context; const eventBus = EventBus(context).getInstance(); let instance, logger, capabilities, capabilitiesFilter, manifestUpdater, manifestLoader, manifestModel, adapter, dashMetrics, mediaSourceController, timeSyncController, contentSteeringController, baseURLController, segmentBaseController, uriFragmentModel, abrController, mediaController, eventController, initCache, urlUtils, errHandler, timelineConverter, streams, activeStream, protectionController, textController, protectionData, autoPlay, isStreamSwitchingInProgress, hasMediaError, hasInitialisationError, mediaSource, videoModel, playbackController, serviceDescriptionController, mediaPlayerModel, customParametersModel, isPaused, initialPlayback, initialSteeringRequest, playbackEndedTimerInterval, bufferSinks, preloadingStreams, supportsChangeType, settings, firstLicenseIsFetched, waitForPlaybackStartTimeout, providedStartTime, errorInformation; function setup() { logger = Debug(context).getInstance().getLogger(instance); timeSyncController = TimeSyncController(context).getInstance(); mediaSourceController = MediaSourceController(context).getInstance(); initCache = InitCache(context).getInstance(); urlUtils = URLUtils(context).getInstance(); resetInitialSettings(); } function initialize(autoPl, protData) { _checkConfig(); autoPlay = autoPl; protectionData = protData; timelineConverter.initialize(); manifestUpdater = ManifestUpdater(context).create(); manifestUpdater.setConfig({ manifestModel: manifestModel, adapter: adapter, manifestLoader: manifestLoader, errHandler: errHandler, settings: settings }); manifestUpdater.initialize(); eventController = EventController(context).getInstance(); eventController.setConfig({ manifestUpdater: manifestUpdater, playbackController: playbackController, settings }); eventController.start(); timeSyncController.setConfig({ dashMetrics, baseURLController, errHandler, settings }); timeSyncController.initialize(); if (protectionController) { eventBus.trigger(Events.PROTECTION_CREATED, { controller: protectionController }); protectionController.setMediaElement(videoModel.getElement()); if (protectionData) { protectionController.setProtectionData(protectionData); } } registerEvents(); } function registerEvents() { eventBus.on(MediaPlayerEvents.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated, instance); eventBus.on(MediaPlayerEvents.PLAYBACK_SEEKING, _onPlaybackSeeking, instance); eventBus.on(MediaPlayerEvents.PLAYBACK_ERROR, _onPlaybackError, instance); eventBus.on(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance); eventBus.on(MediaPlayerEvents.PLAYBACK_PAUSED, _onPlaybackPaused, instance); eventBus.on(MediaPlayerEvents.PLAYBACK_ENDED, _onPlaybackEnded, instance); eventBus.on(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance); eventBus.on(MediaPlayerEvents.MANIFEST_VALIDITY_CHANGED, _onManifestValidityChanged, instance); eventBus.on(MediaPlayerEvents.BUFFER_LEVEL_UPDATED, _onBufferLevelUpdated, instance); eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, _onQualityChanged, instance); if (Events.KEY_SESSION_UPDATED) { eventBus.on(Events.KEY_SESSION_UPDATED, _onKeySessionUpdated, instance); } eventBus.on(Events.MANIFEST_UPDATED, _onManifestUpdated, instance); eventBus.on(Events.STREAM_BUFFERING_COMPLETED, _onStreamBufferingCompleted, instance); eventBus.on(Events.TIME_SYNCHRONIZATION_COMPLETED, _onTimeSyncCompleted, instance); eventBus.on(Events.CURRENT_TRACK_CHANGED, _onCurrentTrackChanged, instance); eventBus.on(Events.SETTING_UPDATED_LIVE_DELAY, _onLiveDelaySettingUpdated, instance); eventBus.on(Events.SETTING_UPDATED_LIVE_DELAY_FRAGMENT_COUNT, _onLiveDelaySettingUpdated, instance); } function unRegisterEvents() { eventBus.off(MediaPlayerEvents.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated, instance); eventBus.off(MediaPlayerEvents.PLAYBACK_SEEKING, _onPlaybackSeeking, instance); eventBus.off(MediaPlayerEvents.PLAYBACK_ERROR, _onPlaybackError, instance); eventBus.off(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance); eventBus.off(MediaPlayerEvents.PLAYBACK_PAUSED, _onPlaybackPaused, instance); eventBus.off(MediaPlayerEvents.PLAYBACK_ENDED, _onPlaybackEnded, instance); eventBus.off(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance); eventBus.off(MediaPlayerEvents.MANIFEST_VALIDITY_CHANGED, _onManifestValidityChanged, instance); eventBus.off(MediaPlayerEvents.BUFFER_LEVEL_UPDATED, _onBufferLevelUpdated, instance); eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, _onQualityChanged, instance); if (Events.KEY_SESSION_UPDATED) { eventBus.off(Events.KEY_SESSION_UPDATED, _onKeySessionUpdated, instance); } eventBus.off(Events.MANIFEST_UPDATED, _onManifestUpdated, instance); eventBus.off(Events.STREAM_BUFFERING_COMPLETED, _onStreamBufferingCompleted, instance); eventBus.off(Events.TIME_SYNCHRONIZATION_COMPLETED, _onTimeSyncCompleted, instance); eventBus.off(Events.CURRENT_TRACK_CHANGED, _onCurrentTrackChanged, instance); eventBus.off(Events.SETTING_UPDATED_LIVE_DELAY, _onLiveDelaySettingUpdated, instance); eventBus.off(Events.SETTING_UPDATED_LIVE_DELAY_FRAGMENT_COUNT, _onLiveDelaySettingUpdated, instance); } function _checkConfig() { if (!manifestLoader || !manifestLoader.hasOwnProperty('load') || !timelineConverter || !timelineConverter.hasOwnProperty('initialize') || !timelineConverter.hasOwnProperty('reset') || !timelineConverter.hasOwnProperty('getClientTimeOffset') || !manifestModel || !errHandler || !dashMetrics || !playbackController) { throw new Error(Constants.MISSING_CONFIG_ERROR); } } function _checkInitialize() { if (!manifestUpdater || !manifestUpdater.hasOwnProperty('setManifest')) { throw new Error('initialize function has to be called previously'); } } /** * Start the streaming session by loading the target manifest * @param {string} url * @param {number} startTime */ function load(url, startTime = NaN) { _checkConfig(); providedStartTime = startTime; manifestLoader.load(url); } /** * Start the streaming session by using the provided manifest object * @param {object} manifest * @param {number} startTime */ function loadWithManifest(manifest, startTime = NaN) { _checkInitialize(); providedStartTime = startTime; manifestUpdater.setManifest(manifest); } /** * When the UTC snychronization is completed we can compose the streams * @private */ function _onTimeSyncCompleted( /*e*/) { _composeStreams(); } /** * * @private */ function _onKeySessionUpdated() { firstLicenseIsFetched = true; } /** * Setup the stream objects after the stream start and each MPD reload. This function is called after the UTC sync has been done (TIME_SYNCHRONIZATION_COMPLETED) * @private */ function _composeStreams() { try { const streamsInfo = adapter.getStreamsInfo(); if (!activeStream &amp;&amp; streamsInfo.length === 0) { throw new Error('There are no periods in the MPD'); } if (activeStream &amp;&amp; streamsInfo.length > 0) { dashMetrics.updateManifestUpdateInfo({ currentTime: playbackController.getTime(), buffered: videoModel.getBufferRange(), presentationStartTime: streamsInfo[0].start, clientTimeOffset: timelineConverter.getClientTimeOffset() }); } // Filter streams that are outdated and not included in the MPD anymore if (streams.length > 0) { _filterOutdatedStreams(streamsInfo); } const promises = []; for (let i = 0, ln = streamsInfo.length; i &lt; ln; i++) { const streamInfo = streamsInfo[i]; promises.push(_initializeOrUpdateStream(streamInfo)); dashMetrics.addManifestUpdateStreamInfo(streamInfo); } Promise.all(promises) .then(() => { if (settings.get().streaming.applyContentSteering &amp;&amp; !activeStream &amp;&amp; contentSteeringController.shouldQueryBeforeStart()) { return contentSteeringController.loadSteeringData(); } return Promise.resolve(); }) .then(() => { if (!activeStream) { _initializeForFirstStream(streamsInfo); } eventBus.trigger(Events.STREAMS_COMPOSED); // Additional periods might have been added after an MPD update. Check again if we can start prebuffering. _checkIfPrebufferingCanStart(); }) .catch((e) => { throw e; }) } catch (e) { errHandler.error(new DashJSError(Errors.MANIFEST_ERROR_ID_NOSTREAMS_CODE, e.message + ' nostreamscomposed', manifestModel.getValue())); hasInitialisationError = true; reset(); } } /** * Called for each stream when composition is performed. Either a new instance of Stream is created or the existing one is updated. * @param {object} streamInfo * @private */ function _initializeOrUpdateStream(streamInfo) { let stream = getStreamById(streamInfo.id); // If the Stream object does not exist we probably loaded the manifest the first time or it was // introduced in the updated manifest, so we need to create a new Stream and perform all the initialization operations if (!stream) { stream = Stream(context).create({ manifestModel, mediaPlayerModel, dashMetrics, manifestUpdater, adapter, timelineConverter, capabilities, capabilitiesFilter, errHandler, baseURLController, segmentBaseController, textController, abrController, playbackController, eventController, mediaController, protectionController, videoModel, streamInfo, settings }); streams.push(stream); stream.initialize(); return Promise.resolve(); } else { return stream.updateData(streamInfo); } } /** * Initialize playback for the first period. * @param {array} streamsInfo * @private */ function _initializeForFirstStream(streamsInfo) { // Add the DVR window so we can calculate the right starting point addDVRMetric(); // If the start is in the future we need to wait const dvrRange = dashMetrics.getCurrentDVRInfo().range; if (dvrRange.end &lt; dvrRange.start) { if (waitForPlaybackStartTimeout) { clearTimeout(waitForPlaybackStartTimeout); } const waitingTime = Math.min((((dvrRange.end - dvrRange.start) * -1) + DVR_WAITING_OFFSET) * 1000, 2147483647); logger.debug(`Waiting for ${waitingTime} ms before playback can start`); eventBus.trigger(Events.AST_IN_FUTURE, { delay: waitingTime }); waitForPlaybackStartTimeout = setTimeout(() => { _initializeForFirstStream(streamsInfo); }, waitingTime); return; } // Calculate the producer reference time offsets if given if (settings.get().streaming.applyProducerReferenceTime) { serviceDescriptionController.calculateProducerReferenceTimeOffsets(streamsInfo); } // Apply Service description parameters. const manifestInfo = streamsInfo[0].manifestInfo; if (settings.get().streaming.applyServiceDescription) { serviceDescriptionController.applyServiceDescription(manifestInfo); } // Compute and set the live delay if (adapter.getIsDynamic()) { const fragmentDuration = _getFragmentDurationForLiveDelayCalculation(streamsInfo, manifestInfo); playbackController.computeAndSetLiveDelay(fragmentDuration, manifestInfo); } // Figure out the correct start time and the correct start period const startTime = _getInitialStartTime(); let initialStream = getStreamForTime(startTime); const startStream = initialStream !== null ? initialStream : streams[0]; eventBus.trigger(Events.INITIAL_STREAM_SWITCH, { startTime }); _switchStream(startStream, null, startTime); _startPlaybackEndedTimerInterval(); } /** * Switch from the current stream (period) to the next stream (period). * @param {object} stream * @param {object} previousStream * @param {number} seekTime * @private */ function _switchStream(stream, previousStream, seekTime) { try { if (isStreamSwitchingInProgress || !stream || (previousStream === stream &amp;&amp; stream.getIsActive())) { return; } isStreamSwitchingInProgress = true; eventBus.trigger(Events.PERIOD_SWITCH_STARTED, { fromStreamInfo: previousStream ? previousStream.getStreamInfo() : null, toStreamInfo: stream.getStreamInfo() }); let keepBuffers = false; activeStream = stream; if (previousStream) { keepBuffers = _canSourceBuffersBeReused(stream, previousStream); previousStream.deactivate(keepBuffers); } // Determine seek time when switching to new period // - seek at given seek time // - or seek at period start if upcoming period is not prebuffered seekTime = !isNaN(seekTime) ? seekTime : (!keepBuffers &amp;&amp; previousStream ? stream.getStreamInfo().start : NaN); logger.info(`Switch to stream ${stream.getId()}. Seektime is ${seekTime}, current playback time is ${playbackController.getTime()}. Seamless period switch is set to ${keepBuffers}`); preloadingStreams = preloadingStreams.filter((s) => { return s.getId() !== activeStream.getId(); }); playbackController.initialize(getActiveStreamInfo(), !!previousStream); if (videoModel.getElement()) { _openMediaSource(seekTime, keepBuffers); } } catch (e) { isStreamSwitchingInProgress = false; } } /** * Setup the Media Source. Open MSE and attach event listeners * @param {number} seekTime * @param {boolean} keepBuffers * @private */ function _openMediaSource(seekTime, keepBuffers) { let sourceUrl; function _onMediaSourceOpen() { // Manage situations in which a call to reset happens while MediaSource is being opened if (!mediaSource || mediaSource.readyState !== 'open') return; logger.debug('MediaSource is open!'); window.URL.revokeObjectURL(sourceUrl); mediaSource.removeEventListener('sourceopen', _onMediaSourceOpen); mediaSource.removeEventListener('webkitsourceopen', _onMediaSourceOpen); _setMediaDuration(); const dvrInfo = dashMetrics.getCurrentDVRInfo(); mediaSourceController.setSeekable(dvrInfo.range.start, dvrInfo.range.end); _activateStream(seekTime, keepBuffers); } function _open() { mediaSource.addEventListener('sourceopen', _onMediaSourceOpen, false); mediaSource.addEventListener('webkitsourceopen', _onMediaSourceOpen, false); sourceUrl = mediaSourceController.attachMediaSource(videoModel); logger.debug('MediaSource attached to element. Waiting on open...'); } if (!mediaSource) { mediaSource = mediaSourceController.createMediaSource(); _open(); } else { if (keepBuffers) { _activateStream(seekTime, keepBuffers); } else { mediaSourceController.detachMediaSource(videoModel); _open(); } } } /** * Activates a new stream. * @param {number} seekTime * @param {boolean} keepBuffers */ function _activateStream(seekTime, keepBuffers) { activeStream.activate(mediaSource, keepBuffers ? bufferSinks : undefined, seekTime) .then((sinks) => { // check if change type is supported by the browser if (sinks) { const keys = Object.keys(sinks); if (keys.length > 0 &amp;&amp; sinks[keys[0]].getBuffer().changeType) { supportsChangeType = true; } bufferSinks = sinks; } // Set the initial time for this stream in the StreamProcessor if (!isNaN(seekTime)) { eventBus.trigger(Events.SEEK_TARGET, { time: seekTime }, { streamId: activeStream.getId() }); playbackController.seek(seekTime, false, true); activeStream.startScheduleControllers(); } isStreamSwitchingInProgress = false; eventBus.trigger(Events.PERIOD_SWITCH_COMPLETED, { toStreamInfo: getActiveStreamInfo() }); }); } /** * A playback seeking event was triggered. We need to disable the preloading streams and call the respective seeking handler. * We distinguish between inner period seeks and outer period seeks * @param {object} e * @private */ function _onPlaybackSeeking(e) { const oldTime = playbackController.getTime(); const newTime = e.seekTime; const seekToStream = getStreamForTime(newTime); if (!seekToStream || seekToStream === activeStream) { _cancelPreloading(oldTime, newTime); _handleInnerPeriodSeek(e); } else if (seekToStream &amp;&amp; seekToStream !== activeStream) { _cancelPreloading(oldTime, newTime, seekToStream); _handleOuterPeriodSeek(e, seekToStream); } _createPlaylistMetrics(PlayList.SEEK_START_REASON); } /** * Cancels the preloading of certain streams based on the position we are seeking to. * @param {number} oldTime * @param {number} newTime * @param {boolean} isInnerPeriodSeek * @private */ function _cancelPreloading(oldTime, newTime, seekToStream = null) { // Inner period seek forward if (oldTime &lt;= newTime &amp;&amp; !seekToStream) { _deactivateAllPreloadingStreams(); } // Inner period seek: If we seek backwards we might need to prune the period(s) that are currently being prebuffered. For now deactivate everything else if (oldTime > newTime &amp;&amp; !seekToStream) { _deactivateAllPreloadingStreams(); } // Outer period seek: Deactivate everything for now else { _deactivateAllPreloadingStreams(); } } /** * Deactivates all preloading streams * @private */ function _deactivateAllPreloadingStreams() { if (preloadingStreams &amp;&amp; preloadingStreams.length > 0) { preloadingStreams.forEach((s) => { s.deactivate(true); }); preloadingStreams = []; } } /** * Handle an inner period seek. Prepare all StreamProcessors for the seek. * @param {object} e * @private */ function _handleInnerPeriodSeek(e) { const streamProcessors = activeStream.getProcessors(); streamProcessors.forEach((sp) => { return sp.prepareInnerPeriodPlaybackSeeking(e); }); _flushPlaylistMetrics(PlayListTrace.USER_REQUEST_STOP_REASON); } /** * Handle an outer period seek. Dispatch the corresponding event to be handled in the BufferControllers and the ScheduleControllers * @param {object} e * @param {object} seekToStream * @private */ function _handleOuterPeriodSeek(e, seekToStream) { // Stop segment requests const seekTime = e &amp;&amp; !isNaN(e.seekTime) ? e.seekTime : NaN; const streamProcessors = activeStream.getProcessors(); const promises = streamProcessors.map((sp) => { // Cancel everything in case the active stream is still buffering return sp.prepareOuterPeriodPlaybackSeeking(e); }); Promise.all(promises) .then(() => { _switchStream(seekToStream, activeStream, seekTime); }) .catch((e) => { errHandler.error(e); }); } /** * A track change occured. We deactivate the preloading streams * @param {object} e * @private */ function _onCurrentTrackChanged(e) { // Track was changed in non active stream. No need to do anything, this only happens when a stream starts preloading if (e.newMediaInfo.streamInfo.id !== activeStream.getId()) { return; } // If the track was changed in the active stream we need to stop preloading and remove the already prebuffered stuff. Since we do not support preloading specific handling of specific AdaptationSets yet. _deactivateAllPreloadingStreams(); activeStream.prepareTrackChange(e); } /** * If the source buffer can be reused we can potentially start buffering the next period * @param {object} nextStream * @param {object} previousStream * @return {boolean} * @private */ function _canSourceBuffersBeReused(nextStream, previousStream) { try { // Seamless period switch allowed only if: // - none of the periods uses contentProtection. // - AND changeType method implemented by browser or periods use the same codec. return (settings.get().streaming.buffer.reuseExistingSourceBuffers &amp;&amp; (previousStream.isProtectionCompatible(nextStream) || firstLicenseIsFetched) &amp;&amp; (supportsChangeType || previousStream.isMediaCodecCompatible(nextStream, previousStream))); } catch (e) { return false; } } /** * Initiate the preloading of the next stream * @param {object} nextStream * @param {object} previousStream * @private */ function _onStreamCanLoadNext(nextStream, previousStream = null) { if (mediaSource &amp;&amp; !nextStream.getPreloaded()) { let seamlessPeriodSwitch = _canSourceBuffersBeReused(nextStream, previousStream); if (seamlessPeriodSwitch) { nextStream.startPreloading(mediaSource, bufferSinks) .then(() => { preloadingStreams.push(nextStream); }); } } } /** * Returns the corresponding stream object for a specific presentation time. * @param {number} time * @return {null|object} */ function getStreamForTime(time) { if (isNaN(time)) { return null; } const ln = streams.length; for (let i = 0; i &lt; ln; i++) { const stream = streams[i]; const streamEnd = parseFloat((stream.getStartTime() + stream.getDuration()).toFixed(5)); if (time &lt; streamEnd) { return stream; } } return null; } /** * Add the DVR window to the metric list. We need the DVR window to restrict the seeking and calculate the right start time. */ function addDVRMetric() { try { const isDynamic = adapter.getIsDynamic(); const streamsInfo = adapter.getStreamsInfo(); const manifestInfo = streamsInfo[0].manifestInfo; const time = playbackController.getTime(); const range = timelineConverter.calcTimeShiftBufferWindow(streams, isDynamic); const activeStreamProcessors = getActiveStreamProcessors(); if (typeof range.start === 'undefined' || typeof range.end === 'undefined') { return; } if (!activeStreamProcessors || activeStreamProcessors.length === 0) { dashMetrics.addDVRInfo(Constants.VIDEO, time, manifestInfo, range); } else { activeStreamProcessors.forEach((sp) => { dashMetrics.addDVRInfo(sp.getType(), time, manifestInfo, range); }); } } catch (e) { } } /** * The buffer level for a certain media type has been updated. If this is the initial playback and we want to autoplay the content we check if we can start playback now. * For livestreams we might have a drift of the target live delay compared to the current live delay because reaching the initial buffer level took time. * @param {object} e * @private */ function _onBufferLevelUpdated(e) { // check if this is the initial playback and we reached the buffer target. If autoplay is true we start playback if (initialPlayback &amp;&amp; autoPlay) { const initialBufferLevel = mediaPlayerModel.getInitialBufferLevel(); const excludedStreamProcessors = [Constants.TEXT]; if (isNaN(initialBufferLevel) || initialBufferLevel &lt;= playbackController.getBufferLevel(excludedStreamProcessors) || (adapter.getIsDynamic() &amp;&amp; initialBufferLevel > playbackController.getLiveDelay())) { initialPlayback = false; _createPlaylistMetrics(PlayList.INITIAL_PLAYOUT_START_REASON); playbackController.play(); } } if (e &amp;&amp; e.mediaType) { dashMetrics.addBufferLevel(e.mediaType, new Date(), e.bufferLevel * 1000); } } /** * When the quality is changed in the currently active stream and we do an aggressive replacement we must stop prebuffering. This is similar to a replacing track switch * Otherwise preloading can go on. * @param e * @private */ function _onQualityChanged(e) { if (e.streamInfo.id === activeStream.getId() &amp;&amp; e.reason &amp;&amp; e.reason.forceReplace) { _deactivateAllPreloadingStreams(); } const stream = getStreamById(e.streamInfo.id); stream.prepareQualityChange(e); } /** * A setting related to the live delay was updated. Check if one of the latency values changed. If so, recalculate the live delay. * @private */ function _onLiveDelaySettingUpdated() { if (adapter.getIsDynamic() &amp;&amp; playbackController.getOriginalLiveDelay() !== 0) { const streamsInfo = adapter.getStreamsInfo() if (streamsInfo.length > 0) { const manifestInfo = streamsInfo[0].manifestInfo; const fragmentDuration = _getFragmentDurationForLiveDelayCalculation(streamsInfo, manifestInfo); playbackController.computeAndSetLiveDelay(fragmentDuration, manifestInfo); } } } /** * When the playback time is updated we add the droppedFrames metric to the dash metric object * @private */ function _onPlaybackTimeUpdated(/*e*/) { if (hasVideoTrack()) { const playbackQuality = videoModel.getPlaybackQuality(); if (playbackQuality) { dashMetrics.addDroppedFrames(playbackQuality); } } } /** * Once playback starts add playlist metrics depending on whether this was the first playback or playback resumed after pause * @private */ function _onPlaybackStarted( /*e*/) { logger.debug('[onPlaybackStarted]'); if (!initialPlayback &amp;&amp; isPaused) { _createPlaylistMetrics(PlayList.RESUME_FROM_PAUSE_START_REASON); } if (initialPlayback) { initialPlayback = false; } if (initialSteeringRequest) { initialSteeringRequest = false; // If this is the initial playback attempt and we have not yet triggered content steering now is the time if (settings.get().streaming.applyContentSteering &amp;&amp; !contentSteeringController.shouldQueryBeforeStart()) { contentSteeringController.loadSteeringData(); } } isPaused = false; } /** * Once playback is paused flush metrics * @param {object} e * @private */ function _onPlaybackPaused(e) { logger.debug('[onPlaybackPaused]'); if (!e.ended) { isPaused = true; _flushPlaylistMetrics(PlayListTrace.USER_REQUEST_STOP_REASON); } } /** * Callback once a stream/period is completely buffered. We can either signal the end of the stream or start prebuffering the next period. * @param {object} e * @private */ function _onStreamBufferingCompleted(e) { logger.debug(`Stream with id ${e.streamInfo.id} finished buffering`); const isLast = e.streamInfo.isLast; if (mediaSource &amp;&amp; isLast) { logger.info('[onStreamBufferingCompleted] calls signalEndOfStream of mediaSourceController.'); mediaSourceController.signalEndOfStream(mediaSource); } else { _checkIfPrebufferingCanStart(); } } /** * Check if we can start prebuffering the next period. * @private */ function _checkIfPrebufferingCanStart() { // In multiperiod situations, we can start buffering the next stream if (!activeStream || !activeStream.getHasFinishedBuffering()) { return; } const upcomingStreams = _getNextStreams(activeStream); let i = 0; while (i &lt; upcomingStreams.length) { const stream = upcomingStreams[i]; const previousStream = i === 0 ? activeStream : upcomingStreams[i - 1]; // If the preloading for the current stream is not scheduled, but its predecessor has finished buffering we can start prebuffering this stream if (!stream.getPreloaded() &amp;&amp; previousStream.getHasFinishedBuffering()) { if (mediaSource) { _onStreamCanLoadNext(stream, previousStream); } } i += 1; } } /** * In some cases we need to fire the playback ended event manually * @private */ function _startPlaybackEndedTimerInterval() { if (!playbackEndedTimerInterval) { playbackEndedTimerInterval = setInterval(function () { if (!isStreamSwitchingInProgress &amp;&amp; playbackController.getTimeToStreamEnd() &lt;= 0 &amp;&amp; !playbackController.isSeeking()) { eventBus.trigger(Events.PLAYBACK_ENDED, { 'isLast': getActiveStreamInfo().isLast }); } }, PLAYBACK_ENDED_TIMER_INTERVAL); } } /** * Stop the check if the playback has ended * @private */ function _stopPlaybackEndedTimerInterval() { if (playbackEndedTimerInterval) { clearInterval(playbackEndedTimerInterval); playbackEndedTimerInterval = null; } } /** * Returns a playhead time, in seconds, converted to be relative * to the start of an identified stream/period or null if no such stream * @param {number} time * @param {string} id * @returns {number|null} */ function getTimeRelativeToStreamId(time, id) { let stream = null; let baseStart = 0; let streamStart = 0; let streamDur = null; for (let i = 0; i &lt; streams.length; i++) { stream = streams[i]; streamStart = stream.getStartTime(); streamDur = stream.getDuration(); // use start time, if not undefined or NaN or similar if (Number.isFinite(streamStart)) { baseStart = streamStart; } if (stream.getId() === id) { return time - baseStart; } else { // use duration if not undefined or NaN or similar if (Number.isFinite(streamDur)) { baseStart += streamDur; } } } return null; } /** * Returns the streamProcessors of the active stream. * @return {array} */ function getActiveStreamProcessors() { return activeStream ? activeStream.getProcessors() : []; } /** * Once playback has ended we switch to the next stream * @param {object} e */ function _onPlaybackEnded(e) { if (activeStream &amp;&amp; !activeStream.getIsEndedEventSignaled()) { activeStream.setIsEndedEventSignaled(true); const nextStream = _getNextStream(); if (nextStream) { logger.debug(`StreamController onEnded, found next stream with id ${nextStream.getStreamInfo().id}. Switching from ${activeStream.getStreamInfo().id} to ${nextStream.getStreamInfo().id}`); _switchStream(nextStream, activeStream, NaN); } else { logger.debug('StreamController no next stream found'); activeStream.setIsEndedEventSignaled(false); } _flushPlaylistMetrics(nextStream ? PlayListTrace.END_OF_PERIOD_STOP_