# Source: streaming/Stream.js

    /**
     * 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 StreamProcessor from './StreamProcessor';
    import FragmentController from './controllers/FragmentController';
    import ThumbnailController from './thumbnail/ThumbnailController';
    import EventBus from '../core/EventBus';
    import Events from '../core/events/Events';
    import Debug from '../core/Debug';
    import Errors from '../core/errors/Errors';
    import FactoryMaker from '../core/FactoryMaker';
    import DashJSError from './vo/DashJSError';
    import BoxParser from './utils/BoxParser';
    import URLUtils from './utils/URLUtils';
    import BlacklistController from './controllers/BlacklistController';

    const MEDIA_TYPES = [Constants.VIDEO, Constants.AUDIO, Constants.TEXT, Constants.MUXED, Constants.IMAGE];

    function Stream(config) {

        config = config || {};
        const context = this.context;
        const eventBus = EventBus(context).getInstance();
        const urlUtils = URLUtils(context).getInstance();

        const manifestModel = config.manifestModel;
        const mediaPlayerModel = config.mediaPlayerModel;
        const dashMetrics = config.dashMetrics;
        const manifestUpdater = config.manifestUpdater;
        const adapter = config.adapter;
        const timelineConverter = config.timelineConverter;
        const capabilities = config.capabilities;
        const errHandler = config.errHandler;
        const abrController = config.abrController;
        const playbackController = config.playbackController;
        const eventController = config.eventController;
        const mediaController = config.mediaController;
        const protectionController = config.protectionController;
        const textController = config.textController;
        const videoModel = config.videoModel;
        let streamInfo = config.streamInfo;
        const settings = config.settings;

        let instance,
            logger,
            streamProcessors,
            isInitialized,
            isActive,
            hasFinishedBuffering,
            hasVideoTrack,
            hasAudioTrack,
            updateError,
            isUpdating,
            fragmentController,
            thumbnailController,
            segmentBlacklistController,
            preloaded,
            boxParser,
            debug,
            isEndedEventSignaled,
            trackChangedEvents;

        /**
         * Setup the stream
         */
        function setup() {
            try {
                debug = Debug(context).getInstance();
                logger = debug.getLogger(instance);
                resetInitialSettings();

                boxParser = BoxParser(context).getInstance();

                segmentBlacklistController = BlacklistController(context).create({
                    updateEventName: Events.SEGMENT_LOCATION_BLACKLIST_CHANGED,
                    addBlacklistEventName: Events.SEGMENT_LOCATION_BLACKLIST_ADD
                });

                fragmentController = FragmentController(context).create({
                    streamInfo: streamInfo,
                    mediaPlayerModel: mediaPlayerModel,
                    dashMetrics: dashMetrics,
                    errHandler: errHandler,
                    settings: settings,
                    boxParser: boxParser,
                    dashConstants: DashConstants,
                    urlUtils: urlUtils
                });

            } catch (e) {
                throw e;
            }
        }

        /**
         * Initialize the events
         */
        function initialize() {
            registerEvents();
            registerProtectionEvents();
            textController.initializeForStream(streamInfo);
            eventBus.trigger(Events.STREAM_UPDATED, { streamInfo: streamInfo });
        }

        /**
         * Register the streaming events
         */
        function registerEvents() {
            eventBus.on(Events.BUFFERING_COMPLETED, onBufferingCompleted, instance);
            eventBus.on(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance);
            eventBus.on(Events.INBAND_EVENTS, onInbandEvents, instance);
        }

        /**
         * Unregister the streaming events
         */
        function unRegisterEvents() {
            eventBus.off(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance);
            eventBus.off(Events.BUFFERING_COMPLETED, onBufferingCompleted, instance);
            eventBus.off(Events.INBAND_EVENTS, onInbandEvents, instance);
        }

        /**
         * Register the protection events
         */
        function registerProtectionEvents() {
            if (protectionController) {
                eventBus.on(Events.KEY_ERROR, onProtectionError, instance);
                eventBus.on(Events.SERVER_CERTIFICATE_UPDATED, onProtectionError, instance);
                eventBus.on(Events.LICENSE_REQUEST_COMPLETE, onProtectionError, instance);
                eventBus.on(Events.KEY_SYSTEM_SELECTED, onProtectionError, instance);
                eventBus.on(Events.KEY_SESSION_CREATED, onProtectionError, instance);
                eventBus.on(Events.KEY_STATUSES_CHANGED, onProtectionError, instance);
            }
        }

        /**
         * Unregister the protection events
         */
        function unRegisterProtectionEvents() {
            if (protectionController) {
                eventBus.off(Events.KEY_ERROR, onProtectionError, instance);
                eventBus.off(Events.SERVER_CERTIFICATE_UPDATED, onProtectionError, instance);
                eventBus.off(Events.LICENSE_REQUEST_COMPLETE, onProtectionError, instance);
                eventBus.off(Events.KEY_SYSTEM_SELECTED, onProtectionError, instance);
                eventBus.off(Events.KEY_SESSION_CREATED, onProtectionError, instance);
                eventBus.off(Events.KEY_STATUSES_CHANGED, onProtectionError, instance);
            }
        }

        /**
         * Returns the stream id
         * @return {*|null}
         */
        function getStreamId() {
            return streamInfo ? streamInfo.id : null;
        }

        /**
         * Activates Stream by re-initializing some of its components
         * @param {MediaSource} mediaSource
         * @param {array} previousBufferSinks
         * @memberof Stream#
         */
        function activate(mediaSource, previousBufferSinks) {
            return new Promise((resolve, reject) => {
                if (isActive) {
                    resolve(previousBufferSinks);
                    return;
                }

                if (getPreloaded()) {
                    isActive = true;
                    eventBus.trigger(Events.STREAM_ACTIVATED, {
                        streamInfo
                    });
                    resolve(previousBufferSinks);
                    return;
                }

                _initializeMedia(mediaSource, previousBufferSinks)
                    .then((bufferSinks) => {
                        isActive = true;
                        eventBus.trigger(Events.STREAM_ACTIVATED, {
                            streamInfo
                        });
                        resolve(bufferSinks);
                    })
                    .catch((e) => {
                        reject(e);
                    });
            });
        }

        function startPreloading(mediaSource, previousBuffers) {
            return new Promise((resolve, reject) => {

                if (getPreloaded()) {
                    reject();
                    return;
                }

                logger.info(`[startPreloading] Preloading next stream with id ${getId()}`);
                setPreloaded(true);

                _commonMediaInitialization(mediaSource, previousBuffers)
                    .then(() => {
                        for (let i = 0; i < streamProcessors.length && streamProcessors[i]; i++) {
                            streamProcessors[i].setExplicitBufferingTime(getStartTime());
                            streamProcessors[i].getScheduleController().startScheduleTimer();
                        }
                        resolve();
                    })
                    .catch(() => {
                        setPreloaded(false);
                        reject();
                    });
            });
        }

        /**
         *
         * @param {object} mediaSource
         * @param {array} previousBufferSinks
         * @return {Promise<Array>}
         * @private
         */
        function _initializeMedia(mediaSource, previousBufferSinks) {
            return _commonMediaInitialization(mediaSource, previousBufferSinks);
        }

        /**
         *
         * @param {object} mediaSource
         * @param {array} previousBufferSinks
         * @return {Promise<array>}
         * @private
         */
        function _commonMediaInitialization(mediaSource, previousBufferSinks) {
            return new Promise((resolve, reject) => {
                checkConfig();

                isUpdating = true;
                _addInlineEvents();

                let element = videoModel.getElement();
                const promises = [];
                MEDIA_TYPES.forEach((mediaType) => {
                    // If we are preloading without a video element we can not start texttrack handling.
                    if (!(mediaType === Constants.TEXT && !mediaSource) && (mediaType !== Constants.VIDEO || (!element || (element && (/^VIDEO$/i).test(element.nodeName))))) {
                        promises.push(_initializeMediaForType(mediaType, mediaSource));
                    }
                });

                Promise.all(promises)
                    .then(() => {
                        return _createBufferSinks(previousBufferSinks)
                    })
                    .then((bufferSinks) => {
                        isUpdating = false;

                        if (streamProcessors.length === 0) {
                            const msg = 'No streams to play.';
                            errHandler.error(new DashJSError(Errors.MANIFEST_ERROR_ID_NOSTREAMS_CODE, msg, manifestModel.getValue()));
                            logger.fatal(msg);
                        } else {
                            _checkIfInitializationCompleted();
                        }

                        if (mediaSource) {
                            // All mediaInfos for texttracks are added to the TextSourceBuffer by now. We can start creating the tracks
                            textController.createTracks(streamInfo);
                        }

                        resolve(bufferSinks);
                    })
                    .catch((e) => {
                        reject(e);
                    });
            });
        }

        /**
         * We call this function if segments have been preloaded without a video element. Once the video element is attached MSE is available
         * @param mediaSource
         * @returns {Promise<unknown>}
         */
        function initializeForTextWithMediaSource(mediaSource) {
            return new Promise((resolve, reject) => {
                _initializeMediaForType(Constants.TEXT, mediaSource)
                    .then(() => {
                        return createBufferSinkForText()
                    })
                    .then(() => {
                        textController.createTracks(streamInfo);
                        resolve()
                    })
                    .catch((e) => {
                        reject(e);
                    })
            })
        }

        /**
         * Initialize for a given media type. Creates a corresponding StreamProcessor
         * @param {string} type
         * @param {object} mediaSource
         * @private
         */
        function _initializeMediaForType(type, mediaSource) {
            let allMediaForType = adapter.getAllMediaInfoForType(streamInfo, type);
            let embeddedMediaInfos = [];

            let mediaInfo = null;
            let initialMediaInfo;

            if (!allMediaForType || allMediaForType.length === 0) {
                logger.info('No ' + type + ' data.');
                return Promise.resolve();
            }

            if (type === Constants.VIDEO) {
                hasVideoTrack = true;
            }

            if (type === Constants.AUDIO) {
                hasAudioTrack = true;
            }

            for (let i = 0, ln = allMediaForType.length; i < ln; i++) {
                mediaInfo = allMediaForType[i];

                if (type === Constants.TEXT && !!mediaInfo.isEmbedded) {
                    textController.addEmbeddedTrack(streamInfo, mediaInfo);
                    embeddedMediaInfos.push(mediaInfo);
                }
                if (_isMediaSupported(mediaInfo)) {
                    mediaController.addTrack(mediaInfo);
                }
            }

            if (embeddedMediaInfos.length > 0) {
                mediaController.setInitialMediaSettingsForType(type, streamInfo);
                textController.addMediaInfosToBuffer(streamInfo, type, embeddedMediaInfos);
            }

            // Filter out embedded text track before creating StreamProcessor
            allMediaForType = allMediaForType.filter(mediaInfo => {
                return !mediaInfo.isEmbedded;
            });
            if (allMediaForType.length === 0) {
                return Promise.resolve();
            }

            if (type === Constants.IMAGE) {
                thumbnailController = ThumbnailController(context).create({
                    streamInfo: streamInfo,
                    adapter: adapter,
                    baseURLController: config.baseURLController,
                    timelineConverter: config.timelineConverter,
                    debug: debug,
                    eventBus: eventBus,
                    events: Events,
                    dashConstants: DashConstants,
                    dashMetrics: config.dashMetrics,
                    segmentBaseController: config.segmentBaseController
                });
                thumbnailController.initialize();
                return Promise.resolve();
            }

            eventBus.trigger(Events.STREAM_INITIALIZING, {
                streamInfo: streamInfo,
                mediaInfo: mediaInfo
            });

            mediaController.setInitialMediaSettingsForType(type, streamInfo);

            let streamProcessor = _createStreamProcessor(allMediaForType, mediaSource);

            initialMediaInfo = mediaController.getCurrentTrackFor(type, streamInfo.id);

            if (initialMediaInfo) {
                abrController.updateTopQualityIndex(initialMediaInfo);
                // In case of mixed fragmented and embedded text tracks, check if initial selected text track is not an embedded track
                return streamProcessor.selectMediaInfo((type !== Constants.TEXT || !initialMediaInfo.isEmbedded) ? initialMediaInfo : allMediaForType[0]);
            }

            return Promise.resolve();
        }

        function _isMediaSupported(mediaInfo) {
            const type = mediaInfo ? mediaInfo.type : null;
            let msg;

            if (type === Constants.MUXED) {
                msg = 'Multiplexed representations are intentionally not supported, as they are not compliant with the DASH-AVC/264 guidelines';
                logger.fatal(msg);
                errHandler.error(new DashJSError(Errors.MANIFEST_ERROR_ID_MULTIPLEXED_CODE, msg, manifestModel.getValue()));
                return false;
            }

            if (type === Constants.TEXT || type === Constants.IMAGE) {
                return true;
            }

            if (!!mediaInfo.contentProtection && !capabilities.supportsEncryptedMedia()) {
                errHandler.error(new DashJSError(Errors.CAPABILITY_MEDIAKEYS_ERROR_CODE, Errors.CAPABILITY_MEDIAKEYS_ERROR_MESSAGE));
                return false;
            }

            return true;
        }

        /**
         * Creates the StreamProcessor for a given media type.
         * @param {array} allMediaForType
         * @param {object} mediaSource
         * @private
         */
        function _createStreamProcessor(allMediaForType, mediaSource) {

            const mediaInfo = (allMediaForType && allMediaForType.length > 0) ? allMediaForType[0] : null;
            let fragmentModel = fragmentController.getModel(mediaInfo ? mediaInfo.type : null);
            const type = mediaInfo ? mediaInfo.type : null;
            const mimeType = mediaInfo ? mediaInfo.mimeType : null;
            const isFragmented = mediaInfo ? mediaInfo.isFragmented : null;

            let streamProcessor = StreamProcessor(context).create({
                streamInfo,
                type,
                mimeType,
                timelineConverter,
                adapter,
                manifestModel,
                mediaPlayerModel,
                fragmentModel,
                dashMetrics: config.dashMetrics,
                baseURLController: config.baseURLController,
                segmentBaseController: config.segmentBaseController,
                abrController,
                playbackController,
                mediaController,
                textController,
                errHandler,
                settings,
                boxParser,
                segmentBlacklistController
            });

            streamProcessor.initialize(mediaSource, hasVideoTrack, isFragmented);
            streamProcessors.push(streamProcessor);

            for (let i = 0; i < allMediaForType.length; i++) {
                streamProcessor.addMediaInfo(allMediaForType[i]);
            }

            if (type === Constants.TEXT) {
                textController.addMediaInfosToBuffer(streamInfo, type, allMediaForType, fragmentModel);
            }

            return streamProcessor;
        }

        /**
         * Creates the SourceBufferSink objects for all StreamProcessors
         * @param {array} previousBuffersSinks
         * @return {Promise<object>}
         * @private
         */
        function _createBufferSinks(previousBuffersSinks) {
            return new Promise((resolve) => {
                const buffers = {};
                const promises = streamProcessors.map((sp) => {
                    return sp.createBufferSinks(previousBuffersSinks);
                });

                Promise.all(promises)
                    .then((bufferSinks) => {
                        bufferSinks.forEach((sink) => {
                            if (sink) {
                                buffers[sink.getType()] = sink;
                            }
                        });
                        resolve(buffers);
                    })
                    .catch(() => {
                        resolve(buffers);
                    });
            });
        }

        function createBufferSinkForText() {
            const sp = _getProcessorByType(Constants.TEXT);
            if (sp) {
                return sp.createBufferSinks()
            }

            return Promise.resolve();
        }

        /**
         * Partially resets some of the Stream elements. This function is called when preloading of streams is canceled or a stream switch occurs.
         * @memberof Stream#
         * @param {boolean} keepBuffers
         */
        function deactivate(keepBuffers) {
            let ln = streamProcessors ? streamProcessors.length : 0;
            const errored = false;
            for (let i = 0; i < ln; i++) {
                let fragmentModel = streamProcessors[i].getFragmentModel();
                fragmentModel.abortRequests();
                fragmentModel.resetInitialSettings();
                streamProcessors[i].reset(errored, keepBuffers);
            }
            if (textController) {
                textController.deactivateStream(streamInfo);
            }
            streamProcessors = [];
            isActive = false;
            hasFinishedBuffering = false;
            setPreloaded(false);
            setIsEndedEventSignaled(false);
            eventBus.trigger(Events.STREAM_DEACTIVATED, { streamInfo });
        }

        function getIsActive() {
            return isActive;
        }

        function setMediaSource(mediaSource) {
            return new Promise((resolve, reject) => {
                const promises = [];
                for (let i = 0; i < streamProcessors.length;) {
                    if (_isMediaSupported(streamProcessors[i].getMediaInfo())) {
                        promises.push(streamProcessors[i].setMediaSource(mediaSource));
                        i++;
                    } else {
                        streamProcessors[i].reset();
                        streamProcessors.splice(i, 1);
                    }
                }

                Promise.all(promises)
                    .then(() => {
                        for (let i = 0; i < streamProcessors.length; i++) {
                            //Adding of new tracks to a stream processor isn't guaranteed by the spec after the METADATA_LOADED state
                            //so do this after the buffers are created above.
                            streamProcessors[i].dischargePreBuffer();
                        }

                        if (streamProcessors.length === 0) {
                            const msg = 'No streams to play.';
                            errHandler.error(new DashJSError(Errors.MANIFEST_ERROR_ID_NOSTREAMS_CODE, msg + 'nostreams', manifestModel.getValue()));
                            logger.fatal(msg);
                        }
                        resolve();
                    })
                    .catch((e) => {
                        logger.error(e);
                        reject(e);
                    })
            })
        }

        function resetInitialSettings(keepBuffers) {
            deactivate(keepBuffers);
            isInitialized = false;
            hasVideoTrack = false;
            hasAudioTrack = false;
            updateError = {};
            isUpdating = false;
            isEndedEventSignaled = false;
            trackChangedEvents = [];
        }

        function reset(keepBuffers) {

            if (fragmentController) {
                fragmentController.reset();
                fragmentController = null;
            }

            if (abrController && streamInfo) {
                abrController.clearDataForStream(streamInfo.id);
            }

            if (segmentBlacklistController) {
                segmentBlacklistController.reset();
                segmentBlacklistController = null;
            }

            resetInitialSettings(keepBuffers);

            streamInfo = null;

            unRegisterEvents();

            unRegisterProtectionEvents();

        }

        function getDuration() {
            return streamInfo ? streamInfo.duration : NaN;
        }

        function getIsEndedEventSignaled() {
            return isEndedEventSignaled;
        }

        function setIsEndedEventSignaled(value) {
            isEndedEventSignaled = value;
        }

        function getStartTime() {
            return streamInfo ? streamInfo.start : NaN;
        }

        function getId() {
            return streamInfo ? streamInfo.id : null;
        }

        function getStreamInfo() {
            return streamInfo;
        }

        function getHasAudioTrack() {
            return hasAudioTrack;
        }

        function getHasVideoTrack() {
            return hasVideoTrack;
        }

        function getThumbnailController() {
            return thumbnailController;
        }

        function checkConfig() {
            if (!videoModel || !abrController) {
                throw new Error(Constants.MISSING_CONFIG_ERROR);
            }
        }

        /**
         * @param {string} type
         * @returns {Array}
         * @memberof Stream#
         */
        function getBitrateListFor(type) {
            checkConfig();
            if (type === Constants.IMAGE) {
                if (!thumbnailController) {
                    return [];
                }
                return thumbnailController.getBitrateList();
            }
            const mediaInfo = getMediaInfo(type);
            return abrController.getBitrateList(mediaInfo);
        }

        function onProtectionError(event) {
            if (event.error) {
                errHandler.error(event.error);
                logger.fatal(event.error.message);
            }
        }

        function prepareTrackChange(e) {
            if (!isActive || !streamInfo) {
                return;
            }

            hasFinishedBuffering = false;

            let mediaInfo = e.newMediaInfo;
            let manifest = manifestModel.getValue();

            adapter.setCurrentMediaInfo(streamInfo.id, mediaInfo.type, mediaInfo);

            let processor = getProcessorForMediaInfo(mediaInfo);
            if (!processor) return;

            let currentTime = playbackController.getTime();
            logger.info('Stream -  Process track changed at current time ' + currentTime);

            // Applies only for MSS streams
            if (manifest.refreshManifestOnSwitchTrack) {
                trackChangedEvents.push(e);
                if (!manifestUpdater.getIsUpdating()) {
                    logger.debug('Stream -  Refreshing manifest for switch track');
                    manifestUpdater.refreshManifest();
                }
            } else {
                processor.selectMediaInfo(mediaInfo)
                    .then(() => {
                        if (mediaInfo.type === Constants.VIDEO || mediaInfo.type === Constants.AUDIO) {
                            abrController.updateTopQualityIndex(mediaInfo);
                        }
                        processor.prepareTrackSwitch();
                    });
            }
        }

        function prepareQualityChange(e) {
            const processor = _getProcessorByType(e.mediaType);

            if (processor) {
                processor.prepareQualityChange(e);
            }
        }

        function _addInlineEvents() {
            if (eventController) {
                const events = adapter.getEventsFor(streamInfo);
                if (events && events.length > 0) {
                    eventController.addInlineEvents(events, streamInfo.id);
                }
            }
        }

        function _checkIfInitializationCompleted() {
            const ln = streamProcessors.length;
            const hasError = !!updateError.audio || !!updateError.video;
            let error = hasError ? new DashJSError(Errors.DATA_UPDATE_FAILED_ERROR_CODE, Errors.DATA_UPDATE_FAILED_ERROR_MESSAGE) : null;

            for (let i = 0; i < ln; i++) {
                if (streamProcessors[i].isUpdating() || isUpdating) {
                    return;
                }
            }

            if (protectionController) {
                // Need to check if streamProcessors exists because streamProcessors
                // could be cleared in case an error is detected while initializing DRM keysystem
                protectionController.clearMediaInfoArray();
                for (let i = 0; i < ln && streamProcessors[i]; i++) {
                    const type = streamProcessors[i].getType();
                    const mediaInfo = streamProcessors[i].getMediaInfo();
                    if (type === Constants.AUDIO ||
                        type === Constants.VIDEO ||
                        (type === Constants.TEXT && mediaInfo.isFragmented)) {
                        let mediaInfo = streamProcessors[i].getMediaInfo();
                        if (mediaInfo) {
                            protectionController.initializeForMedia(mediaInfo);
                        }
                    }
                }
                protectionController.handleKeySystemFromManifest();
            }

            if (error) {
                errHandler.error(error);
            } else if (!isInitialized) {
                isInitialized = true;
                videoModel.waitForReadyState(Constants.VIDEO_ELEMENT_READY_STATES.HAVE_METADATA, () => {
                    eventBus.trigger(Events.STREAM_INITIALIZED, {
                        streamInfo: streamInfo
                    });
                })
            }

        }

        function getMediaInfo(type) {
            let streamProcessor = null;

            for (let i = 0; i < streamProcessors.length; i++) {
                streamProcessor = streamProcessors[i];

                if (streamProcessor.getType() === type) {
                    return streamProcessor.getMediaInfo();
                }
            }

            return null;
        }

        function onBufferingCompleted() {
            let processors = getProcessors();
            const ln = processors.length;

            if (ln === 0) {
                logger.warn('onBufferingCompleted - can\'t trigger STREAM_BUFFERING_COMPLETED because no streamProcessor is defined');
                return;
            }

            // if there is at least one buffer controller that has not completed buffering yet do nothing
            for (let i = 0; i < ln; i++) {
                //if audio or video buffer is not buffering completed state, do not send STREAM_BUFFERING_COMPLETED
                if (!processors[i].isBufferingCompleted() && (processors[i].getType() === Constants.AUDIO || processors[i].getType() === Constants.VIDEO)) {
                    logger.debug('onBufferingCompleted - One streamProcessor has finished but', processors[i].getType(), 'one is not buffering completed');
                    return;
                }
            }

            logger.debug('onBufferingCompleted - trigger STREAM_BUFFERING_COMPLETED');
            hasFinishedBuffering = true;
            eventBus.trigger(Events.STREAM_BUFFERING_COMPLETED, { streamInfo: streamInfo }, { streamInfo });
        }

        function onDataUpdateCompleted(e) {
            updateError[e.mediaType] = e.error;
            _checkIfInitializationCompleted();
        }

        function onInbandEvents(e) {
            if (eventController) {
                eventController.addInbandEvents(e.events, streamInfo.id);
            }
        }

        function getProcessorForMediaInfo(mediaInfo) {
            if (!mediaInfo || !mediaInfo.type) {
                return null;
            }

            return _getProcessorByType(mediaInfo.type);
        }

        function _getProcessorByType(type) {
            if (!type) {
                return null;
            }

            let processors = getProcessors();

            return processors.filter(function (processor) {
                return (processor.getType() === type);
            })[0];
        }

        function getProcessors() {
            let arr = [];

            let type,
                streamProcessor;

            for (let i = 0; i < streamProcessors.length; i++) {
                streamProcessor = streamProcessors[i];
                type = streamProcessor.getType();

                if (type === Constants.AUDIO || type === Constants.VIDEO || type === Constants.TEXT) {
                    arr.push(streamProcessor);
                }
            }

            return arr;
        }

        function startScheduleControllers() {
            const ln = streamProcessors.length;
            for (let i = 0; i < ln && streamProcessors[i]; i++) {
                streamProcessors[i].getScheduleController().startScheduleTimer();
            }
        }

        function updateData(updatedStreamInfo) {
            return new Promise((resolve) => {
                isUpdating = true;
                streamInfo = updatedStreamInfo;

                if (eventController) {
                    _addInlineEvents();
                }

                let promises = [];
                for (let i = 0, ln = streamProcessors.length; i < ln; i++) {
                    let streamProcessor = streamProcessors[i];
                    const currentMediaInfo = streamProcessor.getMediaInfo();
                    promises.push(streamProcessor.updateStreamInfo(streamInfo));
                    let allMediaForType = adapter.getAllMediaInfoForType(streamInfo, streamProcessor.getType());

                    // Filter out embedded text track before updating media info in  StreamProcessor
                    allMediaForType = allMediaForType.filter(mediaInfo => {
                        return !mediaInfo.isEmbedded;
                    });

                    // Check if AdaptationSet has not been removed in MPD update
                    if (allMediaForType) {
                        // Remove the current mediaInfo objects before adding the updated ones
                        streamProcessor.clearMediaInfoArray();
                        for (let j = 0; j < allMediaForType.length; j++) {
                            const mInfo = allMediaForType[j];
                            streamProcessor.addMediaInfo(allMediaForType[j]);
                            if (adapter.areMediaInfosEqual(currentMediaInfo, mInfo)) {
                                abrController.updateTopQualityIndex(mInfo);
                                promises.push(streamProcessor.selectMediaInfo(mInfo))
                            }
                        }
                    }
                }

                Promise.all(promises)
                    .then(() => {
                        promises = [];

                        while (trackChangedEvents.length > 0) {
                            let trackChangedEvent = trackChangedEvents.pop();
                            let mediaInfo = trackChangedEvent.newMediaInfo;
                            let processor = getProcessorForMediaInfo(trackChangedEvent.oldMediaInfo);
                            if (!processor) return;
                            promises.push(processor.prepareTrackSwitch());
                            promises.push(processor.selectMediaInfo(mediaInfo));
                        }

                        return Promise.all(promises)
                    })
                    .then(() => {
                        isUpdating = false;
                        _checkIfInitializationCompleted();
                        eventBus.trigger(Events.STREAM_UPDATED, { streamInfo: streamInfo });
                        resolve();
                    })

            })
        }

        function isMediaCodecCompatible(newStream, previousStream = null) {
            return compareCodecs(newStream, Constants.VIDEO, previousStream) && compareCodecs(newStream, Constants.AUDIO, previousStream);
        }

        function isProtectionCompatible(newStream) {
            if (!newStream) {
                return true;
            }
            return _compareProtectionConfig(Constants.VIDEO, newStream) && _compareProtectionConfig(Constants.AUDIO, newStream);
        }

        function _compareProtectionConfig(type, newStream) {
            const currentStreamInfo = getStreamInfo();
            const newStreamInfo = newStream.getStreamInfo();

            if (!newStreamInfo || !currentStreamInfo) {
                return true;
            }

            const newAdaptation = adapter.getAdaptationForType(newStreamInfo.index, type, newStreamInfo);
            const currentAdaptation = adapter.getAdaptationForType(currentStreamInfo.index, type, currentStreamInfo);

            if (!newAdaptation || !currentAdaptation) {
                // If there is no adaptation for neither the old or the new stream they're compatible
                return !newAdaptation && !currentAdaptation;
            }

            // If the current period is unencrypted and the upcoming one is encrypted we need to reset sourcebuffers.
            return !(!_isAdaptationDrmProtected(currentAdaptation) && _isAdaptationDrmProtected(newAdaptation));
        }

        function _isAdaptationDrmProtected(adaptation) {

            if (!adaptation) {
                // If there is no adaptation for neither the old or the new stream they're compatible
                return false;
            }

            // If the current period is unencrypted and the upcoming one is encrypted we need to reset sourcebuffers.
            return !!(adaptation.ContentProtection || (adaptation.Representation_asArray && adaptation.Representation_asArray.length > 0 && adaptation.Representation_asArray[0].ContentProtection));
        }

        function compareCodecs(newStream, type, previousStream = null) {
            if (!newStream || !newStream.hasOwnProperty('getStreamInfo')) {
                return false;
            }
            const newStreamInfo = newStream.getStreamInfo();
            const currentStreamInfo = previousStream ? previousStream.getStreamInfo() : getStreamInfo();

            if (!newStreamInfo || !currentStreamInfo) {
                return false;
            }

            const newAdaptation = adapter.getAdaptationForType(newStreamInfo.index, type, newStreamInfo);
            const currentAdaptation = adapter.getAdaptationForType(currentStreamInfo.index, type, currentStreamInfo);

            if (!newAdaptation || !currentAdaptation) {
                // If there is no adaptation for neither the old or the new stream they're compatible
                return !newAdaptation && !currentAdaptation;
            }

            const sameMimeType = newAdaptation && currentAdaptation && newAdaptation.mimeType === currentAdaptation.mimeType;
            const oldCodecs = currentAdaptation.Representation_asArray.map((representation) => {
                return representation.codecs;
            });

            const newCodecs = newAdaptation.Representation_asArray.map((representation) => {
                return representation.codecs;
            });

            const codecMatch = newCodecs.some((newCodec) => {
                return oldCodecs.indexOf(newCodec) > -1;
            });

            const partialCodecMatch = newCodecs.some((newCodec) => oldCodecs.some((oldCodec) => capabilities.codecRootCompatibleWithCodec(oldCodec, newCodec)));
            return codecMatch || (partialCodecMatch && sameMimeType);
        }

        function setPreloaded(value) {
            preloaded = value;
        }

        function getPreloaded() {
            return preloaded;
        }

        function getHasFinishedBuffering() {
            return hasFinishedBuffering;
        }

        function getAdapter() {
            return adapter;
        }

        instance = {
            initialize,
            getStreamId,
            activate,
            deactivate,
            getIsActive,
            getDuration,
            getStartTime,
            getId,
            getStreamInfo,
            getHasAudioTrack,
            getHasVideoTrack,
            startPreloading,
            initializeForTextWithMediaSource,
            getThumbnailController,
            getBitrateListFor,
            updateData,
            reset,
            getProcessors,
            setMediaSource,
            isMediaCodecCompatible,
            isProtectionCompatible,
            getPreloaded,
            getIsEndedEventSignaled,
            setIsEndedEventSignaled,
            getAdapter,
            getHasFinishedBuffering,
            setPreloaded,
            startScheduleControllers,
            prepareTrackChange,
            prepareQualityChange
        };

        setup();
        return instance;
    }

    Stream.__dashjs_factory_name = 'Stream';
    export default FactoryMaker.getClassFactory(Stream);

#### Search results