UNPKG

dashjs

Version:

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

1,198 lines 51.2 kB
<!DOCTYPE html>

<html lang="en">
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width">
	<title>dash.js Source: streaming/controllers/AbrController.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/AbrController.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 ABRRulesCollection from '../rules/abr/ABRRulesCollection';
import Constants from '../constants/Constants';
import MetricsConstants from '../constants/MetricsConstants';
import BitrateInfo from '../vo/BitrateInfo';
import FragmentModel from '../models/FragmentModel';
import EventBus from '../../core/EventBus';
import Events from '../../core/events/Events';
import FactoryMaker from '../../core/FactoryMaker';
import RulesContext from '../rules/RulesContext';
import SwitchRequest from '../rules/SwitchRequest';
import SwitchRequestHistory from '../rules/SwitchRequestHistory';
import DroppedFramesHistory from '../rules/DroppedFramesHistory';
import ThroughputHistory from '../rules/ThroughputHistory';
import Debug from '../../core/Debug';
import {HTTPRequest} from '../vo/metrics/HTTPRequest';
import {checkInteger} from '../utils/SupervisorTools';
import MediaPlayerEvents from '../MediaPlayerEvents';

const DEFAULT_VIDEO_BITRATE = 1000;
const DEFAULT_AUDIO_BITRATE = 100;
const QUALITY_DEFAULT = 0;

