Home Manual Reference Source Test

src/browser/main/page.js

/**
 * A class in charge of detecting new {@link HTMLMediaElement}s in current page
 * and registering them as players.
 *
 * The expected usage is call {@link checkForMediaElements} and {@link observeForMedia}
 * upon window.load event.
 */
class Page {
    /**
     *
     *
     * @param {Document} document
     * @param {Playback} playback
     * @param {Host} host
     */
    constructor (document, playback, host) {
        /**
         * The current document to keep as context
         * @type {Document}
         */
        this.document = document;

        /**
         * Current {@link Playback} on the page
         * @type {Playback}
         */
        this.playback = playback;

        /**
         * Current {@link Host} to trigger changes
         * @type {Host}
         */
        this.host = host;

        /**
         * An array holding all {@link Player}s present in the page
         *
         * @type {Array.<Player>}
         */
        this.players = [];

        /**
         * Upon load of a page this property can be used by providers
         * to cache html elements that are likely to be used repeatedly.
         *
         * @see src/browser/providers/soundcloud.js - for an example.
         *
         * @type {Object.<string, HTMLElement>}
         */
        this.elements = {};

        /**
         * An observer for added media elements
         * use {@link observeForMedia} to observer elements
         *
         * @type {MutationObserver}
         * @private
         */
        this._mediaObserver = new MutationObserver(m => this.onMutate(m));

        /**
         * An observer to trigger change events on the host
         * use {@link observeForChanges} to add elements
         *
         * @type {MutationObserver}
         * @private
         */
        this._changesObserver = new MutationObserver(() => this.host.change());
    }

    /**
     * Take the element and add it to the list of players
     * if it's not already there
     *
     * Use when detecting a new element on the DOM
     *
     * If the element is playing it will be set as the active player.
     *
     * @param element
     * @return {boolean}
     */
    registerPlayer (element) {
        if (this.players.find(player => player.element === element)) {
            return false;
        }

        let player = new Player(this, this.host, element);

        this.players.push(player);

        // Ignore short sounds, they are most likely a chat notification sound
        // but still allow when undetermined (e.g. video stream)
        if (player.isPlaying()) {
            this.setActivePlayer(player);
        }
        return true;
    }

    /**
     * Set element to be observed by {@link this._mediaObserver}
     *
     * @param {Element} element
     */
    observeForMedia (element) {
        this._mediaObserver.observe(element, {
            childList: true,
            subtree: true
        });
    }

    /**
     * Set element to be observed by {@link this._changesObserver}
     * by default check for any changes to itself or it's children
     *
     * @param {Element} element
     * @param {MutationObserverInit} options
     */
    observeForChanges (element, options = { childList: true, subtree: true }) {
        this._changesObserver.observe(element, options);
    }

    /**
     * Callback called by {@link this._mediaObserver}
     * Given an array of mutations check if there where any added nodes that are media
     *
     * @param {Array<MutationRecord>} mutations
     */
    onMutate (mutations) {
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (typeof node.matches !== 'function' || typeof node.querySelectorAll !== 'function') {
                    return;
                }

                // first check whether the node itself is audio/video
                if (node.matches('video,audio')) {
                    this.registerPlayer(node);
                    return;
                }

                // if not, check whether any of its children are
                node.querySelectorAll('video,audio')
                  .forEach(player => this.registerPlayer(player));
            });
        });
    }

    /**
     * Search DOM for any &lt;video&gt; or &lt;audio&gt; elements
     */
    checkForMediaElements () {
        this.document.querySelectorAll('video,audio')
          .forEach(player => this.registerPlayer(player));
    }

    /**
     * Set the playback's active player
     *
     * @param {Player} player
     */
    setActivePlayer (player) {
        this.host.start(player);
    }

    /**
     * Get the playbacks active player
     *
     * @returns {Player}
     */
    getActivePlayer () {
        return this.playback.activePlayer;
    }

    /**
     * Get the page's title
     *
     * @returns {null|string}
     */
    getTitle () {
        return this.document.title;
    }

    /**
     * Get the page's favicon
     *
     * @returns {string}
     */
    getIcon () {
        return this.document
          .querySelector('link[rel="shortcut icon"],link[rel="icon"]')
          .getAttribute('href');
    }

}