@pmndrs/uikit
Version:
Build performant 3D user interfaces with Three.js and yoga.
105 lines (104 loc) • 4.14 kB
JavaScript
import { Image } from './image.js';
import { SRGBColorSpace, VideoTexture } from 'three';
import { computed, signal } from '@preact/signals-core';
import { abortableEffect, loadResourceWithParams } from '../utils.js';
export class Video extends Image {
element = signal();
constructor(inputProperties, initialClasses, config) {
super(inputProperties, initialClasses, {
loadTexture: false,
...config,
});
const srcIsElement = computed(() => this.properties.value.src instanceof HTMLVideoElement);
const notYetLoadedElement = computed(() => {
if (srcIsElement.value) {
return this.properties.value.src;
}
const element = document.createElement('video');
element.style.position = 'absolute';
element.style.width = '1px';
element.style.zIndex = '-1000';
element.style.top = '0px';
element.style.left = '0px';
return element;
});
abortableEffect(() => {
const element = notYetLoadedElement.value;
if (element == null) {
return;
}
element.playsInline = true;
element.volume = this.properties.value.volume ?? 1;
element.preservesPitch = this.properties.value.preservesPitch ?? true;
element.playbackRate = this.properties.value.playbackRate ?? 1;
element.muted = this.properties.value.muted ?? false;
element.loop = this.properties.value.loop ?? false;
element.autoplay = this.properties.value.autoplay ?? false;
element.crossOrigin = this.properties.value.crossOrigin ?? null;
const src = this.properties.value.src;
if (src instanceof HTMLVideoElement) {
return;
}
updateVideoElementSrc(element, src);
}, this.abortSignal);
abortableEffect(() => {
const element = notYetLoadedElement.value;
if (srcIsElement.value || element == null) {
return;
}
document.body.appendChild(element);
return () => element.remove();
}, this.abortSignal);
loadResourceWithParams(this.element, loadVideoElement, () => { }, this.abortSignal, notYetLoadedElement);
abortableEffect(() => {
const element = this.element.value;
if (element == null) {
return;
}
const updateTexture = () => {
const texture = new VideoTexture(element);
texture.colorSpace = SRGBColorSpace;
texture.needsUpdate = true;
this.texture.value = texture;
};
updateTexture();
element.addEventListener('resize', updateTexture);
return () => element.removeEventListener('resize', updateTexture);
}, this.abortSignal);
abortableEffect(() => {
const { requestRender } = this.root.value;
const element = this.element.value;
if (requestRender == null || element == null) {
return;
}
let requestId;
const callback = () => {
requestRender();
requestId = element.requestVideoFrameCallback(callback);
};
requestId = element.requestVideoFrameCallback(callback);
return () => element.cancelVideoFrameCallback(requestId);
}, this.abortSignal);
}
}
async function loadVideoElement(element) {
if (element == null) {
return undefined;
}
if (element.readyState < HTMLMediaElement.HAVE_METADATA) {
await new Promise((resolve) => (element.onloadedmetadata = resolve));
}
return element;
}
export function updateVideoElementSrc(element, src) {
if (src == null) {
element.removeAttribute('src');
element.removeAttribute('srcObject');
return;
}
if (typeof src === 'string') {
element.src = src;
return;
}
element.srcObject = src;
}