function AbrController() {

    const context = this.context;
    const debug = Debug(context).getInstance();
    const eventBus = EventBus(context).getInstance();

    let instance,
        logger,
        abrRulesCollection,
        streamController,
        topQualities,
        qualityDict,
        streamProcessorDict,
        abandonmentStateDict,
        abandonmentTimeout,
        windowResizeEventCalled,
        elementWidth,
        elementHeight,
        adapter,
        videoModel,
        mediaPlayerModel,
        customParametersModel,
        domStorage,
        playbackIndex,
        switchHistoryDict,
        droppedFramesHistory,
        throughputHistory,
        isUsingBufferOccupancyAbrDict,
        isUsingL2AAbrDict,
        isUsingLoLPAbrDict,
        dashMetrics,
        settings;

    function setup() {
        logger = debug.getLogger(instance);
        resetInitialSettings();
    }

    /**
     * Initialize everything that is not Stream specific. We only have one instance of the ABR Controller for all periods.
     */
    function initialize() {
        droppedFramesHistory = DroppedFramesHistory(context).create();
        throughputHistory = ThroughputHistory(context).create({
            settings
        });

        abrRulesCollection = ABRRulesCollection(context).create({
            dashMetrics,
            customParametersModel,
            mediaPlayerModel,
            settings
        });

        abrRulesCollection.initialize();

        eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_RENDERED, _onQualityChangeRendered, instance);
        eventBus.on(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance);
        eventBus.on(Events.LOADING_PROGRESS, _onFragmentLoadProgress, instance);
    }

    /**
     * Whenever a StreamProcessor is created it is added to the list of streamProcessorDict
     * In addition, the corresponding objects for this object and its stream id are created
     * @param {object} type
     * @param {object} streamProcessor
     */
    function registerStreamType(type, streamProcessor) {
        const streamId = streamProcessor.getStreamInfo().id;

        if (!streamProcessorDict[streamId]) {
            streamProcessorDict[streamId] = {};
        }

        if (!switchHistoryDict[streamId]) {
            switchHistoryDict[streamId] = {};
        }

        if (!abandonmentStateDict[streamId]) {
            abandonmentStateDict[streamId] = {};
        }

        switchHistoryDict[streamId][type] = SwitchRequestHistory(context).create();
        streamProcessorDict[streamId][type] = streamProcessor;

        abandonmentStateDict[streamId][type] = {};
        abandonmentStateDict[streamId][type].state = MetricsConstants.ALLOW_LOAD;

        _initializeAbrStrategy(type);

        if (type === Constants.VIDEO) {
            setElementSize();
        }
    }

    function _initializeAbrStrategy(type) {
        const strategy = settings.get().streaming.abr.ABRStrategy;

        if (strategy === Constants.ABR_STRATEGY_L2A) {
            isUsingBufferOccupancyAbrDict[type] = false;
            isUsingLoLPAbrDict[type] = false;
            isUsingL2AAbrDict[type] = true;
        } else if (strategy === Constants.ABR_STRATEGY_LoLP) {
            isUsingBufferOccupancyAbrDict[type] = false;
            isUsingLoLPAbrDict[type] = true;
            isUsingL2AAbrDict[type] = false;
        } else if (strategy === Constants.ABR_STRATEGY_BOLA) {
            isUsingBufferOccupancyAbrDict[type] = true;
            isUsingLoLPAbrDict[type] = false;
            isUsingL2AAbrDict[type] = false;
        } else if (strategy === Constants.ABR_STRATEGY_THROUGHPUT) {
            isUsingBufferOccupancyAbrDict[type] = false;
            isUsingLoLPAbrDict[type] = false;
            isUsingL2AAbrDict[type] = false;
        } else if (strategy === Constants.ABR_STRATEGY_DYNAMIC) {
            isUsingBufferOccupancyAbrDict[type] = isUsingBufferOccupancyAbrDict &amp;&amp; isUsingBufferOccupancyAbrDict[type] ? isUsingBufferOccupancyAbrDict[type] : false;
            isUsingLoLPAbrDict[type] = false;
            isUsingL2AAbrDict[type] = false;
        }
    }

    function unRegisterStreamType(streamId, type) {
        try {
            if (streamProcessorDict[streamId] &amp;&amp; streamProcessorDict[streamId][type]) {
                delete streamProcessorDict[streamId][type];
            }

            if (switchHistoryDict[streamId] &amp;&amp; switchHistoryDict[streamId][type]) {
                delete switchHistoryDict[streamId][type];
            }

            if (abandonmentStateDict[streamId] &amp;&amp; abandonmentStateDict[streamId][type]) {
                delete abandonmentStateDict[streamId][type];
            }

        } catch (e) {

        }
    }

    function resetInitialSettings() {
        topQualities = {};
        qualityDict = {};
        abandonmentStateDict = {};
        streamProcessorDict = {};
        switchHistoryDict = {};
        isUsingBufferOccupancyAbrDict = {};
        isUsingL2AAbrDict = {};
        isUsingLoLPAbrDict = {};

        if (windowResizeEventCalled === undefined) {
            windowResizeEventCalled = false;
        }
        if (droppedFramesHistory) {
            droppedFramesHistory.reset();
        }

        playbackIndex = undefined;
        droppedFramesHistory = undefined;
        throughputHistory = undefined;
        clearTimeout(abandonmentTimeout);
        abandonmentTimeout = null;
    }

    function reset() {

        resetInitialSettings();

        eventBus.off(Events.LOADING_PROGRESS, _onFragmentLoadProgress, instance);
        eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_RENDERED, _onQualityChangeRendered, instance);
        eventBus.off(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance);

        if (abrRulesCollection) {
            abrRulesCollection.reset();
        }
    }

    function setConfig(config) {
        if (!config) return;

        if (config.streamController) {
            streamController = config.streamController;
        }
        if (config.domStorage) {
            domStorage = config.domStorage;
        }
        if (config.mediaPlayerModel) {
            mediaPlayerModel = config.mediaPlayerModel;
        }
        if (config.customParametersModel) {
            customParametersModel = config.customParametersModel;
        }
        if (config.dashMetrics) {
            dashMetrics = config.dashMetrics;
        }
        if (config.adapter) {
            adapter = config.adapter;
        }
        if (config.videoModel) {
            videoModel = config.videoModel;
        }
        if (config.settings) {
            settings = config.settings;
        }
    }

    function checkConfig() {
        if (!domStorage || !domStorage.hasOwnProperty('getSavedBitrateSettings')) {
            throw new Error(Constants.MISSING_CONFIG_ERROR);
        }
    }

    /**
     * While fragment loading is in progress we check if we might need to abort the request
     * @param {object} e
     * @private
     */
    function _onFragmentLoadProgress(e) {
        const type = e.request.mediaType;
        const streamId = e.streamId;

        if (!type || !streamId || !streamProcessorDict[streamId] || !settings.get().streaming.abr.autoSwitchBitrate[type]) {
            return;
        }

        const streamProcessor = streamProcessorDict[streamId][type];
        if (!streamProcessor) {
            return;
        }

        const rulesContext = RulesContext(context).create({
            abrController: instance,
            streamProcessor: streamProcessor,
            currentRequest: e.request,
            useBufferOccupancyABR: isUsingBufferOccupancyAbrDict[type],
            useL2AABR: isUsingL2AAbrDict[type],
            useLoLPABR: isUsingLoLPAbrDict[type],
            videoModel
        });
        const switchRequest = abrRulesCollection.shouldAbandonFragment(rulesContext, streamId);

        if (switchRequest.quality > SwitchRequest.NO_CHANGE) {
            const fragmentModel = streamProcessor.getFragmentModel();
            const request = fragmentModel.getRequests({
                state: FragmentModel.FRAGMENT_MODEL_LOADING,
                index: e.request.index
            })[0];
            if (request) {
                abandonmentStateDict[streamId][type].state = MetricsConstants.ABANDON_LOAD;
                switchHistoryDict[streamId][type].reset();
                switchHistoryDict[streamId][type].push({
                    oldValue: getQualityFor(type, streamId),
                    newValue: switchRequest.quality,
                    confidence: 1,
                    reason: switchRequest.reason
                });
                setPlaybackQuality(type, streamController.getActiveStreamInfo(), switchRequest.quality, switchRequest.reason);

                clearTimeout(abandonmentTimeout);
                abandonmentTimeout = setTimeout(
                    () => {
                        abandonmentStateDict[streamId][type].state = MetricsConstants.ALLOW_LOAD;
                        abandonmentTimeout = null;
                    },
                    settings.get().streaming.abandonLoadTimeout
                );
            }
        }
    }

    /**
     * Update dropped frames history when the quality was changed
     * @param {object} e
     * @private
     */
    function _onQualityChangeRendered(e) {
        if (e.mediaType === Constants.VIDEO) {
            if (playbackIndex !== undefined) {
                droppedFramesHistory.push(e.streamId, playbackIndex, videoModel.getPlaybackQuality());
            }
            playbackIndex = e.newQuality;
        }
    }

    /**
     * When the buffer level is updated we check if we need to change the ABR strategy
     * @param e
     * @private
     */
    function _onMetricAdded(e) {
        if (e.metric === MetricsConstants.HTTP_REQUEST &amp;&amp; e.value &amp;&amp; e.value.type === HTTPRequest.MEDIA_SEGMENT_TYPE &amp;&amp; (e.mediaType === Constants.AUDIO || e.mediaType === Constants.VIDEO)) {
            throughputHistory.push(e.mediaType, e.value, settings.get().streaming.abr.useDeadTimeLatency);
        }

        if (e.metric === MetricsConstants.BUFFER_LEVEL &amp;&amp; (e.mediaType === Constants.AUDIO || e.mediaType === Constants.VIDEO)) {
            _updateAbrStrategy(e.mediaType, 0.001 * e.value.level);
        }
    }

    /**
     * Returns the highest possible index taking limitations like maxBitrate, representationRatio and portal size into account.
     * @param {string} type
     * @param {string} streamId
     * @return {number}
     */
    function getMaxAllowedIndexFor(type, streamId) {
        try {
            let idx;
            topQualities[streamId] = topQualities[streamId] || {};

            if (!topQualities[streamId].hasOwnProperty(type)) {
                topQualities[streamId][type] = 0;
            }

            idx = _checkMaxBitrate(type, streamId);
            idx = _checkMaxRepresentationRatio(idx, type, streamId);
            idx = _checkPortalSize(idx, type, streamId);
            return idx;
        } catch (e) {
            return undefined
        }
    }

    /**
     * Returns the minimum allowed index. We consider thresholds defined in the settings, i.e. minBitrate for the corresponding media type.
     * @param {string} type
     * @param {string} streamId
     * @return {undefined|number}
     */
    function getMinAllowedIndexFor(type, streamId) {
        try {
            return _getMinIndexBasedOnBitrateFor(type, streamId);
        } catch (e) {
            return undefined
        }
    }

    /**
     * Returns the maximum allowed index.
     * @param {string} type
     * @param {string} streamId
     * @return {undefined|number}
     */
    function _getMaxIndexBasedOnBitrateFor(type, streamId) {
        try {
            const maxBitrate = mediaPlayerModel.getAbrBitrateParameter('maxBitrate', type);
            if (maxBitrate > -1) {
                return getQualityForBitrate(streamProcessorDict[streamId][type].getMediaInfo(), maxBitrate, streamId);
            } else {
                return undefined;
            }
        } catch (e) {
            return undefined
        }
    }

    /**
     * Returns the minimum allowed index.
     * @param {string} type
     * @param {string} streamId
     * @return {undefined|number}
     */
    function _getMinIndexBasedOnBitrateFor(type, streamId) {
        try {
            const minBitrate = mediaPlayerModel.getAbrBitrateParameter('minBitrate', type);

            if (minBitrate > -1) {
                const mediaInfo = streamProcessorDict[streamId][type].getMediaInfo();
                const bitrateList = getBitrateList(mediaInfo);
                // This returns the quality index &lt;= for the given bitrate
                let minIdx = getQualityForBitrate(mediaInfo, minBitrate, streamId);
                if (bitrateList[minIdx] &amp;&amp; minIdx &lt; bitrateList.length - 1 &amp;&amp; bitrateList[minIdx].bitrate &lt; minBitrate * 1000) {
                    minIdx++; // Go to the next bitrate
                }
                return minIdx;
            } else {
                return undefined;
            }
        } catch (e) {
            return undefined;
        }
    }

    /**
     * Returns the maximum possible index
     * @param type
     * @param streamId
     * @return {number|*}
     */
    function _checkMaxBitrate(type, streamId) {
        let idx = topQualities[streamId][type];
        let newIdx = idx;

        if (!streamProcessorDict[streamId] || !streamProcessorDict[streamId][type]) {
            return newIdx;
        }

        const minIdx = getMinAllowedIndexFor(type, streamId);
        if (minIdx !== undefined) {
            newIdx = Math.max(idx, minIdx);
        }

        const maxIdx = _getMaxIndexBasedOnBitrateFor(type, streamId);
        if (maxIdx !== undefined) {
            newIdx = Math.min(newIdx, maxIdx);
        }

        return newIdx;
    }

    /**
     * Returns the maximum index according to maximum representation ratio
     * @param idx
     * @param type
     * @param streamId
     * @return {number|*}
     * @private
     */
    function _checkMaxRepresentationRatio(idx, type, streamId) {
        let maxIdx = topQualities[streamId][type]
        const maxRepresentationRatio = settings.get().streaming.abr.maxRepresentationRatio[type];

        if (isNaN(maxRepresentationRatio) || maxRepresentationRatio >= 1 || maxRepresentationRatio &lt; 0) {
            return idx;
        }
        return Math.min(idx, Math.round(maxIdx * maxRepresentationRatio));
    }

    /**
     * Returns the maximum index according to the portal size
     * @param idx
     * @param type
     * @param streamId
     * @return {number|*}
     * @private
     */
    function _checkPortalSize(idx, type, streamId) {
        if (type !== Constants.VIDEO || !settings.get().streaming.abr.limitBitrateByPortal || !streamProcessorDict[streamId] || !streamProcessorDict[streamId][type]) {
            return idx;
        }

        if (!windowResizeEventCalled) {
            setElementSize();
        }
        const streamInfo = streamProcessorDict[streamId][type].getStreamInfo();
        const representation = adapter.getAdaptationForType(streamInfo.index, type, streamInfo).Representation_asArray;
        let newIdx = idx;

        if (elementWidth > 0 &amp;&amp; elementHeight > 0) {
            while (
                newIdx > 0 &amp;&amp;
                representation[newIdx] &amp;&amp;
                elementWidth &lt; representation[newIdx].width &amp;&amp;
                elementWidth - representation[newIdx - 1].width &lt; representation[newIdx].width - elementWidth) {
                newIdx = newIdx - 1;
            }

            // Make sure that in case of multiple representation elements have same
            // resolution, every such element is included
            while (newIdx &lt; representation.length - 1 &amp;&amp; representation[newIdx].width === representation[newIdx + 1].width) {
                newIdx = newIdx + 1;
            }
        }

        return newIdx;
    }

    /**
     * Gets top BitrateInfo for the player
     * @param {string} type - 'video' or 'audio' are the type options.
     * @param {string} streamId - Id of the stream
     * @returns {BitrateInfo | null}
     */
    function getTopBitrateInfoFor(type, streamId = null) {
        if (!streamId) {
            streamId = streamController.getActiveStreamInfo().id;
        }
        if (type &amp;&amp; streamProcessorDict &amp;&amp; streamProcessorDict[streamId] &amp;&amp; streamProcessorDict[streamId][type]) {
            const idx = getMaxAllowedIndexFor(type, streamId);
            const bitrates = getBitrateList(streamProcessorDict[streamId][type].getMediaInfo());
            return bitrates[idx] ? bitrates[idx] : null;
        }
        return null;
    }

    /**
     * Returns the initial bitrate for a specific media type and stream id
     * @param {string} type
     * @param {string} streamId
     * @returns {number} A value of the initial bitrate, kbps
     * @memberof AbrController#
     */
    function getInitialBitrateFor(type, streamId) {
        checkConfig();

        if (type === Constants.TEXT) {
            return NaN;
        }

        const savedBitrate = domStorage.getSavedBitrateSettings(type);
        let configBitrate = mediaPlayerModel.getAbrBitrateParameter('initialBitrate', type);
        let configRatio = settings.get().streaming.abr.initialRepresentationRatio[type];

        if (configBitrate === -1) {
            if (configRatio > -1) {
                const streamInfo = streamProcessorDict[streamId][type].getStreamInfo();
                const representation = adapter.getAdaptationForType(streamInfo.index, type, streamInfo).Representation_asArray;
                if (Array.isArray(representation)) {
                    const repIdx = Math.max(Math.round(representation.length * configRatio) - 1, 0);
                    configBitrate = representation[repIdx].bandwidth / 1000;
                } else {
                    configBitrate = 0;
                }
            } else if (!isNaN(savedBitrate)) {
                configBitrate = savedBitrate;
            } else {
                configBitrate = (type === Constants.VIDEO) ? DEFAULT_VIDEO_BITRATE : DEFAULT_AUDIO_BITRATE;
            }
        }

        return configBitrate;
    }

    /**
     * This function is called by the scheduleControllers to check if the quality should be changed.
     * Consider this the main entry point for the ABR decision logic
     * @param {string} type
     * @param {string} streamId
     */
    function checkPlaybackQuality(type, streamId) {
        try {
            if (!type || !streamProcessorDict || !streamProcessorDict[streamId] || !streamProcessorDict[streamId][type]) {
                return false;
            }

            if (droppedFramesHistory) {
                const playbackQuality = videoModel.getPlaybackQuality();
                if (playbackQuality) {
                    droppedFramesHistory.push(streamId, playbackIndex, playbackQuality);
                }
            }

            // ABR is turned off, do nothing
            if (!settings.get().streaming.abr.autoSwitchBitrate[type]) {
                return false;
            }

            const oldQuality = getQualityFor(type, streamId);
            const rulesContext = RulesContext(context).create({
                abrController: instance,
                switchHistory: switchHistoryDict[streamId][type],
                droppedFramesHistory: droppedFramesHistory,
                streamProcessor: streamProcessorDict[streamId][type],
                currentValue: oldQuality,
                useBufferOccupancyABR: isUsingBufferOccupancyAbrDict[type],
                useL2AABR: isUsingL2AAbrDict[type],
                useLoLPABR: isUsingLoLPAbrDict[type],
                videoModel
            });
            const minIdx = getMinAllowedIndexFor(type, streamId);
            const maxIdx = getMaxAllowedIndexFor(type, streamId);
            const switchRequest = abrRulesCollection.getMaxQuality(rulesContext);
            let newQuality = switchRequest.quality;

            if (minIdx !== undefined &amp;&amp; ((newQuality > SwitchRequest.NO_CHANGE) ? newQuality : oldQuality) &lt; minIdx) {
                newQuality = minIdx;
            }
            if (newQuality > maxIdx) {
                newQuality = maxIdx;
            }

            switchHistoryDict[streamId][type].push({ oldValue: oldQuality, newValue: newQuality });

            if (newQuality > SwitchRequest.NO_CHANGE &amp;&amp; newQuality !== oldQuality &amp;&amp; (abandonmentStateDict[streamId][type].state === MetricsConstants.ALLOW_LOAD || newQuality &lt; oldQuality)) {
                _changeQuality(type, oldQuality, newQuality, maxIdx, switchRequest.reason, streamId);
                return true;
            }

            return false;
        } catch (e) {
            return false;
        }

    }

    /**
     * Returns the current quality for a specific media type and a specific streamId
     * @param {string} type
     * @param {string} streamId
     * @return {number|*}
     */
    function getQualityFor(type, streamId = null) {
        try {
            if (!streamId) {
                streamId = streamController.getActiveStreamInfo().id;
            }
            if (type &amp;&amp; streamProcessorDict[streamId] &amp;&amp; streamProcessorDict[streamId][type]) {
                let quality;

                if (streamId) {
                    qualityDict[streamId] = qualityDict[streamId] || {};

                    if (!qualityDict[streamId].hasOwnProperty(type)) {
                        qualityDict[streamId][type] = QUALITY_DEFAULT;
                    }

                    quality = qualityDict[streamId][type];
                    return quality;
                }
            }
            return QUALITY_DEFAULT;
        } catch (e) {
            return QUALITY_DEFAULT;
        }
    }

    /**
     * Sets the new playback quality. Starts from index 0.
     * If the index of the new quality is the same as the old one changeQuality will not be called.
     * @param {string} type
     * @param {object} streamInfo
     * @param {number} newQuality
     * @param {string} reason
     */
    function setPlaybackQuality(type, streamInfo, newQuality, reason = null) {
        if (!streamInfo || !streamInfo.id || !type) {
            return;
        }
        const streamId = streamInfo.id;
        const oldQuality = getQualityFor(type, streamId);

        checkInteger(newQuality);

        const topQualityIdx = getMaxAllowedIndexFor(type, streamId);

        if (newQuality !== oldQuality &amp;&amp; newQuality >= 0 &amp;&amp; newQuality &lt;= topQualityIdx) {
            _changeQuality(type, oldQuality, newQuality, topQualityIdx, reason, streamId);
        }
    }

    /**
     *
     * @param {string} streamId
     * @param {type} type
     * @return {*|null}
     */
    function getAbandonmentStateFor(streamId, type) {
        return abandonmentStateDict[streamId] &amp;&amp; abandonmentStateDict[streamId][type] ? abandonmentStateDict[streamId][type].state : null;
    }


    /**
     * Changes the internal qualityDict values according to the new quality
     * @param {string} type
     * @param {number} oldQuality
     * @param {number} newQuality
     * @param {number} maxIdx
     * @param {string} reason
     * @param {object} streamId
     * @private
     */
    function _changeQuality(type, oldQuality, newQuality, maxIdx, reason, streamId) {
        if (type &amp;&amp; streamProcessorDict[streamId] &amp;&amp; streamProcessorDict[streamId][type]) {
            const streamInfo = streamProcessorDict[streamId][type].getStreamInfo();
            const isDynamic = streamInfo &amp;&amp; streamInfo.manifestInfo &amp;&amp; streamInfo.manifestInfo.isDynamic;
            const bufferLevel = dashMetrics.getCurrentBufferLevel(type);
            logger.info('Stream ID: ' + streamId + ' [' + type + '] switch from ' + oldQuality + ' to ' + newQuality + '/' + maxIdx + ' (buffer: ' + bufferLevel + ') ' + (reason ? JSON.stringify(reason) : '.'));

            qualityDict[streamId] = qualityDict[streamId] || {};
            qualityDict[streamId][type] = newQuality;
            const bitrateInfo = _getBitrateInfoForQuality(streamId, type, newQuality);
            eventBus.trigger(Events.QUALITY_CHANGE_REQUESTED,
                {
                    oldQuality,
                    newQuality,
                    reason,
                    streamInfo,
                    bitrateInfo,
                    maxIdx,
                    mediaType: type
                },
                { streamId: streamInfo.id, mediaType: type }
            );
            const bitrate = throughputHistory.getAverageThroughput(type, isDynamic);
            if (!isNaN(bitrate)) {
                domStorage.setSavedBitrateSettings(type, bitrate);
            }
        }
    }

    function _getBitrateInfoForQuality(streamId, type, idx) {
        if (type &amp;&amp; streamProcessorDict &amp;&amp; streamProcessorDict[streamId] &amp;&amp; streamProcessorDict[streamId][type]) {
            const bitrates = getBitrateList(streamProcessorDict[streamId][type].getMediaInfo());
            return bitrates[idx] ? bitrates[idx] : null;
        }
        return null;
    }

    /**
     * @param {MediaInfo} mediaInfo
     * @param {number} bitrate A bitrate value, kbps
     * @param {String} streamId Period ID
     * @param {number|null} latency Expected latency of connection, ms
     * @returns {number} A quality index &lt;= for the given bitrate
     * @memberof AbrController#
     */
    function getQualityForBitrate(mediaInfo, bitrate, streamId, latency = null) {
        const voRepresentation = mediaInfo &amp;&amp; mediaInfo.type ? streamProcessorDict[streamId][mediaInfo.type].getRepresentationInfo() : null;

        if (settings.get().streaming.abr.useDeadTimeLatency &amp;&amp; latency &amp;&amp; voRepresentation &amp;&amp; voRepresentation.fragmentDuration) {
            latency = latency / 1000;
            const fragmentDuration = voRepresentation.fragmentDuration;
            if (latency > fragmentDuration) {
                return 0;
            } else {
                const deadTimeRatio = latency / fragmentDuration;
                bitrate = bitrate * (1 - deadTimeRatio);
            }
        }

        const bitrateList = getBitrateList(mediaInfo);

        for (let i = bitrateList.length - 1; i >= 0; i--) {
            const bitrateInfo = bitrateList[i];
            if (bitrate * 1000 >= bitrateInfo.bitrate) {
                return i;
            }
        }
        return QUALITY_DEFAULT;
    }

    /**
     * @param {MediaInfo} mediaInfo
     * @returns {Array|null} A list of {@link BitrateInfo} objects
     * @memberof AbrController#
     */
    function getBitrateList(mediaInfo) {
        const infoList = [];
        if (!mediaInfo || !mediaInfo.bitrateList) return infoList;

        const bitrateList = mediaInfo.bitrateList;
        const type = mediaInfo.type;

        let bitrateInfo;

        for (let i = 0, ln = bitrateList.length; i &lt; ln; i++) {
            bitrateInfo = new BitrateInfo();
            bitrateInfo.mediaType = type;
            bitrateInfo.qualityIndex = i;
            bitrateInfo.bitrate = bitrateList[i].bandwidth;
            bitrateInfo.width = bitrateList[i].width;
            bitrateInfo.height = bitrateList[i].height;
            bitrateInfo.scanType = bitrateList[i].scanType;
            infoList.push(bitrateInfo);
        }

        return infoList;
    }

    function _updateAbrStrategy(mediaType, bufferLevel) {
        // else ABR_STRATEGY_DYNAMIC
        const strategy = settings.get().streaming.abr.ABRStrategy;

        if (strategy === Constants.ABR_STRATEGY_DYNAMIC) {
            _updateDynamicAbrStrategy(mediaType, bufferLevel);
        }
    }

    function _updateDynamicAbrStrategy(mediaType, bufferLevel) {
        try {
            const stableBufferTime = mediaPlayerModel.getStableBufferTime();
            const switchOnThreshold = stableBufferTime;
            const switchOffThreshold = 0.5 * stableBufferTime;

            const useBufferABR = isUsingBufferOccupancyAbrDict[mediaType];
            const newUseBufferABR = bufferLevel > (useBufferABR ? switchOffThreshold : switchOnThreshold); // use hysteresis to avoid oscillating rules
            isUsingBufferOccupancyAbrDict[mediaType] = newUseBufferABR;

            if (newUseBufferABR !== useBufferABR) {
                if (newUseBufferABR) {
                    logger.info('[' + mediaType + '] switching from throughput to buffer occupancy ABR rule (buffer: ' + bufferLevel.toFixed(3) + ').');
                } else {
                    logger.info('[' + mediaType + '] switching from buffer occupancy to throughput ABR rule (buffer: ' + bufferLevel.toFixed(3) + ').');
                }
            }
        } catch (e) {
            logger.error(e);
        }
    }

    function getThroughputHistory() {
        return throughputHistory;
    }

    function updateTopQualityIndex(mediaInfo) {
        const type = mediaInfo.type;
        const streamId = mediaInfo.streamInfo.id;
        const max = mediaInfo.representationCount - 1;

        topQualities[streamId] = topQualities[streamId] || {};
        topQualities[streamId][type] = max;

        return max;
    }

    function isPlayingAtTopQuality(streamInfo) {
        const streamId = streamInfo ? streamInfo.id : null;
        const audioQuality = getQualityFor(Constants.AUDIO, streamId);
        const videoQuality = getQualityFor(Constants.VIDEO, streamId);

        const isAtTop = (audioQuality === getMaxAllowedIndexFor(Constants.AUDIO, streamId)) &amp;&amp;
            (videoQuality === getMaxAllowedIndexFor(Constants.VIDEO, streamId));

        return isAtTop;
    }

    function setWindowResizeEventCalled(value) {
        windowResizeEventCalled = value;
    }

    function setElementSize() {
        if (videoModel) {
            const hasPixelRatio = settings.get().streaming.abr.usePixelRatioInLimitBitrateByPortal &amp;&amp; window.hasOwnProperty('devicePixelRatio');
            const pixelRatio = hasPixelRatio ? window.devicePixelRatio : 1;
            elementWidth = videoModel.getClientWidth() * pixelRatio;
            elementHeight = videoModel.getClientHeight() * pixelRatio;
        }
    }

    function clearDataForStream(streamId) {
        if (droppedFramesHistory) {
            droppedFramesHistory.clearForStream(streamId);
        }
        if (streamProcessorDict[streamId]) {
            delete streamProcessorDict[streamId];
        }
        if (switchHistoryDict[streamId]) {
            delete switchHistoryDict[streamId];
        }

        if (abandonmentStateDict[streamId]) {
            delete abandonmentStateDict[streamId];
        }
    }

    instance = {
        initialize,
        isPlayingAtTopQuality,
        updateTopQualityIndex,
        clearDataForStream,
        getThroughputHistory,
        getBitrateList,
        getQualityForBitrate,
        getTopBitrateInfoFor,
        getMinAllowedIndexFor,
        getMaxAllowedIndexFor,
        getInitialBitrateFor,
        getQualityFor,
        getAbandonmentStateFor,
        setPlaybackQuality,
        checkPlaybackQuality,
        setElementSize,
        setWindowResizeEventCalled,
        registerStreamType,
        unRegisterStreamType,
        setConfig,
        reset
    };

    setup();

    return instance;
}

