import { useEffect, useRef, useState } from 'react' import { CDN_BASE } from '../../functions/Backend' import './styles/MediaCanvas.css' import vectorIconThrobber from '../../vectors/throbber.svg' import vectorIconCross from '../../vectors/cross.svg' interface PropsForMediaCanvas { id: string background: boolean } export default function MediaCanvas({ id, background }: PropsForMediaCanvas) { const canvasRef = useRef(null) const videoRef = useRef(null) const [fallback, setFallback] = useState(false) const [loading, setLoading] = useState(true) const [error, setError] = useState('') useEffect(() => { const defer: (() => void)[] = [] const canvas = canvasRef.current! const video = videoRef.current! if (!canvas || !video) return video.onerror = () => { console.warn('Failed to load video, using fallback...') teardown() setFallback(true) return } // --- Initialize Canvas --- const gl = canvas.getContext('webgl', { powerPreference: 'low-power', preserveDrawingBuffer: true, // for download button premultipliedAlpha: false, antialias: false, alpha: true, depth: false, })! if (!gl) { console.error('Context failed, using fallback...') setFallback(true) return } try { const VERT = ` precision mediump float; attribute vec2 aPos; uniform mat3 uMatrix; varying vec2 vUV; void main() { gl_Position = vec4(uMatrix * vec3(aPos, 1.0), 1.0); vUV = aPos; }` const FRAG = ` precision mediump float; uniform sampler2D uFrame; varying vec2 vUV; void main() { vec2 colorUV = vec2(vUV.x, vUV.y * 0.5); vec2 alphaUV = vec2(vUV.x, 0.5 + vUV.y * 0.5); vec4 color = texture2D(uFrame, colorUV); float alpha = texture2D(uFrame, alphaUV).r; gl_FragColor = vec4(color.rgb, alpha); }` function compileShader(type: number, src: string) { const s = gl.createShader(type)! gl.shaderSource(s, src) gl.compileShader(s) return s } const prog = gl.createProgram() gl.attachShader(prog, compileShader(gl.VERTEX_SHADER, VERT)) gl.attachShader(prog, compileShader(gl.FRAGMENT_SHADER, FRAG)) gl.linkProgram(prog) gl.useProgram(prog) defer.push(() => gl.deleteProgram(prog)) // --- Quad --- const buf = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buf) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), gl.STATIC_DRAW) defer.push(() => gl.deleteBuffer(buf)) const aPos = gl.getAttribLocation(prog, 'aPos') gl.enableVertexAttribArray(aPos) gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0) gl.uniformMatrix3fv(gl.getUniformLocation(prog, 'uMatrix'), false, [2, 0, 0, 0, -2, 0, -1, 1, 1]) // --- Texture --- const tex = gl.createTexture() gl.bindTexture(gl.TEXTURE_2D, tex) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.uniform1i(gl.getUniformLocation(prog, 'uFrame'), 0) defer.push(() => gl.deleteTexture(tex)) defer.push(() => gl.getExtension('WEBGL_lose_context')?.loseContext()) } catch (error) { console.error('Init failed, using fallback...', error) setFallback(true) teardown() return } // --- Draw Loop --- let cancel: number let sized = false let start = false function tick() { cancel = requestAnimationFrame(tick) if (!start) { setLoading(false) start = true } try { if (!sized && video.videoWidth > 0) { sized = true canvas.width = video.videoWidth canvas.height = Math.floor(video.videoHeight / 2) gl.viewport(0, 0, canvas.width, canvas.height) } if (!sized) return gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video) gl.drawArrays(gl.TRIANGLES, 0, 6) } catch (error) { // bugfix for safari browsers console.error('Draw failed, using fallback...', error) setFallback(true) teardown() return } } tick() defer.push(() => cancelAnimationFrame(cancel)) video.play().catch(() => {}) // --- Disposal Functions --- function teardown() { let func while ((func = defer.shift())) { try { func() } catch (error) { console.error('Teardown Error:', error) } } } return teardown }, []) return (
{!error && loading && (
LOADING
)} {error && (
{error}
)} {!error && fallback && ( setError('Cannot Load Image')} onLoad={() => setLoading(false)} /> )} {!error && !fallback && ( <>
) }