# Source: dash/controllers/ServiceDescriptionController.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 FactoryMaker from '../../core/FactoryMaker';
    import Debug from '../../core/Debug';
    import Constants from '../../streaming/constants/Constants';
    import DashConstants from '../constants/DashConstants';

    const SUPPORTED_SCHEMES = [Constants.SERVICE_DESCRIPTION_DVB_LL_SCHEME];
    const MEDIA_TYPES = {
        VIDEO: 'video',
        AUDIO: 'audio',
        ANY: 'any',
        ALL: 'all'
    }

    function ServiceDescriptionController() {
        const context = this.context;

        let instance,
            serviceDescriptionSettings,
            prftOffsets,
            logger,
            adapter;

        function setup() {
            logger = Debug(context).getInstance().getLogger(instance);
            _resetInitialSettings();
        }

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

            if (config.adapter) {
                adapter = config.adapter;
            }
        }

        function reset() {
            _resetInitialSettings();
        }

        function _resetInitialSettings() {
            serviceDescriptionSettings = {
                liveDelay: NaN,
                liveCatchup: {
                    maxDrift: NaN,
                    playbackRate: {
                        min: NaN,
                        max: NaN
                    },
                },
                minBitrate: {},
                maxBitrate: {},
                initialBitrate: {},
                contentSteering: null
            };
            prftOffsets = [];
        }

        /**
         * Returns the service description settings for latency, catchup and bandwidth
         */
        function getServiceDescriptionSettings() {
            return serviceDescriptionSettings
        }

        /**
         * Check for potential ServiceDescriptor elements in the MPD and update the settings accordingly
         * @param {object} manifestInfo
         * @private
         */
        function applyServiceDescription(manifestInfo) {
            if (!manifestInfo || !manifestInfo.serviceDescriptions) {
                return;
            }

            const supportedServiceDescriptions = manifestInfo.serviceDescriptions.filter(sd => SUPPORTED_SCHEMES.includes(sd.schemeIdUri));
            const allClientsServiceDescriptions = manifestInfo.serviceDescriptions.filter(sd => sd.schemeIdUri == null);
            let sd = (supportedServiceDescriptions.length > 0)
                ? supportedServiceDescriptions[supportedServiceDescriptions.length - 1]
                : allClientsServiceDescriptions[allClientsServiceDescriptions.length - 1];
            if (!sd) return;

            if (sd.latency && sd.latency.target > 0) {
                _applyServiceDescriptionLatency(sd);
            }

            if (sd.playbackRate) {
                _applyServiceDescriptionPlaybackRate(sd);
            }

            if (sd.operatingQuality) {
                _applyServiceDescriptionOperatingQuality(sd);
            }

            if (sd.operatingBandwidth) {
                _applyServiceDescriptionOperatingBandwidth(sd);
            }

            if (sd.contentSteering) {
                _applyServiceDescriptionContentSteering(sd);
            }
        }

        /**
         * Adjust the latency targets for the service.
         * @param {object} sd - service description element
         * @private
         */
        function _applyServiceDescriptionLatency(sd) {
            let params;

            if (sd.schemeIdUri === Constants.SERVICE_DESCRIPTION_DVB_LL_SCHEME) {
                params = _getDvbServiceDescriptionLatencyParameters(sd);
            } else {
                params = _getStandardServiceDescriptionLatencyParameters(sd);
            }

            if (prftOffsets.length > 0) {
                let { to, id } = _calculateTimeOffset(params);

                // TS 103 285 Clause 10.20.4. 3) Subtract calculated offset from Latency@target converted from milliseconds
                // liveLatency does not consider ST@availabilityTimeOffset so leave out that step
                // Since maxDrift is a difference rather than absolute it does not need offset applied
                serviceDescriptionSettings.liveDelay = params.liveDelay - to;
                serviceDescriptionSettings.liveCatchup.maxDrift = params.maxDrift;

                logger.debug(`
                    Found latency properties coming from service description. Applied time offset of ${to} from ProducerReferenceTime element with id ${id}.
                    Live Delay: ${params.liveDelay - to}, Live catchup max drift: ${params.maxDrift}
                `);
            } else {
                serviceDescriptionSettings.liveDelay = params.liveDelay;
                serviceDescriptionSettings.liveCatchup.maxDrift = params.maxDrift;

                logger.debug(`Found latency properties coming from service description: Live Delay: ${params.liveDelay}, Live catchup max drift: ${params.maxDrift}`);
            }
        }

        /**
         * Get default parameters for liveDelay,maxDrift
         * @param {object} sd
         * @return {{maxDrift: (number|undefined), liveDelay: number, referenceId: (number|undefined)}}
         * @private
         */
        function _getStandardServiceDescriptionLatencyParameters(sd) {
            const liveDelay = sd.latency.target / 1000;
            let maxDrift = !isNaN(sd.latency.max) && sd.latency.max > sd.latency.target ? (sd.latency.max - sd.latency.target + 500) / 1000 : NaN;
            const referenceId = sd.latency.referenceId || NaN;

            return {
                liveDelay,
                maxDrift,
                referenceId
            }
        }

        /**
         * Get DVB DASH parameters for liveDelay,maxDrift
         * @param sd
         * @return {{maxDrift: (number|undefined), liveDelay: number, referenceId: (number|undefined)}}
         * @private
         */
        function _getDvbServiceDescriptionLatencyParameters(sd) {
            const liveDelay = sd.latency.target / 1000;
            let maxDrift = !isNaN(sd.latency.max) && sd.latency.max > sd.latency.target ? (sd.latency.max - sd.latency.target + 500) / 1000 : NaN;
            const referenceId = sd.latency.referenceId || NaN;

            return {
                liveDelay,
                maxDrift,
                referenceId
            }
        }

        /**
         * Adjust the playback rate targets for the service
         * @param {object} sd
         * @private
         */
        function _applyServiceDescriptionPlaybackRate(sd) {
            // Convert each playback rate into a difference from 1. i.e 0.8 becomes -0.2.
            const min = sd.playbackRate.min ? (Math.round((sd.playbackRate.min - 1.0) * 1000) / 1000) : NaN;
            const max = sd.playbackRate.max ? (Math.round((sd.playbackRate.max - 1.0) * 1000) / 1000) : NaN;
            serviceDescriptionSettings.liveCatchup.playbackRate.min = min;
            serviceDescriptionSettings.liveCatchup.playbackRate.max = max;

            logger.debug(`Found latency properties coming from service description: Live catchup min playback rate: ${min}`);
            logger.debug(`Found latency properties coming from service description: Live catchup max playback rate: ${max}`);
        }

        /**
         * Used to specify a quality ranking. We do not support this yet.
         * @private
         */
        function _applyServiceDescriptionOperatingQuality() {
            return;
        }

        /**
         * Adjust the operating quality targets for the service
         * @param {object} sd
         * @private
         */
        function _applyServiceDescriptionOperatingBandwidth(sd) {

            // Aggregation of media types is not supported yet
            if (!sd || !sd.operatingBandwidth || !sd.operatingBandwidth.mediaType || sd.operatingBandwidth.mediaType === MEDIA_TYPES.ALL) {
                return;
            }

            const params = {};

            params.minBandwidth = sd.operatingBandwidth.min;
            params.maxBandwidth = sd.operatingBandwidth.max;
            params.targetBandwidth = sd.operatingBandwidth.target;

            const mediaTypesToApply = [];

            if (sd.operatingBandwidth.mediaType === MEDIA_TYPES.VIDEO || sd.operatingBandwidth.mediaType === MEDIA_TYPES.AUDIO) {
                mediaTypesToApply.push(sd.operatingBandwidth.mediaType);
            } else if (sd.operatingBandwidth.mediaType === MEDIA_TYPES.ANY) {
                mediaTypesToApply.push(MEDIA_TYPES.AUDIO);
                mediaTypesToApply.push(MEDIA_TYPES.VIDEO);
            }

            mediaTypesToApply.forEach((mediaType) => {

                if (!isNaN(params.minBandwidth)) {
                    _updateBandwidthSetting('minBitrate', mediaType, params.minBandwidth);
                }

                if (!isNaN(params.maxBandwidth)) {
                    _updateBandwidthSetting('maxBitrate', mediaType, params.maxBandwidth);
                }

                if (!isNaN(params.targetBandwidth)) {
                    _updateBandwidthSetting('initialBitrate', mediaType, params.targetBandwidth);
                }
            })
        }

        /**
         * Update the bandwidth settings vor a specific field and media type
         * @param {string} field
         * @param {string} mediaType
         * @param {number} value
         * @private
         */
        function _updateBandwidthSetting(field, mediaType, value) {
            try {
                // Service description values are specified in bps. Settings expect the value in kbps
                serviceDescriptionSettings[field][mediaType] = value / 1000;
            } catch (e) {
                logger.error(e);
            }
        }

        /**
         * Add information about content steering element. Handling is up to the ContentSteeringController
         *  @param {object} sd
         *  @private
         */
        function _applyServiceDescriptionContentSteering(sd) {
            serviceDescriptionSettings.contentSteering = sd.contentSteering;
        }

        /**
         * Returns the current calculated time offsets based on ProducerReferenceTime elements
         * @returns {array}
         */
        function getProducerReferenceTimeOffsets() {
            return prftOffsets;
        }

        /**
         * Calculates an array of time offsets each with matching ProducerReferenceTime id.
         * Call before applyServiceDescription if producer reference time elements should be considered.
         * @param {array} streamInfos
         * @returns {array}
         * @private
         */
        function calculateProducerReferenceTimeOffsets(streamInfos) {
            try {
                let timeOffsets = [];
                if (streamInfos && streamInfos.length > 0) {
                    const mediaTypes = [Constants.VIDEO, Constants.AUDIO, Constants.TEXT];
                    const astInSeconds = adapter.getAvailabilityStartTime() / 1000;

                    streamInfos.forEach((streamInfo) => {
                        const offsets = mediaTypes
                            .reduce((acc, mediaType) => {
                                acc = acc.concat(adapter.getAllMediaInfoForType(streamInfo, mediaType));
                                return acc;
                            }, [])
                            .reduce((acc, mediaInfo) => {
                                const prts = adapter.getProducerReferenceTimes(streamInfo, mediaInfo);
                                prts.forEach((prt) => {
                                    const voRepresentations = adapter.getVoRepresentations(mediaInfo);
                                    if (voRepresentations && voRepresentations.length > 0 && voRepresentations[0].adaptation && voRepresentations[0].segmentInfoType === DashConstants.SEGMENT_TEMPLATE) {
                                        const voRep = voRepresentations[0];
                                        const d = new Date(prt[DashConstants.WALL_CLOCK_TIME]);
                                        const wallClockTime = d.getTime() / 1000;
                                        // TS 103 285 Clause 10.20.4
                                        // 1) Calculate PRT0
                                        // i) take the PRT@presentationTime and subtract any ST@presentationTimeOffset
                                        // ii) convert this time to seconds by dividing by ST@timescale
                                        // iii) Add this to start time of period that contains PRT.
                                        // N.B presentationTimeOffset is already divided by timescale at this point
                                        const prt0 = wallClockTime - (((prt[DashConstants.PRESENTATION_TIME] / voRep[DashConstants.TIMESCALE]) - voRep[DashConstants.PRESENTATION_TIME_OFFSET]) + streamInfo.start);
                                        // 2) Calculate TO between PRT at the start of MPD timeline and the AST
                                        const to = astInSeconds - prt0;
                                        // 3) Not applicable as liveLatency does not consider ST@availabilityTimeOffset
                                        acc.push({ id: prt[DashConstants.ID], to });
                                    }
                                });
                                return acc;
                            }, [])

                        timeOffsets = timeOffsets.concat(offsets);
                    })
                }
                prftOffsets = timeOffsets;
            } catch (e) {
                logger.error(e);
                prftOffsets = [];
            }
        };

        /**
         * Calculates offset to apply to live delay as described in TS 103 285 Clause 10.20.4
         * @param {object} sdLatency - service description latency element
         * @returns {number}
         * @private
         */
        function _calculateTimeOffset(sdLatency) {
            let to = 0, id;
            let offset = prftOffsets.filter(prt => {
                return prt.id === sdLatency.referenceId;
            });

            // If only one ProducerReferenceTime to generate one TO, then use that regardless of matching ids
            if (offset.length === 0) {
                to = (prftOffsets.length > 0) ? prftOffsets[0].to : 0;
                id = prftOffsets[0].id || NaN;
            } else {
                // If multiple id matches, use the first but this should be invalid
                to = offset[0].to || 0;
                id = offset[0].id || NaN;
            }

            return { to, id }
        }

        instance = {
            getServiceDescriptionSettings,
            getProducerReferenceTimeOffsets,
            calculateProducerReferenceTimeOffsets,
            applyServiceDescription,
            reset,
            setConfig
        };

        setup();

        return instance;
    }

    ServiceDescriptionController.__dashjs_factory_name = 'ServiceDescriptionController';
    export default FactoryMaker.getSingletonFactory(ServiceDescriptionController);

#### Search results