AbrController.__dashjs_factory_name = 'AbrController';
const factory = FactoryMaker.getSingletonFactory(AbrController);
factory.QUALITY_DEFAULT = QUALITY_DEFAULT;
FactoryMaker.updateSingletonFactory(AbrController.__dashjs_factory_name, factory);
export default factory;
</pre>
    </article>
</section>





		</div>
	</div>

	<div class="clearfix"></div>

	

</div>
</div>


    <div class="modal fade" id="searchResults">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title">Search results</h4>
          </div>
          <div class="modal-body"></div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
          </div>
        </div><!-- /.modal-content -->
      </div><!-- /.modal-dialog -->
    </div>


<footer>


	<span class="copyright">
	<h3>DASH Industry Forum</h3>
	</span>

<span class="jsdoc-message">
	Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.7</a>
	
		on Wed Sep 28th 2022
	
	using the <a href="https://github.com/docstrap/docstrap">DocStrap template</a>.
</span>
</footer>

<script src="scripts/docstrap.lib.js"></script>
<script src="scripts/toc.js"></script>

    <script type="text/javascript" src="scripts/fulltext-search-ui.js"></script>


<script>
$( function () {
	$( "[id*='$']" ).each( function () {
		var $this = $( this );

		$this.attr( "id", $this.attr( "id" ).replace( "$", "__" ) );
	} );

	$( ".tutorial-section pre, .readme-section pre, pre.prettyprint.source" ).each( function () {
		var $this = $( this );

		var example = $this.find( "code" );
		exampleText = example.html();
		var lang = /{@lang (.*?)}/.exec( exampleText );
		if ( lang && lang[1] ) {
			exampleText = exampleText.replace( lang[0], "" );
			example.html( exampleText );
			lang = lang[1];
		} else {
			var langClassMatch = example.parent()[0].className.match(/lang\-(\S+)/);
			lang = langClassMatch ? langClassMatch[1] : "javascript";
		}

		if ( lang ) {

			$this
			.addClass( "sunlight-highlight-" + lang )
			.addClass( "linenums" )
			.html( example.html() );

		}
	} );

	Sunlight.highlightAll( {
		lineNumbers : true,
		showMenu : true,
		enableDoclinks : true
	} );

	$.catchAnchorLinks( {
        navbarOffset: 10
	} );
	$( "#toc" ).toc( {
		anchorName  : function ( i, heading, prefix ) {
			return $( heading ).attr( "id" ) || ( prefix + i );
		},
		selectors   : "#toc-content h1,#toc-content h2,#toc-content h3,#toc-content h4",
		showAndHide : false,
		smoothScrolling: true
	} );

	$( "#main span[id^='toc']" ).addClass( "toc-shim" );
	$( '.dropdown-toggle' ).dropdown();

    $( "table" ).each( function () {
      var $this = $( this );
      $this.addClass('table');
    } );

} );
</script>



