@l5i/dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
718 lines (620 loc) • 29.8 kB
JavaScript
/**
* 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 {PlayListTrace} from '../vo/metrics/PlayList';
import AbrController from './AbrController';
import BufferController from './BufferController';
import BufferLevelRule from '../rules/scheduling/BufferLevelRule';
import NextFragmentRequestRule from '../rules/scheduling/NextFragmentRequestRule';
import FragmentModel from '../models/FragmentModel';
import EventBus from '../../core/EventBus';
import Events from '../../core/events/Events';
import FactoryMaker from '../../core/FactoryMaker';
import Debug from '../../core/Debug';
import MediaController from './MediaController';
import {replaceTokenForTemplate} from '../../dash/utils/SegmentsUtils';
function ScheduleController(config) {
config = config || {};
const context = this.context;
const eventBus = EventBus(context).getInstance();
const metricsModel = config.metricsModel;
const adapter = config.adapter;
const dashMetrics = config.dashMetrics;
const dashManifestModel = config.dashManifestModel;
const timelineConverter = config.timelineConverter;
const mediaPlayerModel = config.mediaPlayerModel;
const abrController = config.abrController;
const playbackController = config.playbackController;
const streamController = config.streamController;
const textController = config.textController;
const type = config.type;
const streamProcessor = config.streamProcessor;
const mediaController = config.mediaController;
let instance,
logger,
fragmentModel,
currentRepresentationInfo,
initialRequest,
isStopped,
playListMetrics,
playListTraceMetrics,
playListTraceMetricsClosed,
isFragmentProcessingInProgress,
timeToLoadDelay,
scheduleTimeout,
seekTarget,
bufferLevelRule,
nextFragmentRequestRule,
lastFragmentRequest,
topQualityIndex,
lastInitQuality,
replaceRequestArray,
switchTrack,
bufferResetInProgress,
mediaRequest;
function setup() {
logger = Debug(context).getInstance().getLogger(instance);
resetInitialSettings();
}
function initialize() {
fragmentModel = streamProcessor.getFragmentModel();
bufferLevelRule = BufferLevelRule(context).create({
abrController: abrController,
dashMetrics: dashMetrics,
metricsModel: metricsModel,
mediaPlayerModel: mediaPlayerModel,
textController: textController
});
nextFragmentRequestRule = NextFragmentRequestRule(context).create({
adapter: adapter,
textController: textController
});
if (dashManifestModel.getIsTextTrack(config.mimeType)) {
eventBus.on(Events.TIMED_TEXT_REQUESTED, onTimedTextRequested, this);
}
//eventBus.on(Events.LIVE_EDGE_SEARCH_COMPLETED, onLiveEdgeSearchCompleted, this);
eventBus.on(Events.QUALITY_CHANGE_REQUESTED, onQualityChanged, this);
eventBus.on(Events.DATA_UPDATE_STARTED, onDataUpdateStarted, this);
eventBus.on(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, this);
eventBus.on(Events.FRAGMENT_LOADING_COMPLETED, onFragmentLoadingCompleted, this);
eventBus.on(Events.STREAM_COMPLETED, onStreamCompleted, this);
eventBus.on(Events.STREAM_INITIALIZED, onStreamInitialized, this);
eventBus.on(Events.BUFFER_LEVEL_STATE_CHANGED, onBufferLevelStateChanged, this);
eventBus.on(Events.BUFFER_CLEARED, onBufferCleared, this);
eventBus.on(Events.BYTES_APPENDED_END_FRAGMENT, onBytesAppended, this);
eventBus.on(Events.INIT_REQUESTED, onInitRequested, this);
eventBus.on(Events.QUOTA_EXCEEDED, onQuotaExceeded, this);
eventBus.on(Events.PLAYBACK_SEEKING, onPlaybackSeeking, this);
eventBus.on(Events.PLAYBACK_STARTED, onPlaybackStarted, this);
eventBus.on(Events.PLAYBACK_RATE_CHANGED, onPlaybackRateChanged, this);
eventBus.on(Events.PLAYBACK_TIME_UPDATED, onPlaybackTimeUpdated, this);
eventBus.on(Events.URL_RESOLUTION_FAILED, onURLResolutionFailed, this);
eventBus.on(Events.FRAGMENT_LOADING_ABANDONED, onFragmentLoadingAbandoned, this);
}
function isStarted() {
return (isStopped === false);
}
function start() {
if (!currentRepresentationInfo || streamProcessor.isBufferingCompleted()) {
logger.warn('Start denied to Schedule Controller');
return;
}
logger.debug('Schedule Controller starts');
addPlaylistTraceMetrics();
isStopped = false;
if (initialRequest) {
initialRequest = false;
}
startScheduleTimer(0);
}
function stop() {
if (isStopped) {
return;
}
logger.debug('Schedule Controller stops');
isStopped = true;
clearTimeout(scheduleTimeout);
}
function hasTopQualityChanged(type, id) {
topQualityIndex[id] = topQualityIndex[id] || {};
const newTopQualityIndex = abrController.getTopQualityIndexFor(type, id);
if (topQualityIndex[id][type] != newTopQualityIndex) {
logger.info('Top quality ' + type + ' index has changed from ' + topQualityIndex[id][type] + ' to ' + newTopQualityIndex);
topQualityIndex[id][type] = newTopQualityIndex;
return true;
}
return false;
}
function schedule() {
const bufferController = streamProcessor.getBufferController();
if (isStopped || isFragmentProcessingInProgress || !bufferController ||
(playbackController.isPaused() && !mediaPlayerModel.getScheduleWhilePaused()) ||
((type === Constants.FRAGMENTED_TEXT || type === Constants.TEXT) && !textController.isTextEnabled())) {
logger.debug('Schedule stop!');
return;
}
if (bufferController.getIsBufferingCompleted()) {
logger.debug('Schedule stop because buffering is completed!');
return;
}
validateExecutedFragmentRequest();
const isReplacement = replaceRequestArray.length > 0;
const streamInfo = streamProcessor.getStreamInfo();
if (bufferResetInProgress || isNaN(lastInitQuality) || switchTrack || isReplacement ||
hasTopQualityChanged(currentRepresentationInfo.mediaInfo.type, streamInfo.id) ||
bufferLevelRule.execute(streamProcessor, streamController.isTrackTypePresent(Constants.VIDEO))) {
const getNextFragment = function () {
const fragmentController = streamProcessor.getFragmentController();
if (currentRepresentationInfo.quality !== lastInitQuality) {
logger.debug('Quality has changed, get init request for representationid = ' + currentRepresentationInfo.id);
lastInitQuality = currentRepresentationInfo.quality;
streamProcessor.switchInitData(currentRepresentationInfo.id);
} else if (switchTrack) {
logger.debug('Switch track has been asked, get init request for ' + type + ' with representationid = ' + currentRepresentationInfo.id);
bufferResetInProgress = mediaController.getSwitchMode(type) === MediaController.TRACK_SWITCH_MODE_ALWAYS_REPLACE ? true : false;
streamProcessor.switchInitData(currentRepresentationInfo.id, bufferResetInProgress);
lastInitQuality = currentRepresentationInfo.quality;
switchTrack = false;
} else {
const replacement = replaceRequestArray.shift();
if (fragmentController.isInitializationRequest(replacement)) {
// To be sure the specific init segment had not already been loaded.
streamProcessor.switchInitData(replacement.representationId);
} else {
let request;
// Don't schedule next fragments while pruning to avoid buffer inconsistencies
if (!streamProcessor.getBufferController().getIsPruningInProgress()) {
request = nextFragmentRequestRule.execute(streamProcessor, replacement);
if (!request && streamInfo.manifestInfo && streamInfo.manifestInfo.isDynamic) {
logger.info('Playing at the bleeding live edge and frag is not available yet');
}
}
if (request) {
logger.debug('getNextFragment - request is ' + request.url);
fragmentModel.executeRequest(request);
} else { // Use case - Playing at the bleeding live edge and frag is not available yet. Cycle back around.
setFragmentProcessState(false);
startScheduleTimer(mediaPlayerModel.getLowLatencyEnabled() ? 100 : 500);
}
}
}
};
setFragmentProcessState(true);
if (!isReplacement && !switchTrack) {
abrController.checkPlaybackQuality(type);
}
getNextFragment();
} else {
startScheduleTimer(500);
}
}
function validateExecutedFragmentRequest() {
// Validate that the fragment request executed and appended into the source buffer is as
// good of quality as the current quality and is the correct media track.
const safeBufferLevel = currentRepresentationInfo.fragmentDuration * 1.5;
const request = fragmentModel.getRequests({
state: FragmentModel.FRAGMENT_MODEL_EXECUTED,
time: playbackController.getTime() + safeBufferLevel,
threshold: 0
})[0];
if (request && replaceRequestArray.indexOf(request) === -1 && !dashManifestModel.getIsTextTrack(type)) {
const fastSwitchModeEnabled = mediaPlayerModel.getFastSwitchEnabled();
const bufferLevel = streamProcessor.getBufferLevel();
const abandonmentState = abrController.getAbandonmentStateFor(type);
// Only replace on track switch when NEVER_REPLACE
const trackChanged = !mediaController.isCurrentTrack(request.mediaInfo) && mediaController.getSwitchMode(request.mediaInfo.type) === MediaController.TRACK_SWITCH_MODE_NEVER_REPLACE;
const qualityChanged = request.quality < currentRepresentationInfo.quality;
if (fastSwitchModeEnabled && (trackChanged || qualityChanged) && bufferLevel >= safeBufferLevel && abandonmentState !== AbrController.ABANDON_LOAD) {
replaceRequest(request);
logger.debug('Reloading outdated fragment at index: ', request.index);
} else if (request.quality > currentRepresentationInfo.quality) {
// The buffer has better quality it in then what we would request so set append point to end of buffer!!
setSeekTarget(playbackController.getTime() + streamProcessor.getBufferLevel());
}
}
}
function startScheduleTimer(value) {
clearTimeout(scheduleTimeout);
scheduleTimeout = setTimeout(schedule, value);
}
function onInitRequested(e) {
if (!e.sender || e.sender.getStreamProcessor() !== streamProcessor) {
return;
}
getInitRequest(currentRepresentationInfo.quality);
}
function setFragmentProcessState (state) {
if (isFragmentProcessingInProgress !== state ) {
isFragmentProcessingInProgress = state;
} else {
logger.debug('isFragmentProcessingInProgress is already equal to', state);
}
}
function getInitRequest(quality) {
const request = adapter.getInitRequest(streamProcessor, quality);
if (request) {
setFragmentProcessState(true);
request.url = replaceTokenForTemplate(request.url, 'Bandwidth', currentRepresentationInfo ? currentRepresentationInfo.bandwidth : null);
fragmentModel.executeRequest(request);
}
}
function switchTrackAsked() {
switchTrack = true;
}
function replaceRequest(request) {
replaceRequestArray.push(request);
}
function onQualityChanged(e) {
if (type !== e.mediaType || streamProcessor.getStreamInfo().id !== e.streamInfo.id) {
return;
}
currentRepresentationInfo = streamProcessor.getRepresentationInfo(e.newQuality);
if (currentRepresentationInfo === null || currentRepresentationInfo === undefined) {
throw new Error('Unexpected error! - currentRepresentationInfo is null or undefined');
}
clearPlayListTraceMetrics(new Date(), PlayListTrace.REPRESENTATION_SWITCH_STOP_REASON);
addPlaylistTraceMetrics();
}
function completeQualityChange(trigger) {
if (playbackController && fragmentModel) {
const item = fragmentModel.getRequests({
state: FragmentModel.FRAGMENT_MODEL_EXECUTED,
time: playbackController.getTime(),
threshold: 0
})[0];
if (item && playbackController.getTime() >= item.startTime) {
if ((!lastFragmentRequest.mediaInfo || (item.mediaInfo.type === lastFragmentRequest.mediaInfo.type && item.mediaInfo.id !== lastFragmentRequest.mediaInfo.id)) && trigger) {
eventBus.trigger(Events.TRACK_CHANGE_RENDERED, {
mediaType: type,
oldMediaInfo: lastFragmentRequest.mediaInfo,
newMediaInfo: item.mediaInfo
});
}
if ((item.quality !== lastFragmentRequest.quality || item.adaptationIndex !== lastFragmentRequest.adaptationIndex) && trigger) {
eventBus.trigger(Events.QUALITY_CHANGE_RENDERED, {
mediaType: type,
oldQuality: lastFragmentRequest.quality,
newQuality: item.quality
});
}
lastFragmentRequest = {
mediaInfo: item.mediaInfo,
quality: item.quality,
adaptationIndex: item.adaptationIndex
};
}
}
}
function onDataUpdateCompleted(e) {
if (e.error || e.sender.getStreamProcessor() !== streamProcessor) {
return;
}
currentRepresentationInfo = adapter.convertDataToRepresentationInfo(e.currentRepresentation);
}
function onStreamInitialized(e) {
if (e.error || streamProcessor.getStreamInfo().id !== e.streamInfo.id) {
return;
}
currentRepresentationInfo = streamProcessor.getRepresentationInfo();
if (initialRequest) {
if (playbackController.getIsDynamic()) {
timelineConverter.setTimeSyncCompleted(true);
setLiveEdgeSeekTarget();
} else {
seekTarget = playbackController.getStreamStartTime(false);
streamProcessor.getBufferController().setSeekStartTime(seekTarget);
}
}
if (isStopped) {
start();
}
}
function setLiveEdgeSeekTarget() {
const liveEdgeFinder = streamProcessor.getLiveEdgeFinder();
if (liveEdgeFinder) {
const liveEdge = liveEdgeFinder.getLiveEdge();
const dvrWindowSize = currentRepresentationInfo.mediaInfo.streamInfo.manifestInfo.DVRWindowSize / 2;
const startTime = liveEdge - playbackController.computeLiveDelay(currentRepresentationInfo.fragmentDuration, dvrWindowSize);
const request = adapter.getFragmentRequest(streamProcessor, currentRepresentationInfo, startTime, {
ignoreIsFinished: true
});
if (request) {
// When low latency mode is selected but browser doesn't support fetch
// start at the beginning of the segment to avoid consuming the whole buffer
if (mediaPlayerModel.getLowLatencyEnabled()) {
const liveStartTime = request.duration < mediaPlayerModel.getLiveDelay() ? request.startTime : request.startTime + request.duration - mediaPlayerModel.getLiveDelay();
playbackController.setLiveStartTime(liveStartTime);
} else {
playbackController.setLiveStartTime(request.startTime);
}
} else {
logger.debug('setLiveEdgeSeekTarget : getFragmentRequest returned undefined request object');
}
seekTarget = playbackController.getStreamStartTime(false, liveEdge);
streamProcessor.getBufferController().setSeekStartTime(seekTarget);
//special use case for multi period stream. If the startTime is out of the current period, send a seek command.
//in onPlaybackSeeking callback (StreamController), the detection of switch stream is done.
if (seekTarget > (currentRepresentationInfo.mediaInfo.streamInfo.start + currentRepresentationInfo.mediaInfo.streamInfo.duration)) {
playbackController.seek(seekTarget);
}
const manifestUpdateInfo = dashMetrics.getCurrentManifestUpdate(metricsModel.getMetricsFor(Constants.STREAM));
metricsModel.updateManifestUpdateInfo(manifestUpdateInfo, {
currentTime: seekTarget,
presentationStartTime: liveEdge,
latency: liveEdge - seekTarget,
clientTimeOffset: timelineConverter.getClientTimeOffset()
});
}
}
function onStreamCompleted(e) {
if (e.fragmentModel !== fragmentModel) {
return;
}
stop();
setFragmentProcessState(false);
logger.info('Stream is complete');
}
function onFragmentLoadingCompleted(e) {
if (e.sender !== fragmentModel) {
return;
}
logger.info('OnFragmentLoadingCompleted - Url:', e.request ? e.request.url : 'undefined',
', Range:', e.request.range ? e.request.range : 'undefined');
if (dashManifestModel.getIsTextTrack(type)) {
setFragmentProcessState(false);
}
if (e.error && e.request.serviceLocation && !isStopped) {
replaceRequest(e.request);
setFragmentProcessState(false);
startScheduleTimer(0);
}
if (bufferResetInProgress) {
mediaRequest = e.request;
}
}
function onPlaybackTimeUpdated() {
completeQualityChange(true);
}
function onBytesAppended(e) {
if (e.sender.getStreamProcessor() !== streamProcessor) {
return;
}
if (bufferResetInProgress && !isNaN(e.startTime)) {
bufferResetInProgress = false;
fragmentModel.addExecutedRequest(mediaRequest);
}
setFragmentProcessState(false);
startScheduleTimer(0);
}
function onFragmentLoadingAbandoned(e) {
if (e.streamProcessor !== streamProcessor) {
return;
}
logger.info('onFragmentLoadingAbandoned for ' + type + ', request: ' + e.request.url + ' has been aborted');
if (!playbackController.isSeeking() && !switchTrack) {
logger.info('onFragmentLoadingAbandoned for ' + type + ', request: ' + e.request.url + ' has to be downloaded again, origin is not seeking process or switch track call');
replaceRequest(e.request);
}
setFragmentProcessState(false);
startScheduleTimer(0);
}
function onDataUpdateStarted(e) {
if (e.sender.getStreamProcessor() !== streamProcessor) {
return;
}
stop();
}
function onBufferCleared(e) {
if (e.sender.getStreamProcessor() !== streamProcessor) {
return;
}
if (e.unintended) {
// There was an unintended buffer remove, probably creating a gap in the buffer, remove every saved request
streamProcessor.getFragmentModel().removeExecutedRequestsAfterTime(e.from,
streamProcessor.getStreamInfo().duration);
} else {
streamProcessor.getFragmentModel().syncExecutedRequestsWithBufferedRange(
streamProcessor.getBufferController().getBuffer().getAllBufferRanges(),
streamProcessor.getStreamInfo().duration);
}
if (e.hasEnoughSpaceToAppend && isStopped) {
start();
}
}
function onBufferLevelStateChanged(e) {
if ((e.sender.getStreamProcessor() === streamProcessor) && e.state === BufferController.BUFFER_EMPTY && !playbackController.isSeeking()) {
logger.info('Buffer is empty! Stalling!');
clearPlayListTraceMetrics(new Date(), PlayListTrace.REBUFFERING_REASON);
}
}
function onQuotaExceeded(e) {
if (e.sender.getStreamProcessor() !== streamProcessor) {
return;
}
stop();
setFragmentProcessState(false);
}
function onURLResolutionFailed() {
fragmentModel.abortRequests();
stop();
}
function onTimedTextRequested(e) {
if (e.sender.getStreamProcessor() !== streamProcessor) {
return;
}
//if subtitles are disabled, do not download subtitles file.
if (textController.isTextEnabled()) {
getInitRequest(e.index);
}
}
function onPlaybackStarted() {
if (isStopped || !mediaPlayerModel.getScheduleWhilePaused()) {
start();
}
}
function onPlaybackSeeking(e) {
seekTarget = e.seekTime;
setTimeToLoadDelay(0);
if (isStopped) {
start();
}
const manifestUpdateInfo = dashMetrics.getCurrentManifestUpdate(metricsModel.getMetricsFor(Constants.STREAM));
const latency = currentRepresentationInfo.DVRWindow && playbackController ? currentRepresentationInfo.DVRWindow.end - playbackController.getTime() : NaN;
metricsModel.updateManifestUpdateInfo(manifestUpdateInfo, {
latency: latency
});
//if, during the seek command, the scheduleController is waiting : stop waiting, request chunk as soon as possible
if (!isFragmentProcessingInProgress) {
startScheduleTimer(0);
} else {
logger.debug('onPlaybackSeeking for ' + type + ', call fragmentModel.abortRequests in order to seek quicker');
fragmentModel.abortRequests();
}
}
function onPlaybackRateChanged(e) {
if (playListTraceMetrics) {
playListTraceMetrics.playbackspeed = e.playbackRate.toString();
}
}
function getSeekTarget() {
return seekTarget;
}
function setSeekTarget(value) {
seekTarget = value;
}
function setTimeToLoadDelay(value) {
timeToLoadDelay = value;
}
function getTimeToLoadDelay() {
return timeToLoadDelay;
}
function getBufferTarget() {
return bufferLevelRule.getBufferTarget(streamProcessor, streamController.isTrackTypePresent(Constants.VIDEO));
}
function getType() {
return type;
}
function setPlayList(playList) {
playListMetrics = playList;
}
function finalisePlayList(time, reason) {
clearPlayListTraceMetrics(time, reason);
playListMetrics = null;
}
function clearPlayListTraceMetrics(endTime, stopreason) {
if (playListMetrics && playListTraceMetricsClosed === false) {
const startTime = playListTraceMetrics.start;
const duration = endTime.getTime() - startTime.getTime();
playListTraceMetrics.duration = duration;
playListTraceMetrics.stopreason = stopreason;
playListMetrics.trace.push(playListTraceMetrics);
playListTraceMetricsClosed = true;
}
}
function addPlaylistTraceMetrics() {
if (playListMetrics && playListTraceMetricsClosed === true && currentRepresentationInfo) {
playListTraceMetricsClosed = false;
playListTraceMetrics = new PlayListTrace();
playListTraceMetrics.representationid = currentRepresentationInfo.id;
playListTraceMetrics.start = new Date();
playListTraceMetrics.mstart = playbackController.getTime() * 1000;
playListTraceMetrics.playbackspeed = playbackController.getPlaybackRate().toString();
}
}
function resetInitialSettings() {
isFragmentProcessingInProgress = false;
timeToLoadDelay = 0;
seekTarget = NaN;
playListMetrics = null;
playListTraceMetrics = null;
playListTraceMetricsClosed = true;
initialRequest = true;
lastInitQuality = NaN;
lastFragmentRequest = {
mediaInfo: undefined,
quality: NaN,
adaptationIndex: NaN
};
topQualityIndex = {};
replaceRequestArray = [];
isStopped = true;
switchTrack = false;
bufferResetInProgress = false;
mediaRequest = null;
}
function reset() {
//eventBus.off(Events.LIVE_EDGE_SEARCH_COMPLETED, onLiveEdgeSearchCompleted, this);
eventBus.off(Events.DATA_UPDATE_STARTED, onDataUpdateStarted, this);
eventBus.off(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, this);
eventBus.off(Events.BUFFER_LEVEL_STATE_CHANGED, onBufferLevelStateChanged, this);
eventBus.off(Events.QUALITY_CHANGE_REQUESTED, onQualityChanged, this);
eventBus.off(Events.FRAGMENT_LOADING_COMPLETED, onFragmentLoadingCompleted, this);
eventBus.off(Events.STREAM_COMPLETED, onStreamCompleted, this);
eventBus.off(Events.STREAM_INITIALIZED, onStreamInitialized, this);
eventBus.off(Events.QUOTA_EXCEEDED, onQuotaExceeded, this);
eventBus.off(Events.BYTES_APPENDED_END_FRAGMENT, onBytesAppended, this);
eventBus.off(Events.BUFFER_CLEARED, onBufferCleared, this);
eventBus.off(Events.INIT_REQUESTED, onInitRequested, this);
eventBus.off(Events.PLAYBACK_RATE_CHANGED, onPlaybackRateChanged, this);
eventBus.off(Events.PLAYBACK_SEEKING, onPlaybackSeeking, this);
eventBus.off(Events.PLAYBACK_STARTED, onPlaybackStarted, this);
eventBus.off(Events.PLAYBACK_TIME_UPDATED, onPlaybackTimeUpdated, this);
eventBus.off(Events.URL_RESOLUTION_FAILED, onURLResolutionFailed, this);
eventBus.off(Events.FRAGMENT_LOADING_ABANDONED, onFragmentLoadingAbandoned, this);
if (dashManifestModel.getIsTextTrack(type)) {
eventBus.off(Events.TIMED_TEXT_REQUESTED, onTimedTextRequested, this);
}
stop();
completeQualityChange(false);
resetInitialSettings();
}
instance = {
initialize: initialize,
getType: getType,
getSeekTarget: getSeekTarget,
setSeekTarget: setSeekTarget,
setTimeToLoadDelay: setTimeToLoadDelay,
getTimeToLoadDelay: getTimeToLoadDelay,
replaceRequest: replaceRequest,
switchTrackAsked: switchTrackAsked,
isStarted: isStarted,
start: start,
stop: stop,
reset: reset,
setPlayList: setPlayList,
getBufferTarget: getBufferTarget,
finalisePlayList: finalisePlayList
};
setup();
return instance;
}
ScheduleController.__dashjs_factory_name = 'ScheduleController';
export default FactoryMaker.getClassFactory(ScheduleController);