Source: media/thumbnail/WebVTTThumbnail.js

import { WebVTT } from "./WebVTT.js";
export { WebVTTThumbnail }

/**
 * Class that handles webVTT files for thumbnails.
 * @extends WebVTT
 * @author ckraemme <ckraemme@awi.de>
 */
class WebVTTThumbnail extends WebVTT {
    /**
     * Build a webVTT object for thumbnails, ready to be filled with thumbnail related data.
     * @param {string} webVTTPath - Path to the webVTT file containing thumbnail information.
     */
    constructor(webVTTPath) {
        super(webVTTPath);
        this.manifestData = [];
        this.thumbnailLoadingState = [];
    }

    /**
     * Read in the thumbnail manifest file (webVTT) and store its information in a machine readable form. Afterwards preload thumbnails.
     * @param {callback} - Function to be executed subsequently.
     * @param {Number} num - Number of thumbnails that should be preloaded. If num=-1, load all.
     * @param {boolean} sparse - If true, load the thumbnails spread over the whole video, otherwise sequentially from the beginning (for separated thumbnail images only).
     * @returns {Promise} Promise that is resolved after thumbnails were loaded successfully.
     */
    init(num, sparse) {
        return this.readFile().then(() => {
            this.reduceManifestViaTimestamps_();
            this.extractUrlsInManifest_();
            return this.preloadThumbnails_(num, sparse);
        });
    }

    /**
     * Remove all lines in the manifest file that do not contain timestamps and data.
     * @private
     */
    reduceManifestViaTimestamps_() {
        this.manifestSource.forEach(line => {
            if (line['time_start'] != null && line['time_end'] != null && line['data'] != null)
                this.manifestData.push(line);
        });
    }

    /**
     * For each thumbnail image, extract the pure thumbnail URL, i.e., removing trailing coordinate information if available, and create the absolute URL path.
     * @private
     */
    extractUrlsInManifest_() {
        var urlWebVTTPath = new URL(this.webVTTPath);

        for (let i = 0; i < this.manifestData.length; i++) {
            const urlWebVTTImage = this.manifestData[i]['data'].split('#')[0];

            var regexAbsoluteUrl = new RegExp('^(?:[a-z+]+:)?//', 'i');
            var regexRootUrl = new RegExp('^/(?!/)', 'i');

            const isAbsoluteUrl = regexAbsoluteUrl.test(urlWebVTTImage);
            const isRootUrl = regexRootUrl.test(urlWebVTTImage);
            const isRelativeUrl = !isAbsoluteUrl && !isRootUrl;

            var base;
            if (isAbsoluteUrl)
                base = undefined;
            else if (isRootUrl)
                base = urlWebVTTPath.protocol + '//' + urlWebVTTPath.hostname;
            else if (isRelativeUrl)
                base = this.webVTTPath.substring(0, this.webVTTPath.lastIndexOf("/")) + '/';

            const urlObject = new URL(urlWebVTTImage, base);
            this.manifestData[i]['url'] = urlObject['href'];
        }
    }

    /**
     * Preload thumbnail images and write the image data into this.thumbnailLoadingState.
     * @param {Number} num - Number of thumbnails that should be preloaded. If num=-1, load all.
     * @param {boolean} sparse - If true, load the thumbnails spread over the whole video, otherwise sequentially from the beginning (for separated thumbnail images only).
     * @private
     */
    preloadThumbnails_(num, sparse) {

        if (num == -1)
            num = this.manifestData.length;

        let delta = 1;
        if (sparse == true) {
            delta = Math.floor(this.manifestData.length / num);
            delta = Math.max(delta, 1); //in case of wrong configuration
        }

        for (let i = 0; i < this.manifestData.length; i++)
            this.manifestData[i]['image'] = undefined;

        for (let i = 0; i < this.manifestData.length; i = i + delta) {
            this.loadThumbnail_(i);
            if (i == (num - 1) * delta)
                break;
        }
        return Promise.all(this.thumbnailLoadingState);
    }

    /**
     * Load the thumbnail image.
     * @param {Number} pos - Position of the thumbnail in this.manifestData.
     * @private
     */
    loadThumbnail_(pos) {
        if (this.thumbnailLoadingState[pos] === undefined) {
            this.thumbnailLoadingState[pos] = new Promise((resolve, reject) => {
                const image = new Image();
                image.onload = () => {
                    this.manifestData[pos]['image'] = image;
                    resolve(image);
                };
                image.onerror = reject;
                image.src = this.manifestData[pos]['url'];
            });
        }
    }
}