<!--Navigation and Symbol Display-->

<script>
	$( function () {
		$( '#main' ).localScroll( {
			offset : { top : 60 } //offset by the height of your header (give or take a few px, see what works for you)
		} );
		$( "dt.name" ).each( function () {
			var $this = $( this ).find("h4");
			var icon = $( "<i/>" ).addClass( "icon-plus-sign" ).addClass( "pull-right" ).addClass( "icon-white" );
			var dt = $(this);
			var children = dt.next( "dd" );

			dt.prepend( icon ).css( {cursor : "pointer"} );
			dt.addClass( "member-collapsed" ).addClass( "member" );


			children.hide();

			dt.children().on( "click", function () {
				children = dt.next( "dd" );
				children.slideToggle( "fast", function () {

					if ( children.is( ":visible" ) ) {
						icon.addClass( "icon-minus-sign" ).removeClass( "icon-plus-sign" ).removeClass( "icon-white" );
						dt.addClass( "member-open" ).animate( "member-collapsed" );
					} else {
						icon.addClass( "icon-plus-sign" ).removeClass( "icon-minus-sign" ).addClass( "icon-white" );
						dt.addClass( "member-collapsed" ).removeClass( "member-open" );
					}
				} );
			} );

		} );
	} );
</script>


<!--Google Analytics-->



    <script type="text/javascript">
        $(document).ready(function() {
            SearcherDisplay.init();
        });
    </script>


</body>
</html>