{"version":3,"file":"loadVideoTextures.mjs","sources":["../../../../../src/assets/loader/parsers/textures/loadVideoTextures.ts"],"sourcesContent":["import { ExtensionType } from '../../../../extensions/Extensions';\nimport { VideoSource } from '../../../../rendering/renderers/shared/texture/sources/VideoSource';\nimport { detectVideoAlphaMode } from '../../../../utils/browser/detectVideoAlphaMode';\nimport { getResolutionOfUrl } from '../../../../utils/network/getResolutionOfUrl';\nimport { checkDataUrl } from '../../../utils/checkDataUrl';\nimport { checkExtension } from '../../../utils/checkExtension';\nimport { createTexture } from './utils/createTexture';\n\nimport type { VideoSourceOptions } from '../../../../rendering/renderers/shared/texture/sources/VideoSource';\nimport type { Texture } from '../../../../rendering/renderers/shared/texture/Texture';\nimport type { ResolvedAsset } from '../../../types';\nimport type { Loader } from '../../Loader';\nimport type { LoaderParser } from '../LoaderParser';\n\nconst validVideoExtensions = ['.mp4', '.m4v', '.webm', '.ogg', '.ogv', '.h264', '.avi', '.mov'];\nconst validVideoMIMEs = validVideoExtensions.map((ext) => `video/${ext.substring(1)}`);\n\n/**\n * Set cross origin based detecting the url and the crossorigin\n * @param element - Element to apply crossOrigin\n * @param url - URL to check\n * @param crossorigin - Cross origin value to use\n * @memberof assets\n */\nexport function crossOrigin(element: HTMLImageElement | HTMLVideoElement, url: string, crossorigin?: boolean | string): void\n{\n    if (crossorigin === undefined && !url.startsWith('data:'))\n    {\n        element.crossOrigin = determineCrossOrigin(url);\n    }\n    else if (crossorigin !== false)\n    {\n        element.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous';\n    }\n}\n\n/**\n * Preload a video element\n * @param element - Video element to preload\n */\nexport function preloadVideo(element: HTMLVideoElement): Promise<void>\n{\n    return new Promise((resolve, reject) =>\n    {\n        element.addEventListener('canplaythrough', loaded);\n        element.addEventListener('error', error);\n\n        element.load();\n\n        function loaded(): void\n        {\n            cleanup();\n            resolve();\n        }\n\n        function error(err: ErrorEvent): void\n        {\n            cleanup();\n            reject(err);\n        }\n\n        function cleanup(): void\n        {\n            element.removeEventListener('canplaythrough', loaded);\n            element.removeEventListener('error', error);\n        }\n    });\n}\n\n/**\n * Sets the `crossOrigin` property for this resource based on if the url\n * for this resource is cross-origin. If crossOrigin was manually set, this\n * function does nothing.\n * Nipped from the resource loader!\n * @ignore\n * @param url - The url to test.\n * @param {object} [loc=window.location] - The location object to test against.\n * @returns The crossOrigin value to use (or empty string for none).\n * @memberof assets\n */\nexport function determineCrossOrigin(url: string, loc: Location = globalThis.location): string\n{\n    // data: and javascript: urls are considered same-origin\n    if (url.startsWith('data:'))\n    {\n        return '';\n    }\n\n    // default is window.location\n    loc = loc || globalThis.location;\n\n    const parsedUrl = new URL(url, document.baseURI);\n\n    // if cross origin\n    if (parsedUrl.hostname !== loc.hostname || parsedUrl.port !== loc.port || parsedUrl.protocol !== loc.protocol)\n    {\n        return 'anonymous';\n    }\n\n    return '';\n}\n\n/**\n * A simple plugin to load video textures.\n *\n * You can pass VideoSource options to the loader via the .data property of the asset descriptor\n * when using Asset.load().\n * ```js\n * // Set the data\n * const texture = await Assets.load({\n *     src: './assets/city.mp4',\n *     data: {\n *         preload: true,\n *         autoPlay: true,\n *     },\n * });\n * ```\n * @memberof assets\n */\nexport const loadVideoTextures = {\n\n    name: 'loadVideo',\n\n    extension: {\n        type: ExtensionType.LoadParser,\n        name: 'loadVideo',\n    },\n\n    test(url: string): boolean\n    {\n        const isValidDataUrl = checkDataUrl(url, validVideoMIMEs);\n        const isValidExtension = checkExtension(url, validVideoExtensions);\n\n        return isValidDataUrl || isValidExtension;\n    },\n\n    async load(url: string, asset: ResolvedAsset<VideoSourceOptions>, loader: Loader): Promise<Texture>\n    {\n        // --- Merge default and provided options ---\n        const options: VideoSourceOptions = {\n            ...VideoSource.defaultOptions,\n            resolution: asset.data?.resolution || getResolutionOfUrl(url),\n            alphaMode: asset.data?.alphaMode || await detectVideoAlphaMode(),\n            ...asset.data,\n        };\n\n        // --- Create and configure HTMLVideoElement ---\n        const videoElement = document.createElement('video');\n\n        // Set attributes based on options\n        const attributeMap = {\n            preload: options.autoLoad !== false ? 'auto' : undefined,\n            'webkit-playsinline': options.playsinline !== false ? '' : undefined,\n            playsinline: options.playsinline !== false ? '' : undefined,\n            muted: options.muted === true ? '' : undefined,\n            loop: options.loop === true ? '' : undefined,\n            autoplay: options.autoPlay !== false ? '' : undefined\n        };\n\n        Object.keys(attributeMap).forEach((key) =>\n        {\n            const value = attributeMap[key as keyof typeof attributeMap];\n\n            if (value !== undefined) videoElement.setAttribute(key, value);\n        });\n\n        if (options.muted === true)\n        {\n            videoElement.muted = true;\n        }\n\n        crossOrigin(videoElement, url, options.crossorigin); // Assume crossOrigin is globally available\n\n        // --- Set up source and MIME type ---\n        const sourceElement = document.createElement('source');\n\n        // Determine MIME type\n        let mime: string | undefined;\n\n        if (url.startsWith('data:'))\n        {\n            mime = url.slice(5, url.indexOf(';'));\n        }\n        else if (!url.startsWith('blob:'))\n        {\n            const ext = url.split('?')[0].slice(url.lastIndexOf('.') + 1).toLowerCase();\n\n            mime = VideoSource.MIME_TYPES[ext] || `video/${ext}`;\n        }\n\n        sourceElement.src = url;\n\n        if (mime)\n        {\n            sourceElement.type = mime;\n        }\n\n        // this promise will make sure that video is ready to play - as in we have a valid width, height and it can be\n        // uploaded to the GPU. Our textures are kind of dumb now, and don't want to handle resizing right now.\n        return new Promise((resolve) =>\n        {\n            const onCanPlay = async () =>\n            {\n                const base = new VideoSource({ ...options, resource: videoElement });\n\n                videoElement.removeEventListener('canplay', onCanPlay);\n\n                if (asset.data.preload)\n                {\n                    await preloadVideo(videoElement);\n                }\n\n                resolve(createTexture(base, loader, url));\n            };\n\n            videoElement.addEventListener('canplay', onCanPlay);\n            videoElement.appendChild(sourceElement);\n        });\n    },\n\n    unload(texture: Texture): void\n    {\n        texture.destroy(true);\n    }\n} satisfies LoaderParser<Texture, VideoSourceOptions>;\n"],"names":[],"mappings":"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"}