Skip to content

Source: src/util/Debug.js

/* eslint-disable no-console */

/**
 * The Debug component provides internal diagnostics during player initialization and runtime.
 * It logs basic environment and state data and helps developers debug player behavior by monitoring events and inspecting supported media formats.
 * While not intended for end users, this component can be very useful during development, testing, or when troubleshooting media playback issues.
 * Note that this component is usually not included in the regular production builds.
 * @exports module:src/util/Debug
 * @author Frank Kudermann - alphanull
 * @version 1.0.0
 * @license MIT
 */
export default class Debug {

    /**
     * Contains configuration options for this component.
     * @type     {Object}
     * @property {boolean} [logMediaEvents=true]   Logs media related events, i.e. Event topic starts with `media`.
     * @property {boolean} [logPlayerEvents=true]  Logs all other events, except for media related events, like `player/ready`.
     * @property {boolean} [verboseLogging=false]  Enables verbose logging, i.e. Additional 'spammy' events like `media/progress` are logged via console.debug.
     */
    #config = {
        logMediaEvents: true,
        logPlayerEvents: true,
        verboseLogging: false
    };

    /**
     * Reference to the main player instance.
     * @type {module:src/core/Player}
     */
    #player;

    /**
     * Holds tokens of subscriptions to player events, for later unsubscribe.
     * @type {number[]}
     */
    #subscriptions = [];

    /**
     * Creates an instance of the Debug component.
     * @param {module:src/core/Player} player  Reference to the VisionPlayer instance.
     */
    constructor(player) {

        this.#config = player.initConfig('debug', this.#config);

        if (!this.#config) return [false];

        console.info('[Debug] Client Info:', player.getClient());
        console.info('[Debug] Player State:', player.getState());

        Debug.canPlayTest(player);

        this.#player = player;

        // subscribe to all player events
        this.#subscriptions.push(this.#player.subscribe('*', this.#logEvent, { priority: 99 }));

    }

    /**
     * Logs events coming from the debug mode to the console.
     * @param {Object} data   Data object from the event.
     * @param {string} topic  The pubsub event topic (e.g., "media/play", "media/error").
     */
    #logEvent = (data, topic) => {

        const eventName = topic.match(/^vip\/[^/]+\/(.*)$/)[1];

        if (eventName.startsWith('media/') && this.#config.logMediaEvents) {

            if (eventName.endsWith('/error')) {

                console.error('[Debug] Media Error:  ', eventName, data);

            } else {

                const verboseEvents = ['media/timeupdate', 'media/progress', 'media/suspend', 'media/durationchange'],
                      isVerbose = verboseEvents.find(verbose => verbose === eventName);

                if (isVerbose) {
                    if (this.#config.verboseLogging) console.debug('[Debug] Media Event:  ', eventName, data);
                } else {
                    console.log('[Debug] Media Event:  ', eventName, data);
                }
            }

        } else if (this.#config.logPlayerEvents) {

            if (eventName.endsWith('/error')) {

                console.error('[Debug] Player Error: ', eventName, data);

            } else {

                const verboseEvents = ['scrubber/update', 'scrubber/tooltip/move'],
                      isVerbose = verboseEvents.find(verbose => verbose === eventName);

                if (isVerbose) {
                    if (this.#config.verboseLogging) console.debug('[Debug] Global Event: ', eventName, data);
                } else {
                    console.log('[Debug] Global Event: ', eventName, data);
                }
            }

        }

    };

    /**
     * Cleans up the Debug component by unsubscribing from events.
     */
    destroy() {

        this.#player.unsubscribe(this.#subscriptions);

    }

    /**
     * Determines all MIME type formats the client can (probably) play and displays the results in the console.
     * @param {module:src/core/Player} player  Reference to the media player instance.
     */
    static canPlayTest(player) {

        const audio = [],
              video = [];

        player.constructor.getFormats().forEach(format => {
            if (format.mimeTypeAudio) {
                const canPlay = player.media.canPlay({ mimeType: format.mimeTypeAudio[0] });
                if (canPlay === 'maybe' || canPlay === 'probably') audio.push(format.extensions.join(', '));
            }
            if (format.mimeTypeVideo) {
                const canPlay = player.media.canPlay({ mimeType: format.mimeTypeVideo[0] });
                if (canPlay === 'maybe' || canPlay === 'probably') video.push(format.extensions.join(', '));
            }
        });

        console.info('[Debug] canPlayTest Result');
        console.log('  Audio: ', audio.join(', '));
        console.log('  Video: ', video.join(', '));

    }

}