rc-1
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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<HTMLCanvasElement>(null)
|
||||
const videoRef = useRef<HTMLVideoElement>(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 (
|
||||
<div className={`media-canvas ${background ? 'background' : ''}`}>
|
||||
{!error && loading && (
|
||||
<div className="popup">
|
||||
<img className="icon" src={vectorIconThrobber} />
|
||||
<span className="hint">LOADING</span>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="popup">
|
||||
<img className="icon" src={vectorIconCross} />
|
||||
<span className="hint">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
{!error && fallback && (
|
||||
<img
|
||||
className="render"
|
||||
src={`${CDN_BASE}/${id}/standard.avif`}
|
||||
onError={() => setError('Cannot Load Image')}
|
||||
onLoad={() => setLoading(false)}
|
||||
/>
|
||||
)}
|
||||
{!error && !fallback && (
|
||||
<>
|
||||
<video
|
||||
ref={videoRef}
|
||||
crossOrigin="anonymous"
|
||||
className="decode"
|
||||
src={`${CDN_BASE}/${id}/alpha.webm`}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<canvas ref={canvasRef} className="render" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user