streaming_text_DVBFonts.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) 2024, BBC.
 * 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 BBC 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 FactoryMaker from '../../core/FactoryMaker';
import URLUtils from '../utils/URLUtils';
import EventBus from '../../core/EventBus';
import MediaPlayerEvents from '../MediaPlayerEvents';
import Debug from '../../core/Debug';

function DVBFonts(config) {

    let context = this.context;
    const eventBus = EventBus(context).getInstance();
    const urlUtils = URLUtils(context).getInstance();
    const adapter = config.adapter;
    const baseURLController = config.baseURLController;

    const FONT_DOWNLOAD_STATUS = {
        ERROR: 'error',
        LOADED: 'loaded',
        UNLOADED: 'unloaded'
    };

    let instance,
        logger,
        dvbFontList;

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

    /**
     * Add any dvb fonts from a single track to the dvbFontList
     * @param {object} track - A text track
     * @param {string} streamId - Id of current stream
     * @private
     */
    function _addFontFromTrack(track, streamId) {
        let asBaseUrl;
        let isEssential = false;
        let dvbFontProps;

        // If there is a baseurl in the manifest resolve against a representation inside the current adaptation set
        if (baseURLController.resolve()) {
            const reps = adapter.getVoRepresentations(track);
            if (reps && reps.length > 0) {
                asBaseUrl = baseURLController.resolve(reps[0].path).url
            }
        }

        const essentialTags = track.essentialPropertiesAsArray.filter(tag =>
            (tag.schemeIdUri && tag.schemeIdUri === Constants.FONT_DOWNLOAD_DVB_SCHEME)
        );
        const supplementalTags = track.supplementalPropertiesAsArray.filter(tag =>
            (tag.schemeIdUri && tag.schemeIdUri === Constants.FONT_DOWNLOAD_DVB_SCHEME)
        );

        // When it comes to the property descriptors it's Essential OR Supplementary, with Essential taking preference
        if (essentialTags.length > 0) {
            isEssential = true;
            dvbFontProps = essentialTags;
        } else {
            dvbFontProps = supplementalTags;
        }

        dvbFontProps.forEach(attrs => {
            if (_hasMandatoryDvbFontAttributes(attrs)) {
                let resolvedUrl = _resolveFontUrl(attrs.dvbUrl, asBaseUrl);
                dvbFontList.push({
                    fontFamily: attrs.dvbFontFamily,
                    url: resolvedUrl,
                    mimeType: attrs.dvbMimeType,
                    trackId: track.id,
                    streamId,
                    isEssential,
                    status: FONT_DOWNLOAD_STATUS.UNLOADED,
                    fontFace: new FontFace(
                        attrs.dvbFontFamily,
                        `url(${resolvedUrl})`,
                        { display: 'swap' }
                    )
                });
            }
        });
    }

    /**
     * Clean up dvb font downloads
     * @private
     */
    function _cleanUpDvbCustomFonts() {
        for (const font of dvbFontList) {
            let deleted = document.fonts.delete(font.fontFace);
            logger.debug(`Removal of fontFamily: ${font.fontFamily} was ${deleted ? 'successful' : 'unsuccessful'}`);
        }
    }

    /**
     * Check the attributes of a supplemental or essential property descriptor to establish if
     * it has the mandatory values for a dvb font download
     * @param {object} attrs - property descriptor attributes
     * @returns {boolean} true if mandatory attributes present
     * @private
     */
    function _hasMandatoryDvbFontAttributes(attrs) {
        return !!((attrs.value && attrs.value === '1') &&
            (attrs.dvbUrl && attrs.dvbUrl.length > 0) &&
            (attrs.dvbFontFamily && attrs.dvbFontFamily.length > 0) &&
            (attrs.dvbMimeType && (attrs.dvbMimeType === Constants.OFF_MIMETYPE || attrs.dvbMimeType === Constants.WOFF_MIMETYPE)));
    }

    /**
     * Resolves a given font download URL.
     * @param {string} fontUrl - URL as in the 'dvb:url' property
     * @param {string} baseUrl - BaseURL for Adaptation Set
     * @returns {string} resolved URL
     * @private
     */
    function _resolveFontUrl(fontUrl, baseUrl) {
        if (urlUtils.isPathAbsolute(fontUrl)) {
            return fontUrl;
        } else if (urlUtils.isRelative(fontUrl)) {
            if (baseUrl) {
                return urlUtils.resolve(fontUrl, baseUrl);
            } else {
                return urlUtils.resolve(fontUrl);
            }
        } else {
            return fontUrl;
        }
    }

    /**
     * Updates the status of a given dvb font relative to whether it is loaded in the browser
     * or if the download has failed
     * @param {number} index - Index of font in dvbFontList
     * @param {string} newStatus - Status value to update. Property of FONT_DOWNLOAD_STATUS
     * @private
     */
    function _updateFontStatus(index, newStatus) {
        const font = dvbFontList[index];
        dvbFontList[index] = { ...font, status: newStatus };
    }

    /**
     * Adds all fonts to the dvb font list from all tracks
     * @param {array} tracks - All text tracks
     * @param {string} streamId - Id of the stream
     */
    function addFontsFromTracks(tracks, streamId) {
        if (tracks && Array.isArray(tracks) && streamId) {
            for (let i = 0; i < tracks.length; i++) {
                let track = tracks[i];
                _addFontFromTrack(track, streamId);
            }
        }
    }

    /**
     * Initiate the download of a dvb custom font.
     * The browser will neatly handle duplicate fonts
     */
    function downloadFonts() {
        for (let i = 0; i < dvbFontList.length; i++) {
            let font = dvbFontList[i];

            document.fonts.add(font.fontFace);
            eventBus.trigger(MediaPlayerEvents.DVB_FONT_DOWNLOAD_ADDED, font);

            font.fontFace.load().then(
                () => {
                    _updateFontStatus(i, FONT_DOWNLOAD_STATUS.LOADED);
                    eventBus.trigger(MediaPlayerEvents.DVB_FONT_DOWNLOAD_COMPLETE, font);
                },
                (err) => {
                    _updateFontStatus(i, FONT_DOWNLOAD_STATUS.ERROR);
                    logger.debug('Font download error: ', err);
                    eventBus.trigger(MediaPlayerEvents.DVB_FONT_DOWNLOAD_FAILED, font);
                }
            );
        }
    }

    /**
     * Returns current list of all known DVB Fonts
     * @returns {array} dvbFontList
     */
    function getFonts() {
        return dvbFontList;
    }

    /**
     * Returns dvbFonts relative to a track given a trackId
     * @param {number} - TrackId
     * @returns {array} filtered DVBFontList
     */
    function getFontsForTrackId(trackId) {
        return dvbFontList.filter(font =>
            (font.trackId && font.trackId === trackId)
        );
    }

    function resetInitialSettings() {
        dvbFontList = [];
    }

    /** Reset DVBFonts instance */
    function reset() {
        _cleanUpDvbCustomFonts();
        resetInitialSettings();
    }

    instance = {
        addFontsFromTracks,
        downloadFonts,
        getFonts,
        getFontsForTrackId,
        reset
    };
    setup();
    return instance;
}

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