UNPKG

dashjs

Version:

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

678 lines (591 loc) 95.2 kB
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="https://dashif.org/img/favicon.ico"><link type="text/css" rel="stylesheet" href="jsdoc-custom.css"><title>Source: streaming/StreamProcessor.js</title><!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">Home</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-DashAdapter.html">DashAdapter</a></div><div class="sidebar-section-children"><a href="module-DashMetrics.html">DashMetrics</a></div><div class="sidebar-section-children"><a href="module-MediaPlayer.html">MediaPlayer</a></div><div class="sidebar-section-children"><a href="module-OfflineController.html">OfflineController</a></div><div class="sidebar-section-children"><a href="module-ProtectionController.html">ProtectionController</a></div><div class="sidebar-section-children"><a href="module-Settings.html">Settings</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Errors.html">Errors</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html">MediaPlayerEvents</a></div><div class="sidebar-section-children"><a href="MediaPlayerModel.html">MediaPlayerModel</a></div><div class="sidebar-section-children"><a href="MetricsReportingEvents.html">MetricsReportingEvents</a></div><div class="sidebar-section-children"><a href="MssErrors.html">MssErrors</a></div><div class="sidebar-section-children"><a href="OfflineErrors.html">OfflineErrors</a></div><div class="sidebar-section-children"><a href="OfflineEvents.html">OfflineEvents</a></div><div class="sidebar-section-children"><a href="ProtectionErrors.html">ProtectionErrors</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html">ProtectionEvents</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-events"><div>Events</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIES">ADAPTATION_SET_REMOVED_NO_CAPABILITIES</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:AST_IN_FUTURE">AST_IN_FUTURE</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:BASE_URLS_UPDATED">BASE_URLS_UPDATED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:BUFFER_EMPTY">BUFFER_EMPTY</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_STATE_CHANGED">BUFFER_LEVEL_STATE_CHANGED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_UPDATED">BUFFER_LEVEL_UPDATED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:BUFFER_LOADED">BUFFER_LOADED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CAN_PLAY">CAN_PLAY</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CAN_PLAY_THROUGH">CAN_PLAY_THROUGH</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CAPTION_CONTAINER_RESIZE">CAPTION_CONTAINER_RESIZE</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CAPTION_RENDERED">CAPTION_RENDERED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CONFORMANCE_VIOLATION">CONFORMANCE_VIOLATION</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CONTENT_STEERING_REQUEST_COMPLETED">CONTENT_STEERING_REQUEST_COMPLETED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CUE_ENTER">CUE_ENTER</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:CUE_ENTER">CUE_ENTER</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:DVB_FONT_DOWNLOAD_ADDED">DVB_FONT_DOWNLOAD_ADDED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:DVB_FONT_DOWNLOAD_COMPLETE">DVB_FONT_DOWNLOAD_COMPLETE</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:DVB_FONT_DOWNLOAD_FAILED">DVB_FONT_DOWNLOAD_FAILED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:DYNAMIC_TO_STATIC">DYNAMIC_TO_STATIC</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:ERROR">ERROR</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_RECEIVE">EVENT_MODE_ON_RECEIVE</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_START">EVENT_MODE_ON_START</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_ABANDONED">FRAGMENT_LOADING_ABANDONED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_COMPLETED">FRAGMENT_LOADING_COMPLETED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_PROGRESS">FRAGMENT_LOADING_PROGRESS</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_STARTED">FRAGMENT_LOADING_STARTED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:INBAND_PRFT">INBAND_PRFT</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:LOG">LOG</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:MANIFEST_LOADED">MANIFEST_LOADED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:MANIFEST_LOADING_FINISHED">MANIFEST_LOADING_FINISHED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:MANIFEST_LOADING_STARTED">MANIFEST_LOADING_STARTED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:MANIFEST_VALIDITY_CHANGED">MANIFEST_VALIDITY_CHANGED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:METRIC_ADDED">METRIC_ADDED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:METRIC_CHANGED">METRIC_CHANGED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:METRIC_UPDATED">METRIC_UPDATED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:METRICS_CHANGED">METRICS_CHANGED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_COMPLETED">PERIOD_SWITCH_COMPLETED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_STARTED">PERIOD_SWITCH_STARTED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_ENDED">PLAYBACK_ENDED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_ERROR">PLAYBACK_ERROR</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_LOADED_DATA">PLAYBACK_LOADED_DATA</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_METADATA_LOADED">PLAYBACK_METADATA_LOADED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_NOT_ALLOWED">PLAYBACK_NOT_ALLOWED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_PAUSED">PLAYBACK_PAUSED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_PLAYING">PLAYBACK_PLAYING</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_PROGRESS">PLAYBACK_PROGRESS</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_RATE_CHANGED">PLAYBACK_RATE_CHANGED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKED">PLAYBACK_SEEKED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKING">PLAYBACK_SEEKING</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_STALLED">PLAYBACK_STALLED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_STARTED">PLAYBACK_STARTED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_TIME_UPDATED">PLAYBACK_TIME_UPDATED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_VOLUME_CHANGED">PLAYBACK_VOLUME_CHANGED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:PLAYBACK_WAITING">PLAYBACK_WAITING</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_RENDERED">QUALITY_CHANGE_RENDERED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_REQUESTED">QUALITY_CHANGE_REQUESTED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:REPRESENTATION_SWITCH">REPRESENTATION_SWITCH</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:STREAM_ACTIVATED">STREAM_ACTIVATED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:STREAM_DEACTIVATED">STREAM_DEACTIVATED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZED">STREAM_INITIALIZED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZING">STREAM_INITIALIZING</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:STREAM_TEARDOWN_COMPLETE">STREAM_TEARDOWN_COMPLETE</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:STREAM_UPDATED">STREAM_UPDATED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:TEXT_TRACK_ADDED">TEXT_TRACK_ADDED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:TEXT_TRACKS_ADDED">TEXT_TRACKS_ADDED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:THROUGHPUT_MEASUREMENT_STORED">THROUGHPUT_MEASUREMENT_STORED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:TRACK_CHANGE_RENDERED">TRACK_CHANGE_RENDERED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:TTML_PARSED">TTML_PARSED</a></div><div class="sidebar-section-children"><a href="MediaPlayerEvents.html#event:TTML_TO_PARSE">TTML_TO_PARSE</a></div><div class="sidebar-section-children"><a href="MetricsReportingEvents.html#event:CMCD_DATA_GENERATED">CMCD_DATA_GENERATED</a></div><div class="sidebar-section-children"><a href="OfflineEvents.html#event:OFFLINE_RECORD_FINISHED">OFFLINE_RECORD_FINISHED</a></div><div class="sidebar-section-children"><a href="OfflineEvents.html#event:OFFLINE_RECORD_LOADEDMETADATA">OFFLINE_RECORD_LOADEDMETADATA</a></div><div class="sidebar-section-children"><a href="OfflineEvents.html#event:OFFLINE_RECORD_STARTED">OFFLINE_RECORD_STARTED</a></div><div class="sidebar-section-children"><a href="OfflineEvents.html#event:OFFLINE_RECORD_STOPPED">OFFLINE_RECORD_STOPPED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_ADDED">KEY_ADDED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_ERROR">KEY_ERROR</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_MESSAGE">KEY_MESSAGE</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_SESSION_CLOSED">KEY_SESSION_CLOSED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_SESSION_CREATED">KEY_SESSION_CREATED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_SESSION_REMOVED">KEY_SESSION_REMOVED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_STATUSES_CHANGED">KEY_STATUSES_CHANGED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_SYSTEM_ACCESS_COMPLETE">KEY_SYSTEM_ACCESS_COMPLETE</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:KEY_SYSTEM_SELECTED">KEY_SYSTEM_SELECTED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:LICENSE_REQUEST_COMPLETE">LICENSE_REQUEST_COMPLETE</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:LICENSE_REQUEST_SENDING">LICENSE_REQUEST_SENDING</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:PROTECTION_CREATED">PROTECTION_CREATED</a></div><div class="sidebar-section-children"><a href="ProtectionEvents.html#event:PROTECTION_DESTROYED">PROTECTION_DESTROYED</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#LICENSE_SERVER_MANIFEST_CONFIGURATIONS">LICENSE_SERVER_MANIFEST_CONFIGURATIONS</a></div><div class="sidebar-section-children"><a href="global.html#MediaType">MediaType</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">streaming_StreamProcessor.js</h1></header><article><pre class="prettyprint source lang-js"><code>/** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import Constants from './constants/Constants'; import DashConstants from '../dash/constants/DashConstants'; import MetricsConstants from './constants/MetricsConstants'; import FragmentModel from './models/FragmentModel'; import BufferController from './controllers/BufferController'; import NotFragmentedTextBufferController from './text/NotFragmentedTextBufferController'; import ScheduleController from './controllers/ScheduleController'; import RepresentationController from '../dash/controllers/RepresentationController'; import FactoryMaker from '../core/FactoryMaker'; import {checkInteger} from './utils/SupervisorTools'; import EventBus from '../core/EventBus'; import Events from '../core/events/Events'; import MediaPlayerEvents from './MediaPlayerEvents'; import DashHandler from '../dash/DashHandler'; import Errors from '../core/errors/Errors'; import DashJSError from './vo/DashJSError'; import Debug from '../core/Debug'; import RequestModifier from './utils/RequestModifier'; import URLUtils from '../streaming/utils/URLUtils'; import {PlayListTrace} from './vo/metrics/PlayList'; import SegmentsController from '../dash/controllers/SegmentsController'; import {HTTPRequest} from './vo/metrics/HTTPRequest'; import TimeUtils from './utils/TimeUtils'; function StreamProcessor(config) { config = config || {}; let context = this.context; let eventBus = EventBus(context).getInstance(); let streamInfo = config.streamInfo; let type = config.type; let errHandler = config.errHandler; let mimeType = config.mimeType; let timelineConverter = config.timelineConverter; let adapter = config.adapter; let manifestModel = config.manifestModel; let mediaPlayerModel = config.mediaPlayerModel; let fragmentModel = config.fragmentModel; let abrController = config.abrController; let playbackController = config.playbackController; let mediaController = config.mediaController; let textController = config.textController; let dashMetrics = config.dashMetrics; let settings = config.settings; let boxParser = config.boxParser; let segmentBlacklistController = config.segmentBlacklistController; let instance, logger, isDynamic, mediaInfo, mediaInfoArr, bufferController, scheduleController, representationController, shouldUseExplicitTimeForRequest, shouldRepeatRequest, qualityChangeInProgress, dashHandler, segmentsController, bufferingTime, pendingSwitchToRepresentationInfo; function setup() { logger = Debug(context).getInstance().getLogger(instance); resetInitialSettings(); eventBus.on(Events.DATA_UPDATE_COMPLETED, _onDataUpdateCompleted, instance, { priority: EventBus.EVENT_PRIORITY_HIGH }); // High priority to be notified before Stream eventBus.on(Events.INIT_FRAGMENT_NEEDED, _onInitFragmentNeeded, instance); eventBus.on(Events.MEDIA_FRAGMENT_NEEDED, _onMediaFragmentNeeded, instance); eventBus.on(Events.INIT_FRAGMENT_LOADED, _onInitFragmentLoaded, instance); eventBus.on(Events.MEDIA_FRAGMENT_LOADED, _onMediaFragmentLoaded, instance); eventBus.on(Events.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance); eventBus.on(Events.BUFFER_CLEARED, _onBufferCleared, instance); eventBus.on(Events.SEEK_TARGET, _onSeekTarget, instance); eventBus.on(Events.FRAGMENT_LOADING_ABANDONED, _onFragmentLoadingAbandoned, instance); eventBus.on(Events.FRAGMENT_LOADING_COMPLETED, _onFragmentLoadingCompleted, instance); eventBus.on(Events.QUOTA_EXCEEDED, _onQuotaExceeded, instance); eventBus.on(Events.SET_FRAGMENTED_TEXT_AFTER_DISABLED, _onSetFragmentedTextAfterDisabled, instance); eventBus.on(Events.SET_NON_FRAGMENTED_TEXT, _onSetNonFragmentedText, instance); eventBus.on(Events.SOURCE_BUFFER_ERROR, _onSourceBufferError, instance); eventBus.on(Events.BYTES_APPENDED_END_FRAGMENT, _onBytesAppended, instance); } function initialize(mediaSource, hasVideoTrack, isFragmented) { segmentsController = SegmentsController(context).create({ events: Events, eventBus, streamInfo, timelineConverter, dashConstants: DashConstants, segmentBaseController: config.segmentBaseController, type }); dashHandler = DashHandler(context).create({ streamInfo, type, timelineConverter, dashMetrics, mediaPlayerModel, baseURLController: config.baseURLController, errHandler, segmentsController, settings, boxParser, events: Events, eventBus, errors: Errors, debug: Debug(context).getInstance(), requestModifier: RequestModifier(context).getInstance(), dashConstants: DashConstants, constants: Constants, urlUtils: URLUtils(context).getInstance() }); isDynamic = streamInfo.manifestInfo.isDynamic; // Create/initialize controllers dashHandler.initialize(isDynamic); abrController.registerStreamType(type, instance); representationController = RepresentationController(context).create({ streamInfo, type, abrController, dashMetrics, playbackController, timelineConverter, dashConstants: DashConstants, events: Events, eventBus, errors: Errors, isDynamic, adapter, segmentsController }); bufferController = _createBufferControllerForType(type, isFragmented); if (bufferController) { bufferController.initialize(mediaSource); } scheduleController = ScheduleController(context).create({ streamInfo, type, mimeType, adapter, dashMetrics, mediaPlayerModel, fragmentModel, abrController, playbackController, textController, mediaController, bufferController, representationController, settings }); scheduleController.initialize(hasVideoTrack); bufferingTime = 0; shouldUseExplicitTimeForRequest = false; shouldRepeatRequest = false; } function getStreamId() { return streamInfo.id; } function getType() { return type; } function getIsTextTrack() { return adapter.getIsTextTrack(representationController.getData()); } function resetInitialSettings() { mediaInfoArr = []; mediaInfo = null; bufferingTime = 0; shouldUseExplicitTimeForRequest = false; shouldRepeatRequest = false; qualityChangeInProgress = false; pendingSwitchToRepresentationInfo = null; } function reset(errored, keepBuffers) { if (dashHandler) { dashHandler.reset(); } if (bufferController) { bufferController.reset(errored, keepBuffers); bufferController = null; } if (scheduleController) { scheduleController.reset(); scheduleController = null; } if (representationController) { representationController.reset(); representationController = null; } if (segmentsController) { segmentsController = null; } if (abrController) { abrController.unRegisterStreamType(getStreamId(), type); } eventBus.off(Events.DATA_UPDATE_COMPLETED, _onDataUpdateCompleted, instance); eventBus.off(Events.INIT_FRAGMENT_NEEDED, _onInitFragmentNeeded, instance); eventBus.off(Events.MEDIA_FRAGMENT_NEEDED, _onMediaFragmentNeeded, instance); eventBus.off(Events.INIT_FRAGMENT_LOADED, _onInitFragmentLoaded, instance); eventBus.off(Events.MEDIA_FRAGMENT_LOADED, _onMediaFragmentLoaded, instance); eventBus.off(Events.BUFFER_LEVEL_STATE_CHANGED, _onBufferLevelStateChanged, instance); eventBus.off(Events.BUFFER_CLEARED, _onBufferCleared, instance); eventBus.off(Events.SEEK_TARGET, _onSeekTarget, instance); eventBus.off(Events.FRAGMENT_LOADING_ABANDONED, _onFragmentLoadingAbandoned, instance); eventBus.off(Events.FRAGMENT_LOADING_COMPLETED, _onFragmentLoadingCompleted, instance); eventBus.off(Events.SET_FRAGMENTED_TEXT_AFTER_DISABLED, _onSetFragmentedTextAfterDisabled, instance); eventBus.off(Events.SET_NON_FRAGMENTED_TEXT, _onSetNonFragmentedText, instance); eventBus.off(Events.QUOTA_EXCEEDED, _onQuotaExceeded, instance); eventBus.off(Events.SOURCE_BUFFER_ERROR, _onSourceBufferError, instance); eventBus.off(Events.BYTES_APPENDED_END_FRAGMENT, _onBytesAppended, instance); resetInitialSettings(); type = null; streamInfo = null; } function isUpdating() { return representationController ? representationController.isUpdating() : false; } /** * When a seek within the corresponding period occurs this function initiates the clearing of the buffer and sets the correct buffering time. * @param {object} e * @returns {Promise&lt;any>} */ function prepareInnerPeriodPlaybackSeeking(e) { return new Promise((resolve) => { // If we seek to a buffered area we can keep requesting where we left before the seek // If we seek back then forwards buffering will stop until we are below our buffer goal // If we seek forwards then pruneBuffer() will make sure that the bufferToKeep setting is respected const hasBufferAtTargetTime = bufferController.hasBufferAtTime(e.seekTime); if (hasBufferAtTargetTime) { bufferController.pruneBuffer(); const continuousBufferTime = bufferController.getContinuousBufferTimeForTargetTime(e.seekTime); if (_shouldSetBufferingComplete(continuousBufferTime)) { bufferController.setIsBufferingCompleted(true); } resolve(); return; } // Stop segment requests until we have figured out for which time we need to request a segment. We don't want to replace existing segments. scheduleController.clearScheduleTimer(); fragmentModel.abortRequests(); // Abort operations to the SourceBuffer Sink and reset the BufferControllers isBufferingCompleted state. bufferController.prepareForPlaybackSeek() .then(() => { // Clear the buffer. We need to prune everything which is not in the target interval. const clearRanges = bufferController.getAllRangesWithSafetyFactor(e.seekTime); // When everything has been pruned go on return bufferController.clearBuffers(clearRanges); }) .then(() => { // Figure out the correct segment request time. const continuousBufferTime = bufferController.getContinuousBufferTimeForTargetTime(e.seekTime); // If the buffer is continuous and exceeds the duration of the period we are still done buffering. We need to trigger the buffering completed event in order to start prebuffering upcoming periods again if (_shouldSetBufferingComplete(continuousBufferTime)) { bufferController.setIsBufferingCompleted(true); resolve(); } else { const targetTime = isNaN(continuousBufferTime) ? e.seekTime : continuousBufferTime; setExplicitBufferingTime(targetTime); bufferController.setSeekTarget(targetTime); const promises = []; // append window has been reset by abort() operation. Set the correct values again promises.push(bufferController.updateAppendWindow()); // Timestamp offset couldve been changed by preloading period const representationInfo = getRepresentationInfo(); promises.push(bufferController.updateBufferTimestampOffset(representationInfo)); Promise.all(promises) .then(() => { // We might have aborted the append operation of an init segment. Append init segment again. scheduleController.setInitSegmentRequired(true); // Right after a seek we should not immediately check the playback quality scheduleController.setCheckPlaybackQuality(false); scheduleController.startScheduleTimer(); resolve(); }); } }) .catch((e) => { logger.error(e); }); }) } function _shouldSetBufferingComplete(continuousBufferTime) { return !isNaN(continuousBufferTime) &amp;&amp; !isNaN(streamInfo.duration) &amp;&amp; isFinite(streamInfo.duration) &amp;&amp; continuousBufferTime >= streamInfo.start + streamInfo.duration } /** * Seek outside of the current period. * @return {Promise&lt;unknown>} */ function prepareOuterPeriodPlaybackSeeking() { return new Promise((resolve, reject) => { try { // Stop scheduling scheduleController.clearScheduleTimer(); // Abort all ongoing requests fragmentModel.abortRequests(); // buffering not complete anymore and abort current append operation to SourceBuffer bufferController.prepareForPlaybackSeek() .then(() => { // Clear the buffers completely. return bufferController.pruneAllSafely(); }) .then(() => { resolve(); }); } catch (e) { reject(e); } }); } /** * ScheduleController indicates that an init segment needs to be fetched. * @param {object} e * @param {boolean} rescheduleIfNoRequest - Defines whether we reschedule in case no valid request could be generated * @private */ function _onInitFragmentNeeded(e, rescheduleIfNoRequest = true) { // Event propagation may have been stopped (see MssHandler) if (!e.sender) return; if (playbackController.getIsManifestUpdateInProgress()) { _noValidRequest(); return; } if (getIsTextTrack() &amp;&amp; !textController.isTextEnabled()) return; if (bufferController &amp;&amp; e.representationId) { if (!bufferController.appendInitSegmentFromCache(e.representationId)) { const rep = representationController.getCurrentRepresentation(); // Dummy init segment (fragmented tracks without initialization segment) if (rep.range === 0) { _onMediaFragmentNeeded(); return; } // Init segment not in cache, send new request const request = dashHandler ? dashHandler.getInitRequest(mediaInfo, rep) : null; if (request) { fragmentModel.executeRequest(request); } else if (rescheduleIfNoRequest) { scheduleController.setInitSegmentRequired(true); _noValidRequest(); } } } } /** * ScheduleController indicates that a media segment is needed * @param {object} e * @param {boolean} rescheduleIfNoRequest - Defines whether we reschedule in case no valid request could be generated * @private */ function _onMediaFragmentNeeded(e, rescheduleIfNoRequest = true) { // Don't schedule next fragments while updating manifest or pruning to avoid buffer inconsistencies if (playbackController.getIsManifestUpdateInProgress() || bufferController.getIsPruningInProgress()) { _noValidRequest(); return; } let request = _getFragmentRequest(); if (request) { shouldUseExplicitTimeForRequest = false; shouldRepeatRequest = false; _mediaRequestGenerated(request); } else { _noMediaRequestGenerated(rescheduleIfNoRequest); } } /** * If we generated a valid media request we can execute the request. In some cases the segment might be blacklisted. * @param {object} request * @private */ function _mediaRequestGenerated(request) { if (!isNaN(request.startTime + request.duration)) { bufferingTime = request.startTime + request.duration; } request.delayLoadingTime = new Date().getTime() + scheduleController.getTimeToLoadDelay(); scheduleController.setTimeToLoadDelay(0); if (!_shouldIgnoreRequest(request)) { logger.debug(`Next fragment request url for stream id ${streamInfo.id} and media type ${type} is ${request.url}`); fragmentModel.executeRequest(request); } else { logger.warn(`Fragment request url ${request.url} for stream id ${streamInfo.id} and media type ${type} is on the ignore list and will be skipped`); _noValidRequest(); } } /** * We could not generate a valid request. Check if the media is finished, we are stuck in a gap or simply need to wait for the next segment to be available. * @param {boolean} rescheduleIfNoRequest * @private */ function _noMediaRequestGenerated(rescheduleIfNoRequest) { const representation = representationController.getCurrentRepresentation(); // If this statement is true we might be stuck. A static manifest does not change and we did not find a valid request for the target time // There is no point in trying again. We need to adjust the time in order to find a valid request. This can happen if the user/app seeked into a gap. // For dynamic manifests this can also happen especially if we jump over the gap in the previous period and are using SegmentTimeline and in case there is a positive eptDelta at the beginning of the period we are stuck. if (settings.get().streaming.gaps.enableSeekFix &amp;&amp; (shouldUseExplicitTimeForRequest || playbackController.getTime() === 0)) { let adjustedTime; if (!isDynamic) { adjustedTime = dashHandler.getValidTimeAheadOfTargetTime(bufferingTime, mediaInfo, representation, settings.get().streaming.gaps.threshold); } else if (isDynamic &amp;&amp; representation.segmentInfoType === DashConstants.SEGMENT_TIMELINE) { // If we find a valid request ahead of the current time then we are in a gap. Segments are only added at the end of the timeline adjustedTime = dashHandler.getValidTimeAheadOfTargetTime(bufferingTime, mediaInfo, representation, settings.get().streaming.gaps.threshold,); } if (!isNaN(adjustedTime) &amp;&amp; adjustedTime !== bufferingTime) { if (playbackController.isSeeking() || playbackController.getTime() === 0) { // If we are seeking then playback is stalled. Do a seek to get out of this situation logger.warn(`Adjusting playback time ${adjustedTime} because of gap in the manifest. Seeking by ${adjustedTime - bufferingTime}`); playbackController.seek(adjustedTime, false, false); } else { // If we are not seeking we should still be playing but we cant find anything to buffer. So we adjust the buffering time and leave the gap jump to the GapController logger.warn(`Adjusting buffering time ${adjustedTime} because of gap in the manifest. Adjusting time by ${adjustedTime - bufferingTime}`); setExplicitBufferingTime(adjustedTime) if (rescheduleIfNoRequest) { _noValidRequest(); } } return; } } // Check if the media is finished. If so, no need to schedule another request const isLastSegmentRequested = dashHandler.isLastSegmentRequested(representation, bufferingTime); if (isLastSegmentRequested) { const segmentIndex = dashHandler.getCurrentIndex(); logger.debug(`Segment requesting for stream ${streamInfo.id} has finished`); eventBus.trigger(Events.STREAM_REQUESTING_COMPLETED, { segmentIndex }, { streamId: streamInfo.id, mediaType: type }); bufferController.segmentRequestingCompleted(segmentIndex); scheduleController.clearScheduleTimer(); return; } if (rescheduleIfNoRequest) { _noValidRequest(); } } /** * In certain situations we need to ignore a request. For instance, if a segment is blacklisted because it caused an MSE error. * @private */ function _shouldIgnoreRequest(request) { let blacklistUrl = request.url; if (request.range) { blacklistUrl = blacklistUrl.concat('_', request.range); } return segmentBlacklistController.contains(blacklistUrl) } /** * Get the init or media segment request using the DashHandler. * @return {null|FragmentRequest|null} * @private */ function _getFragmentRequest() { const representationInfo = getRepresentationInfo(); let request; if (isNaN(bufferingTime) || (getType() === Constants.TEXT &amp;&amp; !textController.isTextEnabled())) { return null; } if (dashHandler) { const representation = representationController &amp;&amp; representationInfo ? representationController.getRepresentationForQuality(representationInfo.quality) : null; if (shouldUseExplicitTimeForRequest) { request = dashHandler.getSegmentRequestForTime(mediaInfo, representation, bufferingTime); } else if (shouldRepeatRequest) { request = dashHandler.repeatSegmentRequest(mediaInfo, representation); } else { request = dashHandler.getNextSegmentRequest(mediaInfo, representation); } } return request; } /** * Whenever we can not generate a valid request we restart scheduling according to the timeouts defined in the settings. * @private */ function _noValidRequest() { scheduleController.startScheduleTimer(playbackController.getLowLatencyModeEnabled() ? settings.get().streaming.scheduling.lowLatencyTimeout : settings.get().streaming.scheduling.defaultTimeout); } function _onDataUpdateCompleted(e) { if (!e.error) { if (!bufferController.getIsBufferingCompleted()) { bufferController.updateBufferTimestampOffset(e.currentRepresentation); } } } function _onBufferLevelStateChanged(e) { dashMetrics.addBufferState(type, e.state, scheduleController.getBufferTarget()); if (e.state === MetricsConstants.BUFFER_EMPTY &amp;&amp; !playbackController.isSeeking()) { logger.info('Buffer is empty! Stalling!'); dashMetrics.pushPlayListTraceMetrics(new Date(), PlayListTrace.REBUFFERING_REASON); } } function _onBufferCleared(e) { // Remove executed requests not buffered anymore fragmentModel.syncExecutedRequestsWithBufferedRange( bufferController.getBuffer().getAllBufferRanges(), streamInfo.duration); // If buffer removed ahead current time (QuotaExceededError or automatic buffer pruning) then adjust current index handler time if (e.quotaExceeded &amp;&amp; e.from > playbackController.getTime()) { setExplicitBufferingTime(e.from); } // (Re)start schedule once buffer has been pruned after a QuotaExceededError if (e.hasEnoughSpaceToAppend &amp;&amp; e.quotaExceeded) { scheduleController.startScheduleTimer(); } } /** * This function is called when the corresponding SourceBuffer encountered an error. * We blacklist the last segment assuming it caused the error * @param {object} e * @private */ function _onSourceBufferError(e) { if (!e || !e.lastRequestAppended || !e.lastRequestAppended.url) { return; } let blacklistUrl = e.lastRequestAppended.url; if (e.lastRequestAppended.range) { blacklistUrl = blacklistUrl.concat('_', e.lastRequestAppended.range); } logger.warn(`Blacklisting segment with url ${blacklistUrl}`); segmentBlacklistController.add(blacklistUrl); } function _onBytesAppended(e) { logger.debug(`Appended bytes for ${e.mediaType} and stream id ${e.streamId}`); // we save the last initialized quality. That way we make sure that the media fragments we are about to append match the init segment if (e.segmentType === HTTPRequest.INIT_SEGMENT_TYPE) { const lastInitializedQuality = e.quality; scheduleController.setLastInitializedQuality(lastInitializedQuality); logger.info('[' + type + '] ' + 'lastInitializedRepresentationInfo changed to ' + e.quality); } if (pendingSwitchToRepresentationInfo) { _prepareForDefaultQualitySwitch(pendingSwitchToRepresentationInfo) } else { scheduleController.startScheduleTimer(0); } } /** * The quality has changed which means we have switched to a different representation. * If we want to aggressively replace existing parts in the buffer we need to make sure that the new quality is higher than the already buffered one. * @param {object} e */ function prepareQualityChange(e) { if (pendingSwitchToRepresentationInfo) { logger.warn(`Canceling queued representation switch to ${pendingSwitchToRepresentationInfo.quality} for ${type}`); } logger.debug(`Preparing quality switch for type ${type}`); const newQuality = e.newQuality; qualityChangeInProgress = true; // Stop scheduling until we are done with preparing the quality switch scheduleController.clearScheduleTimer(); representationController.prepareQualityChange(newQuality); const representationInfo = getRepresentationInfo(newQuality); // If the switch should occur immediately we need to replace existing stuff in the buffer if (e.reason &amp;&amp; e.reason.forceReplace) { _prepareForForceReplacementQualitySwitch(representationInfo); } // We abandoned a current request else if (e &amp;&amp; e.reason &amp;&amp; e.reason.forceAbandon) { _prepareForAbandonQualitySwitch(representationInfo) } // If fast switch is enabled we check if we are supposed to replace existing stuff in the buffer else if (settings.get().streaming.buffer.fastSwitchEnabled) { _prepareForFastQualitySwitch(representationInfo); } // Default quality switch. We append the new quality to the already