import xStreamingPlayer from '..' import FpsCounter from '../Helper/FpsCounter' import * as THREE from 'three' const vertexShader = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); } ` const easuFragmentShader = ` /* Original:https://www.shadertoy.com/view/stXSWB by goingdigital */ uniform vec2 iResolution; uniform sampler2D iChannel0; out vec4 fragColor; /* EASU stage * * This takes a reduced resolution source, and scales it up while preserving detail. * * Updates: * stretch definition fixed. Thanks nehon for the bug report! */ vec3 FsrEasuCF(vec2 p) { return texture(iChannel0,p).rgb; } /**** EASU ****/ void FsrEasuCon( out vec4 con0, out vec4 con1, out vec4 con2, out vec4 con3, // This the rendered image resolution being upscaled vec2 inputViewportInPixels, // This is the resolution of the resource containing the input image (useful for dynamic resolution) vec2 inputSizeInPixels, // This is the display resolution which the input image gets upscaled to vec2 outputSizeInPixels ) { // Output integer position to a pixel position in viewport. con0 = vec4( inputViewportInPixels.x/outputSizeInPixels.x, inputViewportInPixels.y/outputSizeInPixels.y, .5*inputViewportInPixels.x/outputSizeInPixels.x-.5, .5*inputViewportInPixels.y/outputSizeInPixels.y-.5 ); // Viewport pixel position to normalized image space. // This is used to get upper-left of 'F' tap. con1 = vec4(1,1,1,-1)/inputSizeInPixels.xyxy; // Centers of gather4, first offset from upper-left of 'F'. // +---+---+ // | | | // +--(0)--+ // | b | c | // +---F---+---+---+ // | e | f | g | h | // +--(1)--+--(2)--+ // | i | j | k | l | // +---+---+---+---+ // | n | o | // +--(3)--+ // | | | // +---+---+ // These are from (0) instead of 'F'. con2 = vec4(-1,2,1,2)/inputSizeInPixels.xyxy; con3 = vec4(0,4,0,0)/inputSizeInPixels.xyxy; } // Filtering for a given tap for the scalar. void FsrEasuTapF( inout vec3 aC, // Accumulated color, with negative lobe. inout float aW, // Accumulated weight. vec2 off, // Pixel offset from resolve position to tap. vec2 dir, // Gradient direction. vec2 len, // Length. float lob, // Negative lobe strength. float clp, // Clipping point. vec3 c ) { // Tap color. // Rotate offset by direction. vec2 v = vec2(dot(off, dir), dot(off,vec2(-dir.y,dir.x))); // Anisotropy. v *= len; // Compute distance^2. float d2 = min(dot(v,v),clp); // Limit to the window as at corner, 2 taps can easily be outside. // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 // |_______________________________________| |_______________| // base window // The general form of the 'base' is, // (a*(b*x^2-1)^2-(a-1)) // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. float wB = .4 * d2 - 1.; float wA = lob * d2 -1.; wB *= wB; wA *= wA; wB = 1.5625*wB-.5625; float w= wB * wA; // Do weighted average. aC += c*w; aW += w; } //------------------------------------------------------------------------------------------------------------------------------ // Accumulate direction and length. void FsrEasuSetF( inout vec2 dir, inout float len, float w, float lA,float lB,float lC,float lD,float lE ) { // Direction is the '+' diff. // a // b c d // e // Then takes magnitude from abs average of both sides of 'c'. // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. float lenX = max(abs(lD - lC), abs(lC - lB)); float dirX = lD - lB; dir.x += dirX * w; lenX = clamp(abs(dirX)/lenX,0.,1.); lenX *= lenX; len += lenX * w; // Repeat for the y axis. float lenY = max(abs(lE - lC), abs(lC - lA)); float dirY = lE - lA; dir.y += dirY * w; lenY = clamp(abs(dirY) / lenY,0.,1.); lenY *= lenY; len += lenY * w; } //------------------------------------------------------------------------------------------------------------------------------ void FsrEasuF( out vec3 pix, vec2 ip, // Integer pixel position in output. // Constants generated by FsrEasuCon(). vec4 con0, // xy = output to input scale, zw = first pixel offset correction vec4 con1, vec4 con2, vec4 con3 ) { //------------------------------------------------------------------------------------------------------------------------------ // Get position of 'f'. vec2 pp = ip * con0.xy + con0.zw; // Corresponding input pixel/subpixel vec2 fp = floor(pp);// fp = source nearest pixel pp -= fp; // pp = source subpixel //------------------------------------------------------------------------------------------------------------------------------ // 12-tap kernel. // b c // e f g h // i j k l // n o // Gather 4 ordering. // a b // r g vec2 p0 = fp * con1.xy + con1.zw; // These are from p0 to avoid pulling two constants on pre-Navi hardware. vec2 p1 = p0 + con2.xy; vec2 p2 = p0 + con2.zw; vec2 p3 = p0 + con3.xy; // TextureGather is not available on WebGL2 vec4 off = vec4(-.5,.5,-.5,.5)*con1.xxyy; // textureGather to texture offsets // x=west y=east z=north w=south vec3 bC = FsrEasuCF(p0 + off.xw); float bL = bC.g + 0.5 *(bC.r + bC.b); vec3 cC = FsrEasuCF(p0 + off.yw); float cL = cC.g + 0.5 *(cC.r + cC.b); vec3 iC = FsrEasuCF(p1 + off.xw); float iL = iC.g + 0.5 *(iC.r + iC.b); vec3 jC = FsrEasuCF(p1 + off.yw); float jL = jC.g + 0.5 *(jC.r + jC.b); vec3 fC = FsrEasuCF(p1 + off.yz); float fL = fC.g + 0.5 *(fC.r + fC.b); vec3 eC = FsrEasuCF(p1 + off.xz); float eL = eC.g + 0.5 *(eC.r + eC.b); vec3 kC = FsrEasuCF(p2 + off.xw); float kL = kC.g + 0.5 *(kC.r + kC.b); vec3 lC = FsrEasuCF(p2 + off.yw); float lL = lC.g + 0.5 *(lC.r + lC.b); vec3 hC = FsrEasuCF(p2 + off.yz); float hL = hC.g + 0.5 *(hC.r + hC.b); vec3 gC = FsrEasuCF(p2 + off.xz); float gL = gC.g + 0.5 *(gC.r + gC.b); vec3 oC = FsrEasuCF(p3 + off.yz); float oL = oC.g + 0.5 *(oC.r + oC.b); vec3 nC = FsrEasuCF(p3 + off.xz); float nL = nC.g + 0.5 *(nC.r + nC.b); //------------------------------------------------------------------------------------------------------------------------------ // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). // Accumulate for bilinear interpolation. vec2 dir = vec2(0); float len = 0.; FsrEasuSetF(dir, len, (1.-pp.x)*(1.-pp.y), bL, eL, fL, gL, jL); FsrEasuSetF(dir, len, pp.x *(1.-pp.y), cL, fL, gL, hL, kL); FsrEasuSetF(dir, len, (1.-pp.x)* pp.y , fL, iL, jL, kL, nL); FsrEasuSetF(dir, len, pp.x * pp.y , gL, jL, kL, lL, oL); //------------------------------------------------------------------------------------------------------------------------------ // Normalize with approximation, and cleanup close to zero. vec2 dir2 = dir * dir; float dirR = dir2.x + dir2.y; bool zro = dirR < (1.0/32768.0); dirR = inversesqrt(dirR); dirR = zro ? 1.0 : dirR; dir.x = zro ? 1.0 : dir.x; dir *= vec2(dirR); // Transform from {0 to 2} to {0 to 1} range, and shape with square. len = len * 0.5; len *= len; // Stretch kernel {1.0 vert|horz, to sqrt(2.0) on diagonal}. float stretch = dot(dir,dir) / (max(abs(dir.x), abs(dir.y))); // Anisotropic length after rotation, // x := 1.0 lerp to 'stretch' on edges // y := 1.0 lerp to 2x on edges vec2 len2 = vec2(1. +(stretch-1.0)*len, 1. -.5 * len); // Based on the amount of 'edge', // the window shifts from +/-{sqrt(2.0) to slightly beyond 2.0}. float lob = .5 - .29 * len; // Set distance^2 clipping point to the end of the adjustable window. float clp = 1./lob; //------------------------------------------------------------------------------------------------------------------------------ // Accumulation mixed with min/max of 4 nearest. // b c // e f g h // i j k l // n o vec3 min4 = min(min(fC,gC),min(jC,kC)); vec3 max4 = max(max(fC,gC),max(jC,kC)); // Accumulation. vec3 aC = vec3(0); float aW = 0.; FsrEasuTapF(aC, aW, vec2( 0,-1)-pp, dir, len2, lob, clp, bC); FsrEasuTapF(aC, aW, vec2( 1,-1)-pp, dir, len2, lob, clp, cC); FsrEasuTapF(aC, aW, vec2(-1, 1)-pp, dir, len2, lob, clp, iC); FsrEasuTapF(aC, aW, vec2( 0, 1)-pp, dir, len2, lob, clp, jC); FsrEasuTapF(aC, aW, vec2( 0, 0)-pp, dir, len2, lob, clp, fC); FsrEasuTapF(aC, aW, vec2(-1, 0)-pp, dir, len2, lob, clp, eC); FsrEasuTapF(aC, aW, vec2( 1, 1)-pp, dir, len2, lob, clp, kC); FsrEasuTapF(aC, aW, vec2( 2, 1)-pp, dir, len2, lob, clp, lC); FsrEasuTapF(aC, aW, vec2( 2, 0)-pp, dir, len2, lob, clp, hC); FsrEasuTapF(aC, aW, vec2( 1, 0)-pp, dir, len2, lob, clp, gC); FsrEasuTapF(aC, aW, vec2( 1, 2)-pp, dir, len2, lob, clp, oC); FsrEasuTapF(aC, aW, vec2( 0, 2)-pp, dir, len2, lob, clp, nC); //------------------------------------------------------------------------------------------------------------------------------ // Normalize and dering. pix=min(max4,max(min4,aC/aW)); } void main() { vec4 fragCoord = gl_FragCoord; vec3 c; vec4 con0,con1,con2,con3; // "rendersize" refers to size of source image before upscaling. vec2 rendersize = vec2(textureSize(iChannel0, 0)); FsrEasuCon( con0, con1, con2, con3, rendersize, rendersize, iResolution ); FsrEasuF(c, fragCoord.xy, con0, con1, con2, con3); fragColor = vec4(c.xyz, 1); } ` const rcasFragmentShader = ` /* Original:https://www.shadertoy.com/view/stXSWB by goingdigital */ uniform vec2 iResolution; uniform float sharpness; uniform sampler2D iChannel0; // uniform sampler2D iChannel1; out vec4 fragColor; /* * FidelityFX Super Resolution scales up a low resolution * image, while adding fine detail. * * MIT Open License * * https://gpuopen.com/fsr * * Left: FSR processed * Right: Original texture, bilinear interpolation * * Mouse at top: Sharpness 0 stops (maximum) * Mouse at bottom: Sharpness 2 stops (minimum) * * It works in two passes- * EASU upsamples the image with a clamped Lanczos kernel. * RCAS sharpens the image at the target resolution. * * I needed to make a few changes to improve readability and * WebGL compatibility in an algorithm I don't fully understand. * Expect bugs. * * Shader not currently running for WebGL1 targets (eg. mobile Safari) * * There is kind of no point to using FSR in Shadertoy, as it renders buffers * at full target resolution. But this might be useful for WebGL based demos * running smaller-than-target render buffers. * * For sharpening with a full resolution render buffer, * FidelityFX CAS is a better option. * https://www.shadertoy.com/view/ftsXzM * * For readability and compatibility, these optimisations have been removed: * * Fast approximate inverse and inversesqrt * * textureGather fetches (not WebGL compatible) * * Multiplying by reciprocal instead of division * * Apologies to AMD for the numerous slowdowns and errors I have introduced. * */ /***** RCAS *****/ #define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) //#define FSR_RCAS_DENOISE // Input callback prototypes that need to be implemented by calling shader vec4 FsrRcasLoadF(vec2 p); //------------------------------------------------------------------------------------------------------------------------------ void FsrRcasCon( out float con, // The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. float sharpness ){ // Transform from stops to linear value. con = exp2(-sharpness); } vec3 FsrRcasF( vec2 ip, // Integer pixel position in output. float con ) { // Constant generated by RcasSetup(). // Algorithm uses minimal 3x3 pixel neighborhood. // b // d e f // h vec2 sp = vec2(ip); vec3 b = FsrRcasLoadF(sp + vec2( 0,-1)).rgb; vec3 d = FsrRcasLoadF(sp + vec2(-1, 0)).rgb; vec3 e = FsrRcasLoadF(sp).rgb; vec3 f = FsrRcasLoadF(sp+vec2( 1, 0)).rgb; vec3 h = FsrRcasLoadF(sp+vec2( 0, 1)).rgb; // Luma times 2. float bL = b.g + .5 * (b.b + b.r); float dL = d.g + .5 * (d.b + d.r); float eL = e.g + .5 * (e.b + e.r); float fL = f.g + .5 * (f.b + f.r); float hL = h.g + .5 * (h.b + h.r); // Noise detection. float nz = .25 * (bL + dL + fL + hL) - eL; nz=clamp( abs(nz) /( max(max(bL,dL),max(eL,max(fL,hL))) -min(min(bL,dL),min(eL,min(fL,hL))) ), 0., 1. ); nz=1.-.5*nz; // Min and max of ring. vec3 mn4 = min(b, min(f, h)); vec3 mx4 = max(b, max(f, h)); // Immediate constants for peak range. vec2 peakC = vec2(1., -4.); // Limiters, these need to be high precision RCPs. vec3 hitMin = mn4 / (4. * mx4); vec3 hitMax = (peakC.x - mx4) / (4.* mn4 + peakC.y); vec3 lobeRGB = max(-hitMin, hitMax); float lobe = max( -FSR_RCAS_LIMIT, min(max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)), 0.) )*con; // Apply noise removal. #ifdef FSR_RCAS_DENOISE lobe *= nz; #endif // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. return (lobe * (b + d + h + f) + e) / (4. * lobe + 1.); } vec4 FsrRcasLoadF(vec2 p) { return texture(iChannel0,p/iResolution.xy); } void main() { vec4 fragCoord = gl_FragCoord; // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord.xy/iResolution.xy; // Set up constants float con; // float sharpness = 0.2; // float division = 0.5+.3*sin(iTime*.3); // if (iMouse.z > 0.) { // sharpness = 2.-2.*pow( iMouse.y / iResolution.y,.25); // division = iMouse.x / iResolution.x; // } FsrRcasCon(con,sharpness); // Perform RCAS pass vec3 col = FsrRcasF(fragCoord.xy, con); // Source image // vec2 uv1; // Bilinear interpolation // uv1 = fragCoord.xy/iResolution.xy; // Nearest pixel // uv1 = (floor(vec2(textureSize(iChannel1,0))*fragCoord.xy/iResolution.xy)+.5)/vec2(textureSize(iChannel1,0)); // vec3 col_orig = texture(iChannel1,uv1).xyz; // Comparison tool // if (fragCoord.x/iResolution.x > division) col = col_orig; // if (abs(fragCoord.x/iResolution.x - division)<.005) col = vec3(0); fragColor = vec4(col,1); } ` globalThis.resolution = '' const INTERVAL_MS = 30000 // 30s let lastExecutionTime = 0 export default class VideoComponent { _client:xStreamingPlayer _videoSource _mediaSource _videoRender _focusEvent _framekeyInterval _videoFps _video _canvasPlayer _rcasMaterial constructor(client:xStreamingPlayer) { this._client = client this._video = null this._rcasMaterial = null } create(srcObject) { console.log('xStreamingPlayer Component/Video.ts - Create media element') this._videoFps = new FpsCounter(this._client, 'video') const videoHolder = document.getElementById(this._client._elementHolder) if(videoHolder !== null){ const videoRender = document.createElement('video') videoRender.id = this.getElementId() videoRender.srcObject = srcObject videoRender.style.touchAction = 'none' videoRender.style.width = '100%' videoRender.style.height = '100%' if (this._client._video_format === 'Stretch') { videoRender.style.objectFit = 'fill' } else if (this._client._video_format === 'Zoom') { videoRender.style.objectFit = 'cover' } else { videoRender.style.objectFit = 'contain' } // videoRender.style.backgroundColor = 'black' // videoRender.muted = true videoRender.autoplay = true videoRender.muted = true videoRender.playsInline = true // videoHolder.style.aspectRatio = '16 / 9' setInterval(() => { videoRender.play() }, 4) videoRender.addEventListener('loadedmetadata', () => { const videoWidth = videoRender.videoWidth const videoHeight = videoRender.videoHeight globalThis.resolution = videoWidth + ' x ' + videoHeight }) videoRender.onclick = () => { videoRender.play() this._client._audioComponent._audioRender.play() } const serverDataLoop = (t, i) => { videoRender.requestVideoFrameCallback(serverDataLoop) const currentTime = new Date().getTime() if (currentTime - lastExecutionTime >= INTERVAL_MS) { lastExecutionTime = currentTime if (this._client.getChannelProcessor('input')) { this._videoFps.count() this._client.getChannelProcessor('input').addProcessedFrame({ serverDataKey: i.rtpTimestamp, firstFramePacketArrivalTimeMs: i.receiveTime, frameSubmittedTimeMs: i.receiveTime, frameDecodedTimeMs: i.expectedDisplayTime, frameRenderedTimeMs: i.expectedDisplayTime, }) } } } videoRender.requestVideoFrameCallback(serverDataLoop) this._videoRender = videoRender videoHolder.appendChild(videoRender) this._videoFps.start() // Pointer / Mouse events videoRender.addEventListener('pointermove', (e) => this._client.getChannelProcessor('input')?.onPointerMove(e), { passive: false }), videoRender.addEventListener('pointerdown', (e) => this._client.getChannelProcessor('input')?.onPointerClick(e), { passive: false }), videoRender.addEventListener('pointerup', (e) => this._client.getChannelProcessor('input')?.onPointerClick(e), { passive: false }), videoRender.addEventListener('pointercancel', (e) => this._client.getChannelProcessor('input')?.onPointerClick(e), { passive: false }), videoRender.addEventListener('touchstart', (e) => this._client.getChannelProcessor('input')?.onTouchEvent(e), { passive: false }), videoRender.addEventListener('touchmove', (e) => this._client.getChannelProcessor('input')?.onTouchEvent(e), { passive: false }), videoRender.addEventListener('touchend', (e) => this._client.getChannelProcessor('input')?.onTouchEvent(e), { passive: false }), videoRender.addEventListener('touchcancel', (e) => this._client.getChannelProcessor('input')?.onTouchEvent(e), { passive: false }), videoRender.addEventListener('wheel', (e) => this._client.getChannelProcessor('input')?.onPointerScroll(e), { passive: false }) // Keyboard events window.addEventListener('keydown', (e) => { this._client.getChannelProcessor('input')?.onKeyDown(e) }) window.addEventListener('keyup', (e) => { this._client.getChannelProcessor('input')?.onKeyUp(e) }) // videoHolder.addEventListener("touchmove", (e) => this._client.getChannelProcessor('input').onPointerMove(e)), // videoHolder.addEventListener("touchstart", (e) => this._client.getChannelProcessor('input').onPointerClick(e)), // videoHolder.addEventListener("touchend", (e) => this._client.getChannelProcessor('input').onPointerClick(e)), videoRender.play().then(() => { // }).catch((error) => { console.log('xStreamingPlayer Component/Video.ts - Error executing play() on videoRender:', error) }) this._video = videoRender } else { console.log('xStreamingPlayer Component/Video.ts - Error fetching videoholder: div#'+this._client._elementHolder) } console.log('xStreamingPlayer Component/Video.ts - Media element created') } getElementId(){ return 'xStreamingPlayer_'+this._client._elementHolderRandom+'_videoRender' } getSource() { return this._videoSource } startFSR() { if (!this._video) { return } let DEFAULT_SHARPNESS = 0.2 if (this._client._fsr_sharpness !== undefined) { DEFAULT_SHARPNESS = this._client._fsr_sharpness / 10 } console.log('FSR sharpness:', DEFAULT_SHARPNESS) // dom size variables let width = 0 let height = 0 let aspect = 0 // dom const canvas = document.getElementById('canvas') as HTMLCanvasElement // calculate initial size aspect = this._video.videoWidth / this._video.videoHeight const video_format = this._client._video_format if (video_format && video_format.indexOf(':') !== -1) { const ratioParts = video_format.split(':') const ratioWidth = parseFloat(ratioParts[0]) const ratioHeight = parseFloat(ratioParts[1]) aspect = ratioWidth / ratioHeight } if (video_format === 'Stretch' || video_format === 'Zoom') { width = this._video.clientWidth height = this._video.clientHeight } else { const videoRect = this._video.getBoundingClientRect() const videoAspectRatio = this._video.videoWidth / this._video.videoHeight const containerAspectRatio = videoRect.width / videoRect.height let displayWidth, displayHeight if (videoAspectRatio > containerAspectRatio) { // 视频更宽,上下有黑边 displayWidth = videoRect.width displayHeight = videoRect.width / videoAspectRatio } else { // 视频更高,左右有黑边 displayWidth = videoRect.height * videoAspectRatio displayHeight = videoRect.height } width = displayWidth height = displayHeight } // camera const camera = new THREE.OrthographicCamera( width / -2, width / 2, height / 2, height / -2, ) // common object const videoTexture = new THREE.VideoTexture(this._video) const geometry = new THREE.PlaneGeometry(2, 2) // EASU stage setting const easuScene = new THREE.Scene() const easuMaterial = new THREE.ShaderMaterial({ uniforms: { iChannel0: { value: videoTexture, }, iResolution: { value: new THREE.Vector2(width, height), }, }, vertexShader, fragmentShader: easuFragmentShader, glslVersion: THREE.GLSL3, }) const easuMesh = new THREE.Mesh(geometry, easuMaterial) easuScene.add(easuMesh) easuScene.add(camera) // create renderer const renderer = new THREE.WebGLRenderer({ antialias: true, canvas }) const dpr = window.devicePixelRatio || 1 renderer.setSize(width, height) renderer.setPixelRatio(dpr) renderer.setAnimationLoop(animation) canvas.width = width * dpr canvas.height = height * dpr canvas.style.width = width + 'px' canvas.style.height = height + 'px' // offscreen render target const renderTarget = new THREE.WebGLRenderTarget(width * dpr, height * dpr, { depthBuffer: false, stencilBuffer: false, }) // RCAS stage setting const rcasScene = new THREE.Scene() this._rcasMaterial = new THREE.ShaderMaterial({ uniforms: { iChannel0: { value: videoTexture, }, // I think that sampling source texture only seems to be better quality without sampleing EASU pass.🤔 // I can't make it out that is caused by ShaderToy porting or my misunderstanding. // Set iChannel0.value to renderTarget.texture if you want to process correctly with the original algorithm. iChannel1: { value: renderTarget.texture, }, iResolution: { value: new THREE.Vector2(width * dpr, height * dpr), }, sharpness: { value: DEFAULT_SHARPNESS }, }, vertexShader, fragmentShader: rcasFragmentShader, glslVersion: THREE.GLSL3, }) const rcasMesh = new THREE.Mesh(geometry.clone(), this._rcasMaterial) rcasScene.add(rcasMesh) // tick function animation() { // render EASU stage renderer.setRenderTarget(renderTarget) renderer.render(easuScene, camera) // render RCAS stage renderer.setRenderTarget(null) renderer.render(rcasScene, camera) } // check dom resize const resizeObserver = new ResizeObserver(() => { const video_format = this._client._video_format if (video_format === 'Stretch' || video_format === 'Zoom') { width = this._video.clientWidth height = this._video.clientHeight } else { const videoRect = this._video.getBoundingClientRect() const videoAspectRatio = this._video.videoWidth / this._video.videoHeight const containerAspectRatio = videoRect.width / videoRect.height let displayWidth, displayHeight if (videoAspectRatio > containerAspectRatio) { // 视频更宽,上下有黑边 displayWidth = videoRect.width displayHeight = videoRect.width / videoAspectRatio } else { // 视频更高,左右有黑边 displayWidth = videoRect.height * videoAspectRatio displayHeight = videoRect.height } width = displayWidth height = displayHeight } const dpr = window.devicePixelRatio || 1 renderer.setSize(width, height) renderer.setPixelRatio(dpr) canvas.width = width * dpr canvas.height = height * dpr canvas.style.width = width + 'px' canvas.style.height = height + 'px' // update uniforms easuMaterial.uniforms['iResolution'].value = new THREE.Vector2(width * dpr, height * dpr) this._rcasMaterial.uniforms['iResolution'].value = new THREE.Vector2(width * dpr, height * dpr) }) resizeObserver.observe(document.body) } setFsrSharpnessDynamic(value: number) { if (this._rcasMaterial) { this._rcasMaterial.uniforms['sharpness'].value = value / 10 } } createMediaSource() { const mediaSource = new MediaSource() // @TODO: MediaSource (MSE) is not available on iOS. const videoSourceUrl = window.URL.createObjectURL(mediaSource) mediaSource.addEventListener('sourceopen', () => { console.log('xStreamingPlayer Component/Video.ts - MediaSource opened. Attaching videoSourceBuffer...') const videoSourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42c020"') videoSourceBuffer.mode = 'sequence' videoSourceBuffer.addEventListener('error', (event) => { console.log('xStreamingPlayer Component/Video.ts - Error video...', event) }) this._videoSource = videoSourceBuffer }) this._mediaSource = mediaSource return videoSourceUrl } destroy() { if(this._videoRender){ this._videoRender.pause() this._videoRender.remove() } this._videoFps && this._videoFps.stop() if (this._canvasPlayer) { this._canvasPlayer.destroy() this._canvasPlayer = null } delete this._mediaSource delete this._videoRender delete this._videoSource document.getElementById(this.getElementId())?.remove() console.log('xStreamingPlayer Component/Video.ts - Cleaning up Video element') } }