mirror of
				https://github.com/redphx/better-xcloud.git
				synced 2025-10-31 14:04:07 +01:00 
			
		
		
		
	Optimize Clarity Boost shader
This commit is contained in:
		
							
								
								
									
										6
									
								
								build.ts
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								build.ts
									
									
									
									
									
								
							| @@ -76,6 +76,12 @@ const postProcess = (str: string): string => { | ||||
|     // Remove blank lines | ||||
|     str = str.replaceAll(/\n([\s]*)\n/g, "\n"); | ||||
|  | ||||
|     // Minify WebGL shaders & JS strings | ||||
|     // Replace "\n     " with "\n" | ||||
|     str = str.replaceAll(/\\n+\s*/g, '\\n'); | ||||
|     // Remove comment line | ||||
|     str = str.replaceAll(/\\n\/\/.*?(?=\\n)/g, ''); | ||||
|  | ||||
|     assert(str.includes('/* ADDITIONAL CODE */')); | ||||
|     assert(str.includes('window.BX_EXPOSED = BxExposed')); | ||||
|     assert(str.includes('window.BxEvent = BxEvent')); | ||||
|   | ||||
							
								
								
									
										180
									
								
								dist/better-xcloud.lite.user.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										180
									
								
								dist/better-xcloud.lite.user.js
									
									
									
									
										vendored
									
									
								
							| @@ -1580,7 +1580,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { | ||||
|       }, | ||||
|       suggest: { | ||||
|         lowest: 0, | ||||
|         highest: 4 | ||||
|         highest: 2 | ||||
|       } | ||||
|     }, | ||||
|     video_ratio: { | ||||
| @@ -5033,86 +5033,86 @@ function patchSdpBitrate(sdp, video, audio) { | ||||
|   } | ||||
|   return lines.join("\r\n"); | ||||
| } | ||||
| var clarity_boost_default = "attribute vec2 position;\n\nvoid main() {\n    gl_Position = vec4(position, 0, 1);\n}\n"; | ||||
| var clarity_boost_default2 = "const int FILTER_UNSHARP_MASKING = 1;\nconst int FILTER_CAS = 2;\n\nprecision highp float;\nuniform sampler2D data;\nuniform vec2 iResolution;\n\nuniform int filterId;\nuniform float sharpenFactor;\nuniform float brightness;\nuniform float contrast;\nuniform float saturation;\n\nvec3 textureAt(sampler2D tex, vec2 coord) {\n    return texture2D(tex, coord / iResolution.xy).rgb;\n}\n\nvec3 clarityBoost(sampler2D tex, vec2 coord)\n{\n    // Load a collection of samples in a 3x3 neighorhood, where e is the current pixel.\n    // a b c\n    // d e f\n    // g h i\n    vec3 a = textureAt(tex, coord + vec2(-1, 1));\n    vec3 b = textureAt(tex, coord + vec2(0, 1));\n    vec3 c = textureAt(tex, coord + vec2(1, 1));\n\n    vec3 d = textureAt(tex, coord + vec2(-1, 0));\n    vec3 e = textureAt(tex, coord);\n    vec3 f = textureAt(tex, coord + vec2(1, 0));\n\n    vec3 g = textureAt(tex, coord + vec2(-1, -1));\n    vec3 h = textureAt(tex, coord + vec2(0, -1));\n    vec3 i = textureAt(tex, coord + vec2(1, -1));\n\n    if (filterId == FILTER_CAS) {\n        // Soft min and max.\n        //  a b c             b\n        //  d e f * 0.5  +  d e f * 0.5\n        //  g h i             h\n        // These are 2.0x bigger (factored out the extra multiply).\n        vec3 minRgb = min(min(min(d, e), min(f, b)), h);\n        vec3 minRgb2 = min(min(a, c), min(g, i));\n        minRgb += min(minRgb, minRgb2);\n\n        vec3 maxRgb = max(max(max(d, e), max(f, b)), h);\n        vec3 maxRgb2 = max(max(a, c), max(g, i));\n        maxRgb += max(maxRgb, maxRgb2);\n\n        // Smooth minimum distance to signal limit divided by smooth max.\n        vec3 reciprocalMaxRgb = 1.0 / maxRgb;\n        vec3 amplifyRgb = clamp(min(minRgb, 2.0 - maxRgb) * reciprocalMaxRgb, 0.0, 1.0);\n\n        // Shaping amount of sharpening.\n        amplifyRgb = inversesqrt(amplifyRgb);\n\n        float contrast = 0.8;\n        float peak = -3.0 * contrast + 8.0;\n        vec3 weightRgb = -(1.0 / (amplifyRgb * peak));\n\n        vec3 reciprocalWeightRgb = 1.0 / (4.0 * weightRgb + 1.0);\n\n        //                0 w 0\n        // Filter shape:  w 1 w\n        //                0 w 0\n        vec3 window = (b + d) + (f + h);\n        vec3 outColor = clamp((window * weightRgb + e) * reciprocalWeightRgb, 0.0, 1.0);\n\n        outColor = mix(e, outColor, sharpenFactor / 2.0);\n\n        return outColor;\n    } else if (filterId == FILTER_UNSHARP_MASKING) {\n        vec3 gaussianBlur = (a * 1.0 + b * 2.0 + c * 1.0 +\n            d * 2.0 + e * 4.0 + f * 2.0 +\n            g * 1.0 + h * 2.0 + i * 1.0) / 16.0;\n\n        // Return edge detection\n        return e + (e - gaussianBlur) * sharpenFactor / 3.0;\n    }\n\n    return e;\n}\n\nvec3 adjustBrightness(vec3 color) {\n    return (1.0 + brightness) * color;\n}\n\nvec3 adjustContrast(vec3 color) {\n    return 0.5 + (1.0 + contrast) * (color - 0.5);\n}\n\nvec3 adjustSaturation(vec3 color) {\n    const vec3 luminosityFactor = vec3(0.2126, 0.7152, 0.0722);\n    vec3 grayscale = vec3(dot(color, luminosityFactor));\n\n    return mix(grayscale, color, 1.0 + saturation);\n}\n\nvoid main() {\n    vec3 color;\n\n    if (sharpenFactor > 0.0) {\n        color = clarityBoost(data, gl_FragCoord.xy);\n    } else {\n        color = textureAt(data, gl_FragCoord.xy);\n    }\n\n    if (saturation != 0.0) {\n        color = adjustSaturation(color);\n    }\n\n    if (contrast != 0.0) {\n        color = adjustContrast(color);\n    }\n\n    if (brightness != 0.0) {\n        color = adjustBrightness(color);\n    }\n\n    gl_FragColor = vec4(color, 1.0);\n}\n"; | ||||
| var clarity_boost_default = "attribute vec4 position;\n\nvoid main() {\ngl_Position = position;\n}\n"; | ||||
| var clarity_boost_default2 = "precision mediump float;\nuniform sampler2D data;\nuniform vec2 iResolution;\n\nconst int FILTER_UNSHARP_MASKING = 1;\nconst int FILTER_CAS = 2;\n\nconst float CAS_CONTRAST_PEAK = (-3.0 * 0.8 + 8.0);\n\nconst vec3 LUMINOSITY_FACTOR = vec3(0.2126, 0.7152, 0.0722);\n\nuniform int filterId;\nuniform float sharpenFactor;\nuniform float brightness;\nuniform float contrast;\nuniform float saturation;\n\nvec3 clarityBoost(sampler2D tex, vec2 coord) {\nvec2 texelSize = 1.0 / iResolution.xy;\n\nvec3 a = texture2D(tex, coord + texelSize * vec2(-1, 1)).rgb;\nvec3 b = texture2D(tex, coord + texelSize * vec2(0, 1)).rgb;\nvec3 c = texture2D(tex, coord + texelSize * vec2(1, 1)).rgb;\n\nvec3 d = texture2D(tex, coord + texelSize * vec2(-1, 0)).rgb;\nvec3 e = texture2D(tex, coord).rgb;\nvec3 f = texture2D(tex, coord + texelSize * vec2(1, 0)).rgb;\n\nvec3 g = texture2D(tex, coord + texelSize * vec2(-1, -1)).rgb;\nvec3 h = texture2D(tex, coord + texelSize * vec2(0, -1)).rgb;\nvec3 i = texture2D(tex, coord + texelSize * vec2(1, -1)).rgb;\n\nif (filterId == FILTER_CAS) {\nvec3 minRgb = min(min(min(d, e), min(f, b)), h);\nvec3 minRgb2 = min(min(a, c), min(g, i));\nminRgb += min(minRgb, minRgb2);\n\nvec3 maxRgb = max(max(max(d, e), max(f, b)), h);\nvec3 maxRgb2 = max(max(a, c), max(g, i));\nmaxRgb += max(maxRgb, maxRgb2);\n\nvec3 reciprocalMaxRgb = 1.0 / maxRgb;\nvec3 amplifyRgb = clamp(min(minRgb, 2.0 - maxRgb) * reciprocalMaxRgb, 0.0, 1.0);\n\namplifyRgb = inversesqrt(amplifyRgb);\n\nvec3 weightRgb = -(1.0 / (amplifyRgb * CAS_CONTRAST_PEAK));\nvec3 reciprocalWeightRgb = 1.0 / (4.0 * weightRgb + 1.0);\n\nvec3 window = (b + d) + (f + h);\nvec3 outColor = clamp((window * weightRgb + e) * reciprocalWeightRgb, 0.0, 1.0);\n\noutColor = mix(e, outColor, sharpenFactor / 2.0);\n\nreturn outColor;\n} else if (filterId == FILTER_UNSHARP_MASKING) {\nvec3 gaussianBlur = (a + c + g + i) * 1.0 +\n(b + d + f + h) * 2.0 +\ne * 4.0;\ngaussianBlur /= 16.0;\n\nreturn e + (e - gaussianBlur) * sharpenFactor / 3.0;\n}\n\nreturn e;\n}\n\nvoid main() {\nvec3 color;\nvec2 uv = gl_FragCoord.xy / iResolution.xy;\n\nif (sharpenFactor > 0.0) {\ncolor = clarityBoost(data, uv);\n} else {\ncolor = texture2D(data, uv).rgb;\n}\n\nif (saturation != 1.0) {\nvec3 grayscale = vec3(dot(color, LUMINOSITY_FACTOR));\ncolor = mix(grayscale, color, saturation);\n}\n\nif (contrast != 1.0) {\ncolor = 0.5 + contrast * (color - 0.5);\n}\n\nif (brightness != 1.0) {\ncolor = brightness * color;\n}\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; | ||||
| var LOG_TAG3 = "WebGL2Player"; | ||||
| class WebGL2Player { | ||||
|   #$video; | ||||
|   #$canvas; | ||||
|   #gl = null; | ||||
|   #resources = []; | ||||
|   #program = null; | ||||
|   #stopped = !1; | ||||
|   #options = { | ||||
|   $video; | ||||
|   $canvas; | ||||
|   gl = null; | ||||
|   resources = []; | ||||
|   program = null; | ||||
|   stopped = !1; | ||||
|   options = { | ||||
|     filterId: 1, | ||||
|     sharpenFactor: 0, | ||||
|     brightness: 0, | ||||
|     contrast: 0, | ||||
|     saturation: 0 | ||||
|   }; | ||||
|   #animFrameId = null; | ||||
|   animFrameId = null; | ||||
|   constructor($video) { | ||||
|     BxLogger.info(LOG_TAG3, "Initialize"), this.#$video = $video; | ||||
|     BxLogger.info(LOG_TAG3, "Initialize"), this.$video = $video; | ||||
|     const $canvas = document.createElement("canvas"); | ||||
|     $canvas.width = $video.videoWidth, $canvas.height = $video.videoHeight, this.#$canvas = $canvas, this.#setupShaders(), this.#setupRendering(), $video.insertAdjacentElement("afterend", $canvas); | ||||
|     $canvas.width = $video.videoWidth, $canvas.height = $video.videoHeight, this.$canvas = $canvas, this.setupShaders(), this.setupRendering(), $video.insertAdjacentElement("afterend", $canvas); | ||||
|   } | ||||
|   setFilter(filterId, update = !0) { | ||||
|     this.#options.filterId = filterId, update && this.updateCanvas(); | ||||
|     this.options.filterId = filterId, update && this.updateCanvas(); | ||||
|   } | ||||
|   setSharpness(sharpness, update = !0) { | ||||
|     this.#options.sharpenFactor = sharpness, update && this.updateCanvas(); | ||||
|     this.options.sharpenFactor = sharpness, update && this.updateCanvas(); | ||||
|   } | ||||
|   setBrightness(brightness, update = !0) { | ||||
|     this.#options.brightness = (brightness - 100) / 100, update && this.updateCanvas(); | ||||
|     this.options.brightness = 1 + (brightness - 100) / 100, update && this.updateCanvas(); | ||||
|   } | ||||
|   setContrast(contrast, update = !0) { | ||||
|     this.#options.contrast = (contrast - 100) / 100, update && this.updateCanvas(); | ||||
|     this.options.contrast = 1 + (contrast - 100) / 100, update && this.updateCanvas(); | ||||
|   } | ||||
|   setSaturation(saturation, update = !0) { | ||||
|     this.#options.saturation = (saturation - 100) / 100, update && this.updateCanvas(); | ||||
|     this.options.saturation = 1 + (saturation - 100) / 100, update && this.updateCanvas(); | ||||
|   } | ||||
|   getCanvas() { | ||||
|     return this.#$canvas; | ||||
|     return this.$canvas; | ||||
|   } | ||||
|   updateCanvas() { | ||||
|     const gl = this.#gl, program = this.#program; | ||||
|     gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.#$canvas.width, this.#$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.#options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.#options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.#options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.#options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.#options.saturation); | ||||
|     const gl = this.gl, program = this.program; | ||||
|     gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.$canvas.width, this.$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.options.saturation); | ||||
|   } | ||||
|   drawFrame() { | ||||
|     const gl = this.#gl, $video = this.#$video; | ||||
|     const gl = this.gl, $video = this.$video; | ||||
|     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, $video), gl.drawArrays(gl.TRIANGLES, 0, 6); | ||||
|   } | ||||
|   #setupRendering() { | ||||
|   setupRendering() { | ||||
|     let animate; | ||||
|     if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) { | ||||
|       const $video = this.#$video; | ||||
|       const $video = this.$video; | ||||
|       animate = () => { | ||||
|         if (this.#stopped) return; | ||||
|         this.drawFrame(), this.#animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|       }, this.#animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|         if (this.stopped) return; | ||||
|         this.drawFrame(), this.animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|       }, this.animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|     } else animate = () => { | ||||
|         if (this.#stopped) return; | ||||
|         this.drawFrame(), this.#animFrameId = requestAnimationFrame(animate); | ||||
|       }, this.#animFrameId = requestAnimationFrame(animate); | ||||
|         if (this.stopped) return; | ||||
|         this.drawFrame(), this.animFrameId = requestAnimationFrame(animate); | ||||
|       }, this.animFrameId = requestAnimationFrame(animate); | ||||
|   } | ||||
|   #setupShaders() { | ||||
|   setupShaders() { | ||||
|     BxLogger.info(LOG_TAG3, "Setting up", getPref("video_power_preference")); | ||||
|     const gl = this.#$canvas.getContext("webgl", { | ||||
|     const gl = this.$canvas.getContext("webgl", { | ||||
|       isBx: !0, | ||||
|       antialias: !0, | ||||
|       alpha: !1, | ||||
|       powerPreference: getPref("video_power_preference") | ||||
|     }); | ||||
|     this.#gl = gl, gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferWidth); | ||||
|     this.gl = gl, gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferWidth); | ||||
|     const vShader = gl.createShader(gl.VERTEX_SHADER); | ||||
|     gl.shaderSource(vShader, clarity_boost_default), gl.compileShader(vShader); | ||||
|     const fShader = gl.createShader(gl.FRAGMENT_SHADER); | ||||
|     gl.shaderSource(fShader, clarity_boost_default2), gl.compileShader(fShader); | ||||
|     const program = gl.createProgram(); | ||||
|     if (this.#program = program, gl.attachShader(program, vShader), gl.attachShader(program, fShader), gl.linkProgram(program), gl.useProgram(program), !gl.getProgramParameter(program, gl.LINK_STATUS)) console.error(`Link failed: ${gl.getProgramInfoLog(program)}`), console.error(`vs info-log: ${gl.getShaderInfoLog(vShader)}`), console.error(`fs info-log: ${gl.getShaderInfoLog(fShader)}`); | ||||
|     if (this.program = program, gl.attachShader(program, vShader), gl.attachShader(program, fShader), gl.linkProgram(program), gl.useProgram(program), !gl.getProgramParameter(program, gl.LINK_STATUS)) console.error(`Link failed: ${gl.getProgramInfoLog(program)}`), console.error(`vs info-log: ${gl.getShaderInfoLog(vShader)}`), console.error(`fs info-log: ${gl.getShaderInfoLog(fShader)}`); | ||||
|     this.updateCanvas(); | ||||
|     const buffer = gl.createBuffer(); | ||||
|     this.#resources.push(buffer), gl.bindBuffer(gl.ARRAY_BUFFER, buffer), gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | ||||
|     this.resources.push(buffer), gl.bindBuffer(gl.ARRAY_BUFFER, buffer), gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | ||||
|       -1, | ||||
|       -1, | ||||
|       1, | ||||
| @@ -5127,51 +5127,51 @@ class WebGL2Player { | ||||
|       1 | ||||
|     ]), gl.STATIC_DRAW), gl.enableVertexAttribArray(0), gl.vertexAttribPointer(0, 2, gl.FLOAT, !1, 0, 0); | ||||
|     const texture = gl.createTexture(); | ||||
|     this.#resources.push(texture), gl.bindTexture(gl.TEXTURE_2D, texture), gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !0), 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(program, "data"), 0), gl.activeTexture(gl.TEXTURE0); | ||||
|     this.resources.push(texture), gl.bindTexture(gl.TEXTURE_2D, texture), gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !0), 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(program, "data"), 0), gl.activeTexture(gl.TEXTURE0); | ||||
|   } | ||||
|   resume() { | ||||
|     this.stop(), this.#stopped = !1, BxLogger.info(LOG_TAG3, "Resume"), this.#$canvas.classList.remove("bx-gone"), this.#setupRendering(); | ||||
|     this.stop(), this.stopped = !1, BxLogger.info(LOG_TAG3, "Resume"), this.$canvas.classList.remove("bx-gone"), this.setupRendering(); | ||||
|   } | ||||
|   stop() { | ||||
|     if (BxLogger.info(LOG_TAG3, "Stop"), this.#$canvas.classList.add("bx-gone"), this.#stopped = !0, this.#animFrameId) { | ||||
|       if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) this.#$video.cancelVideoFrameCallback(this.#animFrameId); | ||||
|       else cancelAnimationFrame(this.#animFrameId); | ||||
|       this.#animFrameId = null; | ||||
|     if (BxLogger.info(LOG_TAG3, "Stop"), this.$canvas.classList.add("bx-gone"), this.stopped = !0, this.animFrameId) { | ||||
|       if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) this.$video.cancelVideoFrameCallback(this.animFrameId); | ||||
|       else cancelAnimationFrame(this.animFrameId); | ||||
|       this.animFrameId = null; | ||||
|     } | ||||
|   } | ||||
|   destroy() { | ||||
|     BxLogger.info(LOG_TAG3, "Destroy"), this.stop(); | ||||
|     const gl = this.#gl; | ||||
|     const gl = this.gl; | ||||
|     if (gl) { | ||||
|       gl.getExtension("WEBGL_lose_context")?.loseContext(); | ||||
|       for (let resource of this.#resources) | ||||
|       for (let resource of this.resources) | ||||
|         if (resource instanceof WebGLProgram) gl.useProgram(null), gl.deleteProgram(resource); | ||||
|         else if (resource instanceof WebGLShader) gl.deleteShader(resource); | ||||
|         else if (resource instanceof WebGLTexture) gl.deleteTexture(resource); | ||||
|         else if (resource instanceof WebGLBuffer) gl.deleteBuffer(resource); | ||||
|       this.#gl = null; | ||||
|       this.gl = null; | ||||
|     } | ||||
|     if (this.#$canvas.isConnected) this.#$canvas.parentElement?.removeChild(this.#$canvas); | ||||
|     this.#$canvas.width = 1, this.#$canvas.height = 1; | ||||
|     if (this.$canvas.isConnected) this.$canvas.parentElement?.removeChild(this.$canvas); | ||||
|     this.$canvas.width = 1, this.$canvas.height = 1; | ||||
|   } | ||||
| } | ||||
| class StreamPlayer { | ||||
|   #$video; | ||||
|   #playerType = "default"; | ||||
|   #options = {}; | ||||
|   #webGL2Player = null; | ||||
|   #$videoCss = null; | ||||
|   #$usmMatrix = null; | ||||
|   $video; | ||||
|   playerType = "default"; | ||||
|   options = {}; | ||||
|   webGL2Player = null; | ||||
|   $videoCss = null; | ||||
|   $usmMatrix = null; | ||||
|   constructor($video, type, options) { | ||||
|     this.#setupVideoElements(), this.#$video = $video, this.#options = options || {}, this.setPlayerType(type); | ||||
|     this.setupVideoElements(), this.$video = $video, this.options = options || {}, this.setPlayerType(type); | ||||
|   } | ||||
|   #setupVideoElements() { | ||||
|     if (this.#$videoCss = document.getElementById("bx-video-css"), this.#$videoCss) { | ||||
|       this.#$usmMatrix = this.#$videoCss.querySelector("#bx-filter-usm-matrix"); | ||||
|   setupVideoElements() { | ||||
|     if (this.$videoCss = document.getElementById("bx-video-css"), this.$videoCss) { | ||||
|       this.$usmMatrix = this.$videoCss.querySelector("#bx-filter-usm-matrix"); | ||||
|       return; | ||||
|     } | ||||
|     const $fragment = document.createDocumentFragment(); | ||||
|     this.#$videoCss = CE("style", { id: "bx-video-css" }), $fragment.appendChild(this.#$videoCss); | ||||
|     this.$videoCss = CE("style", { id: "bx-video-css" }), $fragment.appendChild(this.$videoCss); | ||||
|     const $svg = CE("svg", { | ||||
|       id: "bx-video-filters", | ||||
|       xmlns: "http://www.w3.org/2000/svg", | ||||
| @@ -5179,31 +5179,31 @@ class StreamPlayer { | ||||
|     }, CE("defs", { xmlns: "http://www.w3.org/2000/svg" }, CE("filter", { | ||||
|       id: "bx-filter-usm", | ||||
|       xmlns: "http://www.w3.org/2000/svg" | ||||
|     }, this.#$usmMatrix = CE("feConvolveMatrix", { | ||||
|     }, this.$usmMatrix = CE("feConvolveMatrix", { | ||||
|       id: "bx-filter-usm-matrix", | ||||
|       order: "3", | ||||
|       xmlns: "http://www.w3.org/2000/svg" | ||||
|     })))); | ||||
|     $fragment.appendChild($svg), document.documentElement.appendChild($fragment); | ||||
|   } | ||||
|   #getVideoPlayerFilterStyle() { | ||||
|     const filters = [], sharpness = this.#options.sharpness || 0; | ||||
|     if (this.#options.processing === "usm" && sharpness != 0) { | ||||
|   getVideoPlayerFilterStyle() { | ||||
|     const filters = [], sharpness = this.options.sharpness || 0; | ||||
|     if (this.options.processing === "usm" && sharpness != 0) { | ||||
|       const matrix = `0 -1 0 -1 ${(7 - (sharpness / 2 - 1) * 0.5).toFixed(1)} -1 0 -1 0`; | ||||
|       this.#$usmMatrix?.setAttributeNS(null, "kernelMatrix", matrix), filters.push("url(#bx-filter-usm)"); | ||||
|       this.$usmMatrix?.setAttributeNS(null, "kernelMatrix", matrix), filters.push("url(#bx-filter-usm)"); | ||||
|     } | ||||
|     const saturation = this.#options.saturation || 100; | ||||
|     const saturation = this.options.saturation || 100; | ||||
|     if (saturation != 100) filters.push(`saturate(${saturation}%)`); | ||||
|     const contrast = this.#options.contrast || 100; | ||||
|     const contrast = this.options.contrast || 100; | ||||
|     if (contrast != 100) filters.push(`contrast(${contrast}%)`); | ||||
|     const brightness = this.#options.brightness || 100; | ||||
|     const brightness = this.options.brightness || 100; | ||||
|     if (brightness != 100) filters.push(`brightness(${brightness}%)`); | ||||
|     return filters.join(" "); | ||||
|   } | ||||
|   #resizePlayer() { | ||||
|     const PREF_RATIO = getPref("video_ratio"), $video = this.#$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport; | ||||
|   resizePlayer() { | ||||
|     const PREF_RATIO = getPref("video_ratio"), $video = this.$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport; | ||||
|     let $webGL2Canvas; | ||||
|     if (this.#playerType == "webgl2") $webGL2Canvas = this.#webGL2Player?.getCanvas(); | ||||
|     if (this.playerType == "webgl2") $webGL2Canvas = this.webGL2Player?.getCanvas(); | ||||
|     let targetWidth, targetHeight, targetObjectFit; | ||||
|     if (PREF_RATIO.includes(":")) { | ||||
|       const tmp = PREF_RATIO.split(":"), videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]); | ||||
| @@ -5214,53 +5214,53 @@ class StreamPlayer { | ||||
|       width = Math.ceil(Math.min(parentRect.width, width)), height = Math.ceil(Math.min(parentRect.height, height)), $video.dataset.width = width.toString(), $video.dataset.height = height.toString(), targetWidth = `${width}px`, targetHeight = `${height}px`, targetObjectFit = PREF_RATIO === "16:9" ? "contain" : "fill"; | ||||
|     } else targetWidth = "100%", targetHeight = "100%", targetObjectFit = PREF_RATIO, $video.dataset.width = window.innerWidth.toString(), $video.dataset.height = window.innerHeight.toString(); | ||||
|     if ($video.style.width = targetWidth, $video.style.height = targetHeight, $video.style.objectFit = targetObjectFit, $webGL2Canvas) $webGL2Canvas.style.width = targetWidth, $webGL2Canvas.style.height = targetHeight, $webGL2Canvas.style.objectFit = targetObjectFit; | ||||
|     if (isNativeTouchGame && this.#playerType == "webgl2") window.BX_EXPOSED.streamSession.updateDimensions(); | ||||
|     if (isNativeTouchGame && this.playerType == "webgl2") window.BX_EXPOSED.streamSession.updateDimensions(); | ||||
|   } | ||||
|   setPlayerType(type, refreshPlayer = !1) { | ||||
|     if (this.#playerType !== type) if (type === "webgl2") { | ||||
|         if (!this.#webGL2Player) this.#webGL2Player = new WebGL2Player(this.#$video); | ||||
|         else this.#webGL2Player.resume(); | ||||
|         this.#$videoCss.textContent = "", this.#$video.classList.add("bx-pixel"); | ||||
|       } else this.#webGL2Player?.stop(), this.#$video.classList.remove("bx-pixel"); | ||||
|     this.#playerType = type, refreshPlayer && this.refreshPlayer(); | ||||
|     if (this.playerType !== type) if (type === "webgl2") { | ||||
|         if (!this.webGL2Player) this.webGL2Player = new WebGL2Player(this.$video); | ||||
|         else this.webGL2Player.resume(); | ||||
|         this.$videoCss.textContent = "", this.$video.classList.add("bx-pixel"); | ||||
|       } else this.webGL2Player?.stop(), this.$video.classList.remove("bx-pixel"); | ||||
|     this.playerType = type, refreshPlayer && this.refreshPlayer(); | ||||
|   } | ||||
|   setOptions(options, refreshPlayer = !1) { | ||||
|     this.#options = options, refreshPlayer && this.refreshPlayer(); | ||||
|     this.options = options, refreshPlayer && this.refreshPlayer(); | ||||
|   } | ||||
|   updateOptions(options, refreshPlayer = !1) { | ||||
|     this.#options = Object.assign(this.#options, options), refreshPlayer && this.refreshPlayer(); | ||||
|     this.options = Object.assign(this.options, options), refreshPlayer && this.refreshPlayer(); | ||||
|   } | ||||
|   getPlayerElement(playerType) { | ||||
|     if (typeof playerType === "undefined") playerType = this.#playerType; | ||||
|     if (playerType === "webgl2") return this.#webGL2Player?.getCanvas(); | ||||
|     return this.#$video; | ||||
|     if (typeof playerType === "undefined") playerType = this.playerType; | ||||
|     if (playerType === "webgl2") return this.webGL2Player?.getCanvas(); | ||||
|     return this.$video; | ||||
|   } | ||||
|   getWebGL2Player() { | ||||
|     return this.#webGL2Player; | ||||
|     return this.webGL2Player; | ||||
|   } | ||||
|   refreshPlayer() { | ||||
|     if (this.#playerType === "webgl2") { | ||||
|       const options = this.#options, webGL2Player = this.#webGL2Player; | ||||
|     if (this.playerType === "webgl2") { | ||||
|       const options = this.options, webGL2Player = this.webGL2Player; | ||||
|       if (options.processing === "usm") webGL2Player.setFilter(1); | ||||
|       else webGL2Player.setFilter(2); | ||||
|       webGL2Player.setSharpness(options.sharpness || 0), webGL2Player.setSaturation(options.saturation || 100), webGL2Player.setContrast(options.contrast || 100), webGL2Player.setBrightness(options.brightness || 100); | ||||
|     } else { | ||||
|       let filters = this.#getVideoPlayerFilterStyle(), videoCss = ""; | ||||
|       let filters = this.getVideoPlayerFilterStyle(), videoCss = ""; | ||||
|       if (filters) videoCss += `filter: ${filters} !important;`; | ||||
|       let css = ""; | ||||
|       if (videoCss) css = `#game-stream video { ${videoCss} }`; | ||||
|       this.#$videoCss.textContent = css; | ||||
|       this.$videoCss.textContent = css; | ||||
|     } | ||||
|     this.#resizePlayer(); | ||||
|     this.resizePlayer(); | ||||
|   } | ||||
|   reloadPlayer() { | ||||
|     this.#cleanUpWebGL2Player(), this.#playerType = "default", this.setPlayerType("webgl2", !1); | ||||
|     this.cleanUpWebGL2Player(), this.playerType = "default", this.setPlayerType("webgl2", !1); | ||||
|   } | ||||
|   #cleanUpWebGL2Player() { | ||||
|     this.#webGL2Player?.destroy(), this.#webGL2Player = null; | ||||
|   cleanUpWebGL2Player() { | ||||
|     this.webGL2Player?.destroy(), this.webGL2Player = null; | ||||
|   } | ||||
|   destroy() { | ||||
|     this.#cleanUpWebGL2Player(); | ||||
|     this.cleanUpWebGL2Player(); | ||||
|   } | ||||
| } | ||||
| function patchVideoApi() { | ||||
|   | ||||
							
								
								
									
										190
									
								
								dist/better-xcloud.user.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										190
									
								
								dist/better-xcloud.user.js
									
									
									
									
										vendored
									
									
								
							| @@ -1607,7 +1607,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { | ||||
|       }, | ||||
|       suggest: { | ||||
|         lowest: 0, | ||||
|         highest: 4 | ||||
|         highest: 2 | ||||
|       } | ||||
|     }, | ||||
|     video_ratio: { | ||||
| @@ -3903,13 +3903,13 @@ class VibrationManager { | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| var controller_shortcuts_default = "if (window.BX_EXPOSED.disableGamepadPolling) {\n    this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(50) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, 50);\n    return;\n}\n\nconst currentGamepad = ${gamepadVar};\n\n// Share button on XS controller\nif (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {\n    window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));\n}\n\nconst btnHome = currentGamepad.buttons[16];\nif (btnHome) {\n    if (!this.bxHomeStates) {\n        this.bxHomeStates = {};\n    }\n\n    let intervalMs = 0;\n    let hijack = false;\n\n    if (btnHome.pressed) {\n        hijack = true;\n        intervalMs = 16;\n        this.gamepadIsIdle.set(currentGamepad.index, false);\n\n        if (this.bxHomeStates[currentGamepad.index]) {\n            const lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;\n\n            if (currentGamepad.timestamp !== lastTimestamp) {\n                this.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;\n\n                const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);\n                if (handled) {\n                    this.bxHomeStates[currentGamepad.index].shortcutPressed += 1;\n                }\n            }\n        } else {\n            // First time pressing > save current timestamp\n            window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);\n            this.bxHomeStates[currentGamepad.index] = {\n                shortcutPressed: 0,\n                timestamp: currentGamepad.timestamp,\n            };\n        }\n    } else if (this.bxHomeStates[currentGamepad.index]) {\n        hijack = true;\n        const info = structuredClone(this.bxHomeStates[currentGamepad.index]);\n\n        // Home button released\n        this.bxHomeStates[currentGamepad.index] = null;\n\n        if (info.shortcutPressed === 0) {\n            const fakeGamepadMappings = [{\n                GamepadIndex: currentGamepad.index,\n                A: 0,\n                B: 0,\n                X: 0,\n                Y: 0,\n                LeftShoulder: 0,\n                RightShoulder: 0,\n                LeftTrigger: 0,\n                RightTrigger: 0,\n                View: 0,\n                Menu: 0,\n                LeftThumb: 0,\n                RightThumb: 0,\n                DPadUp: 0,\n                DPadDown: 0,\n                DPadLeft: 0,\n                DPadRight: 0,\n                Nexus: 1,\n                LeftThumbXAxis: 0,\n                LeftThumbYAxis: 0,\n                RightThumbXAxis: 0,\n                RightThumbYAxis: 0,\n                PhysicalPhysicality: 0,\n                VirtualPhysicality: 0,\n                Dirty: true,\n                Virtual: false,\n            }];\n\n            const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;\n            intervalMs = isLongPress ? 500 : 100;\n\n            this.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);\n        } else {\n            intervalMs = 4;\n        }\n    }\n\n    if (hijack && intervalMs) {\n        // Listen to next button press\n        this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);\n\n        // Hijack this button\n        return;\n    }\n}\n"; | ||||
| var expose_stream_session_default = "window.BX_EXPOSED.streamSession = this;\n\nconst orgSetMicrophoneState = this.setMicrophoneState.bind(this);\nthis.setMicrophoneState = state => {\n    orgSetMicrophoneState(state);\n\n    const evt = new Event(BxEvent.MICROPHONE_STATE_CHANGED);\n    evt.microphoneState = state;\n\n    window.dispatchEvent(evt);\n};\n\nwindow.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));\n\n// Patch updateDimensions() to make native touch work correctly with WebGL2\nlet updateDimensionsStr = this.updateDimensions.toString();\n\nif (updateDimensionsStr.startsWith('function ')) {\n    updateDimensionsStr = updateDimensionsStr.substring(9);\n}\n\n// if(r){\nconst renderTargetVar = updateDimensionsStr.match(/if\\((\\w+)\\){/)[1];\n\nupdateDimensionsStr = updateDimensionsStr.replaceAll(renderTargetVar + '.scroll', 'scroll');\n\nupdateDimensionsStr = updateDimensionsStr.replace(`if(${renderTargetVar}){`, `\nif (${renderTargetVar}) {\n    const scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;\n    const scrollHeight = ${renderTargetVar}.dataset.height ? parseInt(${renderTargetVar}.dataset.height) : ${renderTargetVar}.scrollHeight;\n`);\n\neval(`this.updateDimensions = function ${updateDimensionsStr}`);\n"; | ||||
| var local_co_op_enable_default = "let match;\nlet onGamepadChangedStr = this.onGamepadChanged.toString();\n\nif (onGamepadChangedStr.startsWith('function ')) {\n    onGamepadChangedStr = onGamepadChangedStr.substring(9);\n}\n\nonGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');\neval(`this.onGamepadChanged = function ${onGamepadChangedStr}`);\n\nlet onGamepadInputStr = this.onGamepadInput.toString();\n\nmatch = onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);\nif (match) {\n    const gamepadIndexVar = match[0];\n    onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`);\n    eval(`this.onGamepadInput = function ${onGamepadInputStr}`);\n    BxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');\n} else {\n    BxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');\n}\n"; | ||||
| var controller_shortcuts_default = "if (window.BX_EXPOSED.disableGamepadPolling) {\nthis.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(50) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, 50);\nreturn;\n}\n\nconst currentGamepad = ${gamepadVar};\n\nif (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {\nwindow.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));\n}\n\nconst btnHome = currentGamepad.buttons[16];\nif (btnHome) {\nif (!this.bxHomeStates) {\nthis.bxHomeStates = {};\n}\n\nlet intervalMs = 0;\nlet hijack = false;\n\nif (btnHome.pressed) {\nhijack = true;\nintervalMs = 16;\nthis.gamepadIsIdle.set(currentGamepad.index, false);\n\nif (this.bxHomeStates[currentGamepad.index]) {\nconst lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;\n\nif (currentGamepad.timestamp !== lastTimestamp) {\nthis.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;\n\nconst handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);\nif (handled) {\nthis.bxHomeStates[currentGamepad.index].shortcutPressed += 1;\n}\n}\n} else {\nwindow.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);\nthis.bxHomeStates[currentGamepad.index] = {\nshortcutPressed: 0,\ntimestamp: currentGamepad.timestamp,\n};\n}\n} else if (this.bxHomeStates[currentGamepad.index]) {\nhijack = true;\nconst info = structuredClone(this.bxHomeStates[currentGamepad.index]);\n\nthis.bxHomeStates[currentGamepad.index] = null;\n\nif (info.shortcutPressed === 0) {\nconst fakeGamepadMappings = [{\nGamepadIndex: currentGamepad.index,\nA: 0,\nB: 0,\nX: 0,\nY: 0,\nLeftShoulder: 0,\nRightShoulder: 0,\nLeftTrigger: 0,\nRightTrigger: 0,\nView: 0,\nMenu: 0,\nLeftThumb: 0,\nRightThumb: 0,\nDPadUp: 0,\nDPadDown: 0,\nDPadLeft: 0,\nDPadRight: 0,\nNexus: 1,\nLeftThumbXAxis: 0,\nLeftThumbYAxis: 0,\nRightThumbXAxis: 0,\nRightThumbYAxis: 0,\nPhysicalPhysicality: 0,\nVirtualPhysicality: 0,\nDirty: true,\nVirtual: false,\n}];\n\nconst isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;\nintervalMs = isLongPress ? 500 : 100;\n\nthis.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);\n} else {\nintervalMs = 4;\n}\n}\n\nif (hijack && intervalMs) {\nthis.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);\n\nreturn;\n}\n}\n"; | ||||
| var expose_stream_session_default = "window.BX_EXPOSED.streamSession = this;\n\nconst orgSetMicrophoneState = this.setMicrophoneState.bind(this);\nthis.setMicrophoneState = state => {\norgSetMicrophoneState(state);\n\nconst evt = new Event(BxEvent.MICROPHONE_STATE_CHANGED);\nevt.microphoneState = state;\n\nwindow.dispatchEvent(evt);\n};\n\nwindow.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));\n\nlet updateDimensionsStr = this.updateDimensions.toString();\n\nif (updateDimensionsStr.startsWith('function ')) {\nupdateDimensionsStr = updateDimensionsStr.substring(9);\n}\n\nconst renderTargetVar = updateDimensionsStr.match(/if\\((\\w+)\\){/)[1];\n\nupdateDimensionsStr = updateDimensionsStr.replaceAll(renderTargetVar + '.scroll', 'scroll');\n\nupdateDimensionsStr = updateDimensionsStr.replace(`if(${renderTargetVar}){`, `\nif (${renderTargetVar}) {\nconst scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;\nconst scrollHeight = ${renderTargetVar}.dataset.height ? parseInt(${renderTargetVar}.dataset.height) : ${renderTargetVar}.scrollHeight;\n`);\n\neval(`this.updateDimensions = function ${updateDimensionsStr}`);\n"; | ||||
| var local_co_op_enable_default = "let match;\nlet onGamepadChangedStr = this.onGamepadChanged.toString();\n\nif (onGamepadChangedStr.startsWith('function ')) {\nonGamepadChangedStr = onGamepadChangedStr.substring(9);\n}\n\nonGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');\neval(`this.onGamepadChanged = function ${onGamepadChangedStr}`);\n\nlet onGamepadInputStr = this.onGamepadInput.toString();\n\nmatch = onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);\nif (match) {\nconst gamepadIndexVar = match[0];\nonGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`);\neval(`this.onGamepadInput = function ${onGamepadInputStr}`);\nBxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');\n} else {\nBxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');\n}\n"; | ||||
| var set_currently_focused_interactable_default = "e && BxEvent.dispatch(window, BxEvent.NAVIGATION_FOCUS_CHANGED, {element: e});\n"; | ||||
| var remote_play_enable_default = "connectMode: window.BX_REMOTE_PLAY_CONFIG ? \"xhome-connect\" : \"cloud-connect\",\nremotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',\n"; | ||||
| var remote_play_keep_alive_default = "const msg = JSON.parse(e);\nif (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {\n    try {\n        this.sendKeepAlive();\n        return;\n    } catch (ex) { console.log(ex); }\n}\n"; | ||||
| var vibration_adjust_default = "if (!window.BX_ENABLE_CONTROLLER_VIBRATION) {\n    return void(0);\n}\n\nconst intensity = window.BX_VIBRATION_INTENSITY;\nif (intensity === 0) {\n    return void(0);\n}\n\nif (intensity < 1) {\n    e.leftMotorPercent *= intensity;\n    e.rightMotorPercent *= intensity;\n    e.leftTriggerMotorPercent *= intensity;\n    e.rightTriggerMotorPercent *= intensity;\n}\n"; | ||||
| var remote_play_keep_alive_default = "const msg = JSON.parse(e);\nif (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {\ntry {\nthis.sendKeepAlive();\nreturn;\n} catch (ex) { console.log(ex); }\n}\n"; | ||||
| var vibration_adjust_default = "if (!window.BX_ENABLE_CONTROLLER_VIBRATION) {\nreturn void(0);\n}\n\nconst intensity = window.BX_VIBRATION_INTENSITY;\nif (intensity === 0) {\nreturn void(0);\n}\n\nif (intensity < 1) {\ne.leftMotorPercent *= intensity;\ne.rightMotorPercent *= intensity;\ne.leftTriggerMotorPercent *= intensity;\ne.rightTriggerMotorPercent *= intensity;\n}\n"; | ||||
| var FeatureGates = { | ||||
|   PwaPrompt: !1, | ||||
|   EnableWifiWarnings: !1, | ||||
| @@ -6942,86 +6942,86 @@ function patchSdpBitrate(sdp, video, audio) { | ||||
|   } | ||||
|   return lines.join("\r\n"); | ||||
| } | ||||
| var clarity_boost_default = "attribute vec2 position;\n\nvoid main() {\n    gl_Position = vec4(position, 0, 1);\n}\n"; | ||||
| var clarity_boost_default2 = "const int FILTER_UNSHARP_MASKING = 1;\nconst int FILTER_CAS = 2;\n\nprecision highp float;\nuniform sampler2D data;\nuniform vec2 iResolution;\n\nuniform int filterId;\nuniform float sharpenFactor;\nuniform float brightness;\nuniform float contrast;\nuniform float saturation;\n\nvec3 textureAt(sampler2D tex, vec2 coord) {\n    return texture2D(tex, coord / iResolution.xy).rgb;\n}\n\nvec3 clarityBoost(sampler2D tex, vec2 coord)\n{\n    // Load a collection of samples in a 3x3 neighorhood, where e is the current pixel.\n    // a b c\n    // d e f\n    // g h i\n    vec3 a = textureAt(tex, coord + vec2(-1, 1));\n    vec3 b = textureAt(tex, coord + vec2(0, 1));\n    vec3 c = textureAt(tex, coord + vec2(1, 1));\n\n    vec3 d = textureAt(tex, coord + vec2(-1, 0));\n    vec3 e = textureAt(tex, coord);\n    vec3 f = textureAt(tex, coord + vec2(1, 0));\n\n    vec3 g = textureAt(tex, coord + vec2(-1, -1));\n    vec3 h = textureAt(tex, coord + vec2(0, -1));\n    vec3 i = textureAt(tex, coord + vec2(1, -1));\n\n    if (filterId == FILTER_CAS) {\n        // Soft min and max.\n        //  a b c             b\n        //  d e f * 0.5  +  d e f * 0.5\n        //  g h i             h\n        // These are 2.0x bigger (factored out the extra multiply).\n        vec3 minRgb = min(min(min(d, e), min(f, b)), h);\n        vec3 minRgb2 = min(min(a, c), min(g, i));\n        minRgb += min(minRgb, minRgb2);\n\n        vec3 maxRgb = max(max(max(d, e), max(f, b)), h);\n        vec3 maxRgb2 = max(max(a, c), max(g, i));\n        maxRgb += max(maxRgb, maxRgb2);\n\n        // Smooth minimum distance to signal limit divided by smooth max.\n        vec3 reciprocalMaxRgb = 1.0 / maxRgb;\n        vec3 amplifyRgb = clamp(min(minRgb, 2.0 - maxRgb) * reciprocalMaxRgb, 0.0, 1.0);\n\n        // Shaping amount of sharpening.\n        amplifyRgb = inversesqrt(amplifyRgb);\n\n        float contrast = 0.8;\n        float peak = -3.0 * contrast + 8.0;\n        vec3 weightRgb = -(1.0 / (amplifyRgb * peak));\n\n        vec3 reciprocalWeightRgb = 1.0 / (4.0 * weightRgb + 1.0);\n\n        //                0 w 0\n        // Filter shape:  w 1 w\n        //                0 w 0\n        vec3 window = (b + d) + (f + h);\n        vec3 outColor = clamp((window * weightRgb + e) * reciprocalWeightRgb, 0.0, 1.0);\n\n        outColor = mix(e, outColor, sharpenFactor / 2.0);\n\n        return outColor;\n    } else if (filterId == FILTER_UNSHARP_MASKING) {\n        vec3 gaussianBlur = (a * 1.0 + b * 2.0 + c * 1.0 +\n            d * 2.0 + e * 4.0 + f * 2.0 +\n            g * 1.0 + h * 2.0 + i * 1.0) / 16.0;\n\n        // Return edge detection\n        return e + (e - gaussianBlur) * sharpenFactor / 3.0;\n    }\n\n    return e;\n}\n\nvec3 adjustBrightness(vec3 color) {\n    return (1.0 + brightness) * color;\n}\n\nvec3 adjustContrast(vec3 color) {\n    return 0.5 + (1.0 + contrast) * (color - 0.5);\n}\n\nvec3 adjustSaturation(vec3 color) {\n    const vec3 luminosityFactor = vec3(0.2126, 0.7152, 0.0722);\n    vec3 grayscale = vec3(dot(color, luminosityFactor));\n\n    return mix(grayscale, color, 1.0 + saturation);\n}\n\nvoid main() {\n    vec3 color;\n\n    if (sharpenFactor > 0.0) {\n        color = clarityBoost(data, gl_FragCoord.xy);\n    } else {\n        color = textureAt(data, gl_FragCoord.xy);\n    }\n\n    if (saturation != 0.0) {\n        color = adjustSaturation(color);\n    }\n\n    if (contrast != 0.0) {\n        color = adjustContrast(color);\n    }\n\n    if (brightness != 0.0) {\n        color = adjustBrightness(color);\n    }\n\n    gl_FragColor = vec4(color, 1.0);\n}\n"; | ||||
| var clarity_boost_default = "attribute vec4 position;\n\nvoid main() {\ngl_Position = position;\n}\n"; | ||||
| var clarity_boost_default2 = "precision mediump float;\nuniform sampler2D data;\nuniform vec2 iResolution;\n\nconst int FILTER_UNSHARP_MASKING = 1;\nconst int FILTER_CAS = 2;\n\nconst float CAS_CONTRAST_PEAK = (-3.0 * 0.8 + 8.0);\n\nconst vec3 LUMINOSITY_FACTOR = vec3(0.2126, 0.7152, 0.0722);\n\nuniform int filterId;\nuniform float sharpenFactor;\nuniform float brightness;\nuniform float contrast;\nuniform float saturation;\n\nvec3 clarityBoost(sampler2D tex, vec2 coord) {\nvec2 texelSize = 1.0 / iResolution.xy;\n\nvec3 a = texture2D(tex, coord + texelSize * vec2(-1, 1)).rgb;\nvec3 b = texture2D(tex, coord + texelSize * vec2(0, 1)).rgb;\nvec3 c = texture2D(tex, coord + texelSize * vec2(1, 1)).rgb;\n\nvec3 d = texture2D(tex, coord + texelSize * vec2(-1, 0)).rgb;\nvec3 e = texture2D(tex, coord).rgb;\nvec3 f = texture2D(tex, coord + texelSize * vec2(1, 0)).rgb;\n\nvec3 g = texture2D(tex, coord + texelSize * vec2(-1, -1)).rgb;\nvec3 h = texture2D(tex, coord + texelSize * vec2(0, -1)).rgb;\nvec3 i = texture2D(tex, coord + texelSize * vec2(1, -1)).rgb;\n\nif (filterId == FILTER_CAS) {\nvec3 minRgb = min(min(min(d, e), min(f, b)), h);\nvec3 minRgb2 = min(min(a, c), min(g, i));\nminRgb += min(minRgb, minRgb2);\n\nvec3 maxRgb = max(max(max(d, e), max(f, b)), h);\nvec3 maxRgb2 = max(max(a, c), max(g, i));\nmaxRgb += max(maxRgb, maxRgb2);\n\nvec3 reciprocalMaxRgb = 1.0 / maxRgb;\nvec3 amplifyRgb = clamp(min(minRgb, 2.0 - maxRgb) * reciprocalMaxRgb, 0.0, 1.0);\n\namplifyRgb = inversesqrt(amplifyRgb);\n\nvec3 weightRgb = -(1.0 / (amplifyRgb * CAS_CONTRAST_PEAK));\nvec3 reciprocalWeightRgb = 1.0 / (4.0 * weightRgb + 1.0);\n\nvec3 window = (b + d) + (f + h);\nvec3 outColor = clamp((window * weightRgb + e) * reciprocalWeightRgb, 0.0, 1.0);\n\noutColor = mix(e, outColor, sharpenFactor / 2.0);\n\nreturn outColor;\n} else if (filterId == FILTER_UNSHARP_MASKING) {\nvec3 gaussianBlur = (a + c + g + i) * 1.0 +\n(b + d + f + h) * 2.0 +\ne * 4.0;\ngaussianBlur /= 16.0;\n\nreturn e + (e - gaussianBlur) * sharpenFactor / 3.0;\n}\n\nreturn e;\n}\n\nvoid main() {\nvec3 color;\nvec2 uv = gl_FragCoord.xy / iResolution.xy;\n\nif (sharpenFactor > 0.0) {\ncolor = clarityBoost(data, uv);\n} else {\ncolor = texture2D(data, uv).rgb;\n}\n\nif (saturation != 1.0) {\nvec3 grayscale = vec3(dot(color, LUMINOSITY_FACTOR));\ncolor = mix(grayscale, color, saturation);\n}\n\nif (contrast != 1.0) {\ncolor = 0.5 + contrast * (color - 0.5);\n}\n\nif (brightness != 1.0) {\ncolor = brightness * color;\n}\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; | ||||
| var LOG_TAG7 = "WebGL2Player"; | ||||
| class WebGL2Player { | ||||
|   #$video; | ||||
|   #$canvas; | ||||
|   #gl = null; | ||||
|   #resources = []; | ||||
|   #program = null; | ||||
|   #stopped = !1; | ||||
|   #options = { | ||||
|   $video; | ||||
|   $canvas; | ||||
|   gl = null; | ||||
|   resources = []; | ||||
|   program = null; | ||||
|   stopped = !1; | ||||
|   options = { | ||||
|     filterId: 1, | ||||
|     sharpenFactor: 0, | ||||
|     brightness: 0, | ||||
|     contrast: 0, | ||||
|     saturation: 0 | ||||
|   }; | ||||
|   #animFrameId = null; | ||||
|   animFrameId = null; | ||||
|   constructor($video) { | ||||
|     BxLogger.info(LOG_TAG7, "Initialize"), this.#$video = $video; | ||||
|     BxLogger.info(LOG_TAG7, "Initialize"), this.$video = $video; | ||||
|     const $canvas = document.createElement("canvas"); | ||||
|     $canvas.width = $video.videoWidth, $canvas.height = $video.videoHeight, this.#$canvas = $canvas, this.#setupShaders(), this.#setupRendering(), $video.insertAdjacentElement("afterend", $canvas); | ||||
|     $canvas.width = $video.videoWidth, $canvas.height = $video.videoHeight, this.$canvas = $canvas, this.setupShaders(), this.setupRendering(), $video.insertAdjacentElement("afterend", $canvas); | ||||
|   } | ||||
|   setFilter(filterId, update = !0) { | ||||
|     this.#options.filterId = filterId, update && this.updateCanvas(); | ||||
|     this.options.filterId = filterId, update && this.updateCanvas(); | ||||
|   } | ||||
|   setSharpness(sharpness, update = !0) { | ||||
|     this.#options.sharpenFactor = sharpness, update && this.updateCanvas(); | ||||
|     this.options.sharpenFactor = sharpness, update && this.updateCanvas(); | ||||
|   } | ||||
|   setBrightness(brightness, update = !0) { | ||||
|     this.#options.brightness = (brightness - 100) / 100, update && this.updateCanvas(); | ||||
|     this.options.brightness = 1 + (brightness - 100) / 100, update && this.updateCanvas(); | ||||
|   } | ||||
|   setContrast(contrast, update = !0) { | ||||
|     this.#options.contrast = (contrast - 100) / 100, update && this.updateCanvas(); | ||||
|     this.options.contrast = 1 + (contrast - 100) / 100, update && this.updateCanvas(); | ||||
|   } | ||||
|   setSaturation(saturation, update = !0) { | ||||
|     this.#options.saturation = (saturation - 100) / 100, update && this.updateCanvas(); | ||||
|     this.options.saturation = 1 + (saturation - 100) / 100, update && this.updateCanvas(); | ||||
|   } | ||||
|   getCanvas() { | ||||
|     return this.#$canvas; | ||||
|     return this.$canvas; | ||||
|   } | ||||
|   updateCanvas() { | ||||
|     const gl = this.#gl, program = this.#program; | ||||
|     gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.#$canvas.width, this.#$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.#options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.#options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.#options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.#options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.#options.saturation); | ||||
|     const gl = this.gl, program = this.program; | ||||
|     gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.$canvas.width, this.$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.options.saturation); | ||||
|   } | ||||
|   drawFrame() { | ||||
|     const gl = this.#gl, $video = this.#$video; | ||||
|     const gl = this.gl, $video = this.$video; | ||||
|     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, $video), gl.drawArrays(gl.TRIANGLES, 0, 6); | ||||
|   } | ||||
|   #setupRendering() { | ||||
|   setupRendering() { | ||||
|     let animate; | ||||
|     if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) { | ||||
|       const $video = this.#$video; | ||||
|       const $video = this.$video; | ||||
|       animate = () => { | ||||
|         if (this.#stopped) return; | ||||
|         this.drawFrame(), this.#animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|       }, this.#animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|         if (this.stopped) return; | ||||
|         this.drawFrame(), this.animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|       }, this.animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|     } else animate = () => { | ||||
|         if (this.#stopped) return; | ||||
|         this.drawFrame(), this.#animFrameId = requestAnimationFrame(animate); | ||||
|       }, this.#animFrameId = requestAnimationFrame(animate); | ||||
|         if (this.stopped) return; | ||||
|         this.drawFrame(), this.animFrameId = requestAnimationFrame(animate); | ||||
|       }, this.animFrameId = requestAnimationFrame(animate); | ||||
|   } | ||||
|   #setupShaders() { | ||||
|   setupShaders() { | ||||
|     BxLogger.info(LOG_TAG7, "Setting up", getPref("video_power_preference")); | ||||
|     const gl = this.#$canvas.getContext("webgl", { | ||||
|     const gl = this.$canvas.getContext("webgl", { | ||||
|       isBx: !0, | ||||
|       antialias: !0, | ||||
|       alpha: !1, | ||||
|       powerPreference: getPref("video_power_preference") | ||||
|     }); | ||||
|     this.#gl = gl, gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferWidth); | ||||
|     this.gl = gl, gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferWidth); | ||||
|     const vShader = gl.createShader(gl.VERTEX_SHADER); | ||||
|     gl.shaderSource(vShader, clarity_boost_default), gl.compileShader(vShader); | ||||
|     const fShader = gl.createShader(gl.FRAGMENT_SHADER); | ||||
|     gl.shaderSource(fShader, clarity_boost_default2), gl.compileShader(fShader); | ||||
|     const program = gl.createProgram(); | ||||
|     if (this.#program = program, gl.attachShader(program, vShader), gl.attachShader(program, fShader), gl.linkProgram(program), gl.useProgram(program), !gl.getProgramParameter(program, gl.LINK_STATUS)) console.error(`Link failed: ${gl.getProgramInfoLog(program)}`), console.error(`vs info-log: ${gl.getShaderInfoLog(vShader)}`), console.error(`fs info-log: ${gl.getShaderInfoLog(fShader)}`); | ||||
|     if (this.program = program, gl.attachShader(program, vShader), gl.attachShader(program, fShader), gl.linkProgram(program), gl.useProgram(program), !gl.getProgramParameter(program, gl.LINK_STATUS)) console.error(`Link failed: ${gl.getProgramInfoLog(program)}`), console.error(`vs info-log: ${gl.getShaderInfoLog(vShader)}`), console.error(`fs info-log: ${gl.getShaderInfoLog(fShader)}`); | ||||
|     this.updateCanvas(); | ||||
|     const buffer = gl.createBuffer(); | ||||
|     this.#resources.push(buffer), gl.bindBuffer(gl.ARRAY_BUFFER, buffer), gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | ||||
|     this.resources.push(buffer), gl.bindBuffer(gl.ARRAY_BUFFER, buffer), gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | ||||
|       -1, | ||||
|       -1, | ||||
|       1, | ||||
| @@ -7036,51 +7036,51 @@ class WebGL2Player { | ||||
|       1 | ||||
|     ]), gl.STATIC_DRAW), gl.enableVertexAttribArray(0), gl.vertexAttribPointer(0, 2, gl.FLOAT, !1, 0, 0); | ||||
|     const texture = gl.createTexture(); | ||||
|     this.#resources.push(texture), gl.bindTexture(gl.TEXTURE_2D, texture), gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !0), 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(program, "data"), 0), gl.activeTexture(gl.TEXTURE0); | ||||
|     this.resources.push(texture), gl.bindTexture(gl.TEXTURE_2D, texture), gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !0), 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(program, "data"), 0), gl.activeTexture(gl.TEXTURE0); | ||||
|   } | ||||
|   resume() { | ||||
|     this.stop(), this.#stopped = !1, BxLogger.info(LOG_TAG7, "Resume"), this.#$canvas.classList.remove("bx-gone"), this.#setupRendering(); | ||||
|     this.stop(), this.stopped = !1, BxLogger.info(LOG_TAG7, "Resume"), this.$canvas.classList.remove("bx-gone"), this.setupRendering(); | ||||
|   } | ||||
|   stop() { | ||||
|     if (BxLogger.info(LOG_TAG7, "Stop"), this.#$canvas.classList.add("bx-gone"), this.#stopped = !0, this.#animFrameId) { | ||||
|       if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) this.#$video.cancelVideoFrameCallback(this.#animFrameId); | ||||
|       else cancelAnimationFrame(this.#animFrameId); | ||||
|       this.#animFrameId = null; | ||||
|     if (BxLogger.info(LOG_TAG7, "Stop"), this.$canvas.classList.add("bx-gone"), this.stopped = !0, this.animFrameId) { | ||||
|       if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) this.$video.cancelVideoFrameCallback(this.animFrameId); | ||||
|       else cancelAnimationFrame(this.animFrameId); | ||||
|       this.animFrameId = null; | ||||
|     } | ||||
|   } | ||||
|   destroy() { | ||||
|     BxLogger.info(LOG_TAG7, "Destroy"), this.stop(); | ||||
|     const gl = this.#gl; | ||||
|     const gl = this.gl; | ||||
|     if (gl) { | ||||
|       gl.getExtension("WEBGL_lose_context")?.loseContext(); | ||||
|       for (let resource of this.#resources) | ||||
|       for (let resource of this.resources) | ||||
|         if (resource instanceof WebGLProgram) gl.useProgram(null), gl.deleteProgram(resource); | ||||
|         else if (resource instanceof WebGLShader) gl.deleteShader(resource); | ||||
|         else if (resource instanceof WebGLTexture) gl.deleteTexture(resource); | ||||
|         else if (resource instanceof WebGLBuffer) gl.deleteBuffer(resource); | ||||
|       this.#gl = null; | ||||
|       this.gl = null; | ||||
|     } | ||||
|     if (this.#$canvas.isConnected) this.#$canvas.parentElement?.removeChild(this.#$canvas); | ||||
|     this.#$canvas.width = 1, this.#$canvas.height = 1; | ||||
|     if (this.$canvas.isConnected) this.$canvas.parentElement?.removeChild(this.$canvas); | ||||
|     this.$canvas.width = 1, this.$canvas.height = 1; | ||||
|   } | ||||
| } | ||||
| class StreamPlayer { | ||||
|   #$video; | ||||
|   #playerType = "default"; | ||||
|   #options = {}; | ||||
|   #webGL2Player = null; | ||||
|   #$videoCss = null; | ||||
|   #$usmMatrix = null; | ||||
|   $video; | ||||
|   playerType = "default"; | ||||
|   options = {}; | ||||
|   webGL2Player = null; | ||||
|   $videoCss = null; | ||||
|   $usmMatrix = null; | ||||
|   constructor($video, type, options) { | ||||
|     this.#setupVideoElements(), this.#$video = $video, this.#options = options || {}, this.setPlayerType(type); | ||||
|     this.setupVideoElements(), this.$video = $video, this.options = options || {}, this.setPlayerType(type); | ||||
|   } | ||||
|   #setupVideoElements() { | ||||
|     if (this.#$videoCss = document.getElementById("bx-video-css"), this.#$videoCss) { | ||||
|       this.#$usmMatrix = this.#$videoCss.querySelector("#bx-filter-usm-matrix"); | ||||
|   setupVideoElements() { | ||||
|     if (this.$videoCss = document.getElementById("bx-video-css"), this.$videoCss) { | ||||
|       this.$usmMatrix = this.$videoCss.querySelector("#bx-filter-usm-matrix"); | ||||
|       return; | ||||
|     } | ||||
|     const $fragment = document.createDocumentFragment(); | ||||
|     this.#$videoCss = CE("style", { id: "bx-video-css" }), $fragment.appendChild(this.#$videoCss); | ||||
|     this.$videoCss = CE("style", { id: "bx-video-css" }), $fragment.appendChild(this.$videoCss); | ||||
|     const $svg = CE("svg", { | ||||
|       id: "bx-video-filters", | ||||
|       xmlns: "http://www.w3.org/2000/svg", | ||||
| @@ -7088,31 +7088,31 @@ class StreamPlayer { | ||||
|     }, CE("defs", { xmlns: "http://www.w3.org/2000/svg" }, CE("filter", { | ||||
|       id: "bx-filter-usm", | ||||
|       xmlns: "http://www.w3.org/2000/svg" | ||||
|     }, this.#$usmMatrix = CE("feConvolveMatrix", { | ||||
|     }, this.$usmMatrix = CE("feConvolveMatrix", { | ||||
|       id: "bx-filter-usm-matrix", | ||||
|       order: "3", | ||||
|       xmlns: "http://www.w3.org/2000/svg" | ||||
|     })))); | ||||
|     $fragment.appendChild($svg), document.documentElement.appendChild($fragment); | ||||
|   } | ||||
|   #getVideoPlayerFilterStyle() { | ||||
|     const filters = [], sharpness = this.#options.sharpness || 0; | ||||
|     if (this.#options.processing === "usm" && sharpness != 0) { | ||||
|   getVideoPlayerFilterStyle() { | ||||
|     const filters = [], sharpness = this.options.sharpness || 0; | ||||
|     if (this.options.processing === "usm" && sharpness != 0) { | ||||
|       const matrix = `0 -1 0 -1 ${(7 - (sharpness / 2 - 1) * 0.5).toFixed(1)} -1 0 -1 0`; | ||||
|       this.#$usmMatrix?.setAttributeNS(null, "kernelMatrix", matrix), filters.push("url(#bx-filter-usm)"); | ||||
|       this.$usmMatrix?.setAttributeNS(null, "kernelMatrix", matrix), filters.push("url(#bx-filter-usm)"); | ||||
|     } | ||||
|     const saturation = this.#options.saturation || 100; | ||||
|     const saturation = this.options.saturation || 100; | ||||
|     if (saturation != 100) filters.push(`saturate(${saturation}%)`); | ||||
|     const contrast = this.#options.contrast || 100; | ||||
|     const contrast = this.options.contrast || 100; | ||||
|     if (contrast != 100) filters.push(`contrast(${contrast}%)`); | ||||
|     const brightness = this.#options.brightness || 100; | ||||
|     const brightness = this.options.brightness || 100; | ||||
|     if (brightness != 100) filters.push(`brightness(${brightness}%)`); | ||||
|     return filters.join(" "); | ||||
|   } | ||||
|   #resizePlayer() { | ||||
|     const PREF_RATIO = getPref("video_ratio"), $video = this.#$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport; | ||||
|   resizePlayer() { | ||||
|     const PREF_RATIO = getPref("video_ratio"), $video = this.$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport; | ||||
|     let $webGL2Canvas; | ||||
|     if (this.#playerType == "webgl2") $webGL2Canvas = this.#webGL2Player?.getCanvas(); | ||||
|     if (this.playerType == "webgl2") $webGL2Canvas = this.webGL2Player?.getCanvas(); | ||||
|     let targetWidth, targetHeight, targetObjectFit; | ||||
|     if (PREF_RATIO.includes(":")) { | ||||
|       const tmp = PREF_RATIO.split(":"), videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]); | ||||
| @@ -7123,54 +7123,54 @@ class StreamPlayer { | ||||
|       width = Math.ceil(Math.min(parentRect.width, width)), height = Math.ceil(Math.min(parentRect.height, height)), $video.dataset.width = width.toString(), $video.dataset.height = height.toString(), targetWidth = `${width}px`, targetHeight = `${height}px`, targetObjectFit = PREF_RATIO === "16:9" ? "contain" : "fill"; | ||||
|     } else targetWidth = "100%", targetHeight = "100%", targetObjectFit = PREF_RATIO, $video.dataset.width = window.innerWidth.toString(), $video.dataset.height = window.innerHeight.toString(); | ||||
|     if ($video.style.width = targetWidth, $video.style.height = targetHeight, $video.style.objectFit = targetObjectFit, $webGL2Canvas) $webGL2Canvas.style.width = targetWidth, $webGL2Canvas.style.height = targetHeight, $webGL2Canvas.style.objectFit = targetObjectFit; | ||||
|     if (isNativeTouchGame && this.#playerType == "webgl2") window.BX_EXPOSED.streamSession.updateDimensions(); | ||||
|     if (isNativeTouchGame && this.playerType == "webgl2") window.BX_EXPOSED.streamSession.updateDimensions(); | ||||
|   } | ||||
|   setPlayerType(type, refreshPlayer = !1) { | ||||
|     if (this.#playerType !== type) if (type === "webgl2") { | ||||
|         if (!this.#webGL2Player) this.#webGL2Player = new WebGL2Player(this.#$video); | ||||
|         else this.#webGL2Player.resume(); | ||||
|         this.#$videoCss.textContent = "", this.#$video.classList.add("bx-pixel"); | ||||
|       } else this.#webGL2Player?.stop(), this.#$video.classList.remove("bx-pixel"); | ||||
|     this.#playerType = type, refreshPlayer && this.refreshPlayer(); | ||||
|     if (this.playerType !== type) if (type === "webgl2") { | ||||
|         if (!this.webGL2Player) this.webGL2Player = new WebGL2Player(this.$video); | ||||
|         else this.webGL2Player.resume(); | ||||
|         this.$videoCss.textContent = "", this.$video.classList.add("bx-pixel"); | ||||
|       } else this.webGL2Player?.stop(), this.$video.classList.remove("bx-pixel"); | ||||
|     this.playerType = type, refreshPlayer && this.refreshPlayer(); | ||||
|   } | ||||
|   setOptions(options, refreshPlayer = !1) { | ||||
|     this.#options = options, refreshPlayer && this.refreshPlayer(); | ||||
|     this.options = options, refreshPlayer && this.refreshPlayer(); | ||||
|   } | ||||
|   updateOptions(options, refreshPlayer = !1) { | ||||
|     this.#options = Object.assign(this.#options, options), refreshPlayer && this.refreshPlayer(); | ||||
|     this.options = Object.assign(this.options, options), refreshPlayer && this.refreshPlayer(); | ||||
|   } | ||||
|   getPlayerElement(playerType) { | ||||
|     if (typeof playerType === "undefined") playerType = this.#playerType; | ||||
|     if (playerType === "webgl2") return this.#webGL2Player?.getCanvas(); | ||||
|     return this.#$video; | ||||
|     if (typeof playerType === "undefined") playerType = this.playerType; | ||||
|     if (playerType === "webgl2") return this.webGL2Player?.getCanvas(); | ||||
|     return this.$video; | ||||
|   } | ||||
|   getWebGL2Player() { | ||||
|     return this.#webGL2Player; | ||||
|     return this.webGL2Player; | ||||
|   } | ||||
|   refreshPlayer() { | ||||
|     if (this.#playerType === "webgl2") { | ||||
|       const options = this.#options, webGL2Player = this.#webGL2Player; | ||||
|     if (this.playerType === "webgl2") { | ||||
|       const options = this.options, webGL2Player = this.webGL2Player; | ||||
|       if (options.processing === "usm") webGL2Player.setFilter(1); | ||||
|       else webGL2Player.setFilter(2); | ||||
|       Screenshot.updateCanvasFilters("none"), webGL2Player.setSharpness(options.sharpness || 0), webGL2Player.setSaturation(options.saturation || 100), webGL2Player.setContrast(options.contrast || 100), webGL2Player.setBrightness(options.brightness || 100); | ||||
|     } else { | ||||
|       let filters = this.#getVideoPlayerFilterStyle(), videoCss = ""; | ||||
|       let filters = this.getVideoPlayerFilterStyle(), videoCss = ""; | ||||
|       if (filters) videoCss += `filter: ${filters} !important;`; | ||||
|       if (getPref("screenshot_apply_filters")) Screenshot.updateCanvasFilters(filters); | ||||
|       let css = ""; | ||||
|       if (videoCss) css = `#game-stream video { ${videoCss} }`; | ||||
|       this.#$videoCss.textContent = css; | ||||
|       this.$videoCss.textContent = css; | ||||
|     } | ||||
|     this.#resizePlayer(); | ||||
|     this.resizePlayer(); | ||||
|   } | ||||
|   reloadPlayer() { | ||||
|     this.#cleanUpWebGL2Player(), this.#playerType = "default", this.setPlayerType("webgl2", !1); | ||||
|     this.cleanUpWebGL2Player(), this.playerType = "default", this.setPlayerType("webgl2", !1); | ||||
|   } | ||||
|   #cleanUpWebGL2Player() { | ||||
|     this.#webGL2Player?.destroy(), this.#webGL2Player = null; | ||||
|   cleanUpWebGL2Player() { | ||||
|     this.webGL2Player?.destroy(), this.webGL2Player = null; | ||||
|   } | ||||
|   destroy() { | ||||
|     this.#cleanUpWebGL2Player(); | ||||
|     this.cleanUpWebGL2Player(); | ||||
|   } | ||||
| } | ||||
| function patchVideoApi() { | ||||
|   | ||||
| @@ -1,9 +1,15 @@ | ||||
| precision mediump float; | ||||
| uniform sampler2D data; | ||||
| uniform vec2 iResolution; | ||||
|  | ||||
| const int FILTER_UNSHARP_MASKING = 1; | ||||
| const int FILTER_CAS = 2; | ||||
|  | ||||
| precision highp float; | ||||
| uniform sampler2D data; | ||||
| uniform vec2 iResolution; | ||||
| // constrast = 0.8 | ||||
| const float CAS_CONTRAST_PEAK = (-3.0 * 0.8 + 8.0); | ||||
|  | ||||
| // Luminosity factor | ||||
| const vec3 LUMINOSITY_FACTOR = vec3(0.2126, 0.7152, 0.0722); | ||||
|  | ||||
| uniform int filterId; | ||||
| uniform float sharpenFactor; | ||||
| @@ -11,27 +17,24 @@ uniform float brightness; | ||||
| uniform float contrast; | ||||
| uniform float saturation; | ||||
|  | ||||
| vec3 textureAt(sampler2D tex, vec2 coord) { | ||||
|     return texture2D(tex, coord / iResolution.xy).rgb; | ||||
| } | ||||
| vec3 clarityBoost(sampler2D tex, vec2 coord) { | ||||
|     vec2 texelSize = 1.0 / iResolution.xy; | ||||
|  | ||||
| vec3 clarityBoost(sampler2D tex, vec2 coord) | ||||
| { | ||||
|     // Load a collection of samples in a 3x3 neighorhood, where e is the current pixel. | ||||
|     // a b c | ||||
|     // d e f | ||||
|     // g h i | ||||
|     vec3 a = textureAt(tex, coord + vec2(-1, 1)); | ||||
|     vec3 b = textureAt(tex, coord + vec2(0, 1)); | ||||
|     vec3 c = textureAt(tex, coord + vec2(1, 1)); | ||||
|     vec3 a = texture2D(tex, coord + texelSize * vec2(-1, 1)).rgb; | ||||
|     vec3 b = texture2D(tex, coord + texelSize * vec2(0, 1)).rgb; | ||||
|     vec3 c = texture2D(tex, coord + texelSize * vec2(1, 1)).rgb; | ||||
|  | ||||
|     vec3 d = textureAt(tex, coord + vec2(-1, 0)); | ||||
|     vec3 e = textureAt(tex, coord); | ||||
|     vec3 f = textureAt(tex, coord + vec2(1, 0)); | ||||
|     vec3 d = texture2D(tex, coord + texelSize * vec2(-1, 0)).rgb; | ||||
|     vec3 e = texture2D(tex, coord).rgb; | ||||
|     vec3 f = texture2D(tex, coord + texelSize * vec2(1, 0)).rgb; | ||||
|  | ||||
|     vec3 g = textureAt(tex, coord + vec2(-1, -1)); | ||||
|     vec3 h = textureAt(tex, coord + vec2(0, -1)); | ||||
|     vec3 i = textureAt(tex, coord + vec2(1, -1)); | ||||
|     vec3 g = texture2D(tex, coord + texelSize * vec2(-1, -1)).rgb; | ||||
|     vec3 h = texture2D(tex, coord + texelSize * vec2(0, -1)).rgb; | ||||
|     vec3 i = texture2D(tex, coord + texelSize * vec2(1, -1)).rgb; | ||||
|  | ||||
|     if (filterId == FILTER_CAS) { | ||||
|         // Soft min and max. | ||||
| @@ -54,10 +57,7 @@ vec3 clarityBoost(sampler2D tex, vec2 coord) | ||||
|         // Shaping amount of sharpening. | ||||
|         amplifyRgb = inversesqrt(amplifyRgb); | ||||
|  | ||||
|         float contrast = 0.8; | ||||
|         float peak = -3.0 * contrast + 8.0; | ||||
|         vec3 weightRgb = -(1.0 / (amplifyRgb * peak)); | ||||
|  | ||||
|         vec3 weightRgb = -(1.0 / (amplifyRgb * CAS_CONTRAST_PEAK)); | ||||
|         vec3 reciprocalWeightRgb = 1.0 / (4.0 * weightRgb + 1.0); | ||||
|  | ||||
|         //                0 w 0 | ||||
| @@ -70,9 +70,10 @@ vec3 clarityBoost(sampler2D tex, vec2 coord) | ||||
|  | ||||
|         return outColor; | ||||
|     } else if (filterId == FILTER_UNSHARP_MASKING) { | ||||
|         vec3 gaussianBlur = (a * 1.0 + b * 2.0 + c * 1.0 + | ||||
|             d * 2.0 + e * 4.0 + f * 2.0 + | ||||
|             g * 1.0 + h * 2.0 + i * 1.0) / 16.0; | ||||
|         vec3 gaussianBlur = (a + c + g + i) * 1.0 + | ||||
|                             (b + d + f + h) * 2.0 + | ||||
|                             e * 4.0; | ||||
|         gaussianBlur /= 16.0; | ||||
|  | ||||
|         // Return edge detection | ||||
|         return e + (e - gaussianBlur) * sharpenFactor / 3.0; | ||||
| @@ -81,40 +82,30 @@ vec3 clarityBoost(sampler2D tex, vec2 coord) | ||||
|     return e; | ||||
| } | ||||
|  | ||||
| vec3 adjustBrightness(vec3 color) { | ||||
|     return (1.0 + brightness) * color; | ||||
| } | ||||
|  | ||||
| vec3 adjustContrast(vec3 color) { | ||||
|     return 0.5 + (1.0 + contrast) * (color - 0.5); | ||||
| } | ||||
|  | ||||
| vec3 adjustSaturation(vec3 color) { | ||||
|     const vec3 luminosityFactor = vec3(0.2126, 0.7152, 0.0722); | ||||
|     vec3 grayscale = vec3(dot(color, luminosityFactor)); | ||||
|  | ||||
|     return mix(grayscale, color, 1.0 + saturation); | ||||
| } | ||||
|  | ||||
| void main() { | ||||
|     vec3 color; | ||||
|     vec2 uv = gl_FragCoord.xy / iResolution.xy; | ||||
|  | ||||
|     if (sharpenFactor > 0.0) { | ||||
|         color = clarityBoost(data, gl_FragCoord.xy); | ||||
|         color = clarityBoost(data, uv); | ||||
|     } else { | ||||
|         color = textureAt(data, gl_FragCoord.xy); | ||||
|         color = texture2D(data, uv).rgb; | ||||
|     } | ||||
|  | ||||
|     if (saturation != 0.0) { | ||||
|         color = adjustSaturation(color); | ||||
|     // Saturation | ||||
|     if (saturation != 1.0) { | ||||
|         vec3 grayscale = vec3(dot(color, LUMINOSITY_FACTOR)); | ||||
|         color = mix(grayscale, color, saturation); | ||||
|     } | ||||
|  | ||||
|     if (contrast != 0.0) { | ||||
|         color = adjustContrast(color); | ||||
|     // Contrast | ||||
|     if (contrast != 1.0) { | ||||
|         color = 0.5 + contrast * (color - 0.5); | ||||
|     } | ||||
|  | ||||
|     if (brightness != 0.0) { | ||||
|         color = adjustBrightness(color); | ||||
|     // Brightness | ||||
|     if (brightness != 1.0) { | ||||
|         color = brightness * color; | ||||
|     } | ||||
|  | ||||
|     gl_FragColor = vec4(color, 1.0); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| attribute vec2 position; | ||||
| attribute vec4 position; | ||||
|  | ||||
| void main() { | ||||
|     gl_Position = vec4(position, 0, 1); | ||||
|     gl_Position = position; | ||||
| } | ||||
|   | ||||
| @@ -8,16 +8,16 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage"; | ||||
| const LOG_TAG = 'WebGL2Player'; | ||||
|  | ||||
| export class WebGL2Player { | ||||
|     #$video: HTMLVideoElement; | ||||
|     #$canvas: HTMLCanvasElement; | ||||
|     private $video: HTMLVideoElement; | ||||
|     private $canvas: HTMLCanvasElement; | ||||
|  | ||||
|     #gl: WebGL2RenderingContext | null = null; | ||||
|     #resources: Array<any> = []; | ||||
|     #program: WebGLProgram | null = null; | ||||
|     private gl: WebGL2RenderingContext | null = null; | ||||
|     private resources: Array<any> = []; | ||||
|     private program: WebGLProgram | null = null; | ||||
|  | ||||
|     #stopped: boolean = false; | ||||
|     private stopped: boolean = false; | ||||
|  | ||||
|     #options = { | ||||
|     private options = { | ||||
|         filterId: 1, | ||||
|         sharpenFactor: 0, | ||||
|         brightness: 0.0, | ||||
| @@ -25,112 +25,112 @@ export class WebGL2Player { | ||||
|         saturation: 0.0, | ||||
|     }; | ||||
|  | ||||
|     #animFrameId: number | null = null; | ||||
|     private animFrameId: number | null = null; | ||||
|  | ||||
|     constructor($video: HTMLVideoElement) { | ||||
|         BxLogger.info(LOG_TAG, 'Initialize'); | ||||
|         this.#$video = $video; | ||||
|         this.$video = $video; | ||||
|  | ||||
|         const $canvas = document.createElement('canvas'); | ||||
|         $canvas.width = $video.videoWidth; | ||||
|         $canvas.height = $video.videoHeight; | ||||
|         this.#$canvas = $canvas; | ||||
|         this.$canvas = $canvas; | ||||
|  | ||||
|         this.#setupShaders(); | ||||
|         this.#setupRendering(); | ||||
|         this.setupShaders(); | ||||
|         this.setupRendering(); | ||||
|  | ||||
|         $video.insertAdjacentElement('afterend', $canvas); | ||||
|     } | ||||
|  | ||||
|     setFilter(filterId: number, update = true) { | ||||
|         this.#options.filterId = filterId; | ||||
|         this.options.filterId = filterId; | ||||
|         update && this.updateCanvas(); | ||||
|     } | ||||
|  | ||||
|     setSharpness(sharpness: number, update = true) { | ||||
|         this.#options.sharpenFactor = sharpness; | ||||
|         this.options.sharpenFactor = sharpness; | ||||
|         update && this.updateCanvas(); | ||||
|     } | ||||
|  | ||||
|     setBrightness(brightness: number, update = true) { | ||||
|         this.#options.brightness = (brightness - 100) / 100; | ||||
|         this.options.brightness = 1 + (brightness - 100) / 100; | ||||
|         update && this.updateCanvas(); | ||||
|     } | ||||
|  | ||||
|     setContrast(contrast: number, update = true) { | ||||
|         this.#options.contrast = (contrast - 100) / 100; | ||||
|         this.options.contrast = 1 + (contrast - 100) / 100; | ||||
|         update && this.updateCanvas(); | ||||
|     } | ||||
|  | ||||
|     setSaturation(saturation: number, update = true) { | ||||
|         this.#options.saturation = (saturation - 100) / 100; | ||||
|         this.options.saturation = 1 + (saturation - 100) / 100; | ||||
|         update && this.updateCanvas(); | ||||
|     } | ||||
|  | ||||
|     getCanvas() { | ||||
|         return this.#$canvas; | ||||
|         return this.$canvas; | ||||
|     } | ||||
|  | ||||
|     updateCanvas() { | ||||
|         const gl = this.#gl!; | ||||
|         const program = this.#program!; | ||||
|         const gl = this.gl!; | ||||
|         const program = this.program!; | ||||
|  | ||||
|         gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this.#$canvas.width, this.#$canvas.height); | ||||
|         gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this.$canvas.width, this.$canvas.height); | ||||
|  | ||||
|         gl.uniform1i(gl.getUniformLocation(program, 'filterId'), this.#options.filterId); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'sharpenFactor'), this.#options.sharpenFactor); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'brightness'), this.#options.brightness); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'contrast'), this.#options.contrast); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'saturation'), this.#options.saturation); | ||||
|         gl.uniform1i(gl.getUniformLocation(program, 'filterId'), this.options.filterId); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'sharpenFactor'), this.options.sharpenFactor); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'brightness'), this.options.brightness); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'contrast'), this.options.contrast); | ||||
|         gl.uniform1f(gl.getUniformLocation(program, 'saturation'), this.options.saturation); | ||||
|     } | ||||
|  | ||||
|     drawFrame() { | ||||
|         const gl = this.#gl!; | ||||
|         const $video = this.#$video; | ||||
|         const gl = this.gl!; | ||||
|         const $video = this.$video; | ||||
|  | ||||
|         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, $video); | ||||
|         gl.drawArrays(gl.TRIANGLES, 0, 6); | ||||
|     } | ||||
|  | ||||
|     #setupRendering() { | ||||
|     private setupRendering() { | ||||
|         let animate: any; | ||||
|  | ||||
|         if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) { | ||||
|             const $video = this.#$video; | ||||
|             const $video = this.$video; | ||||
|             animate = () => { | ||||
|                 if (this.#stopped) { | ||||
|                 if (this.stopped) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.drawFrame(); | ||||
|                 this.#animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|                 this.animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|             } | ||||
|  | ||||
|             this.#animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|             this.animFrameId = $video.requestVideoFrameCallback(animate); | ||||
|         } else { | ||||
|             animate = () => { | ||||
|                 if (this.#stopped) { | ||||
|                 if (this.stopped) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.drawFrame(); | ||||
|                 this.#animFrameId = requestAnimationFrame(animate); | ||||
|                 this.animFrameId = requestAnimationFrame(animate); | ||||
|             } | ||||
|  | ||||
|             this.#animFrameId = requestAnimationFrame(animate); | ||||
|             this.animFrameId = requestAnimationFrame(animate); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #setupShaders() { | ||||
|     private setupShaders() { | ||||
|         BxLogger.info(LOG_TAG, 'Setting up', getPref(PrefKey.VIDEO_POWER_PREFERENCE)); | ||||
|  | ||||
|         const gl = this.#$canvas.getContext('webgl', { | ||||
|         const gl = this.$canvas.getContext('webgl', { | ||||
|             isBx: true, | ||||
|             antialias: true, | ||||
|             alpha: false, | ||||
|             powerPreference: getPref(PrefKey.VIDEO_POWER_PREFERENCE), | ||||
|         }) as WebGL2RenderingContext; | ||||
|         this.#gl = gl; | ||||
|         this.gl = gl; | ||||
|  | ||||
|         gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferWidth); | ||||
|  | ||||
| @@ -145,7 +145,7 @@ export class WebGL2Player { | ||||
|  | ||||
|         // Create and link program | ||||
|         const program = gl.createProgram()!; | ||||
|         this.#program = program; | ||||
|         this.program = program; | ||||
|  | ||||
|         gl.attachShader(program, vShader); | ||||
|         gl.attachShader(program, fShader); | ||||
| @@ -162,7 +162,7 @@ export class WebGL2Player { | ||||
|  | ||||
|         // Vertices: A screen-filling quad made from two triangles | ||||
|         const buffer = gl.createBuffer(); | ||||
|         this.#resources.push(buffer); | ||||
|         this.resources.push(buffer); | ||||
|  | ||||
|         gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | ||||
|         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | ||||
| @@ -179,7 +179,7 @@ export class WebGL2Player { | ||||
|  | ||||
|         // Texture to contain the video data | ||||
|         const texture = gl.createTexture(); | ||||
|         this.#resources.push(texture); | ||||
|         this.resources.push(texture); | ||||
|  | ||||
|         gl.bindTexture(gl.TEXTURE_2D, texture); | ||||
|         gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | ||||
| @@ -197,26 +197,26 @@ export class WebGL2Player { | ||||
|  | ||||
|     resume() { | ||||
|         this.stop(); | ||||
|         this.#stopped = false; | ||||
|         this.stopped = false; | ||||
|         BxLogger.info(LOG_TAG, 'Resume'); | ||||
|  | ||||
|         this.#$canvas.classList.remove('bx-gone'); | ||||
|         this.#setupRendering(); | ||||
|         this.$canvas.classList.remove('bx-gone'); | ||||
|         this.setupRendering(); | ||||
|     } | ||||
|  | ||||
|     stop() { | ||||
|         BxLogger.info(LOG_TAG, 'Stop'); | ||||
|         this.#$canvas.classList.add('bx-gone'); | ||||
|         this.$canvas.classList.add('bx-gone'); | ||||
|  | ||||
|         this.#stopped = true; | ||||
|         if (this.#animFrameId) { | ||||
|         this.stopped = true; | ||||
|         if (this.animFrameId) { | ||||
|             if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) { | ||||
|                 this.#$video.cancelVideoFrameCallback(this.#animFrameId); | ||||
|                 this.$video.cancelVideoFrameCallback(this.animFrameId); | ||||
|             } else { | ||||
|                 cancelAnimationFrame(this.#animFrameId); | ||||
|                 cancelAnimationFrame(this.animFrameId); | ||||
|             } | ||||
|  | ||||
|             this.#animFrameId = null; | ||||
|             this.animFrameId = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -224,11 +224,11 @@ export class WebGL2Player { | ||||
|         BxLogger.info(LOG_TAG, 'Destroy'); | ||||
|         this.stop(); | ||||
|  | ||||
|         const gl = this.#gl; | ||||
|         const gl = this.gl; | ||||
|         if (gl) { | ||||
|             gl.getExtension('WEBGL_lose_context')?.loseContext(); | ||||
|  | ||||
|             for (const resource of this.#resources) { | ||||
|             for (const resource of this.resources) { | ||||
|                 if (resource instanceof WebGLProgram) { | ||||
|                     gl.useProgram(null); | ||||
|                     gl.deleteProgram(resource); | ||||
| @@ -241,14 +241,14 @@ export class WebGL2Player { | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.#gl = null; | ||||
|             this.gl = null; | ||||
|         } | ||||
|  | ||||
|         if (this.#$canvas.isConnected) { | ||||
|             this.#$canvas.parentElement?.removeChild(this.#$canvas); | ||||
|         if (this.$canvas.isConnected) { | ||||
|             this.$canvas.parentElement?.removeChild(this.$canvas); | ||||
|         } | ||||
|  | ||||
|         this.#$canvas.width = 1; | ||||
|         this.#$canvas.height = 1; | ||||
|         this.$canvas.width = 1; | ||||
|         this.$canvas.height = 1; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -17,35 +17,35 @@ export type StreamPlayerOptions = Partial<{ | ||||
| }>; | ||||
|  | ||||
| export class StreamPlayer { | ||||
|     #$video: HTMLVideoElement; | ||||
|     #playerType: StreamPlayerType = StreamPlayerType.VIDEO; | ||||
|     private $video: HTMLVideoElement; | ||||
|     private playerType: StreamPlayerType = StreamPlayerType.VIDEO; | ||||
|  | ||||
|     #options: StreamPlayerOptions = {}; | ||||
|     private options: StreamPlayerOptions = {}; | ||||
|  | ||||
|     #webGL2Player: WebGL2Player | null = null; | ||||
|     private webGL2Player: WebGL2Player | null = null; | ||||
|  | ||||
|     #$videoCss: HTMLStyleElement | null = null; | ||||
|     #$usmMatrix: SVGFEConvolveMatrixElement | null = null; | ||||
|     private $videoCss: HTMLStyleElement | null = null; | ||||
|     private $usmMatrix: SVGFEConvolveMatrixElement | null = null; | ||||
|  | ||||
|     constructor($video: HTMLVideoElement, type: StreamPlayerType, options: StreamPlayerOptions) { | ||||
|         this.#setupVideoElements(); | ||||
|         this.setupVideoElements(); | ||||
|  | ||||
|         this.#$video = $video; | ||||
|         this.#options = options || {}; | ||||
|         this.$video = $video; | ||||
|         this.options = options || {}; | ||||
|         this.setPlayerType(type); | ||||
|     } | ||||
|  | ||||
|     #setupVideoElements() { | ||||
|         this.#$videoCss = document.getElementById('bx-video-css') as HTMLStyleElement; | ||||
|         if (this.#$videoCss) { | ||||
|             this.#$usmMatrix = this.#$videoCss.querySelector('#bx-filter-usm-matrix') as any; | ||||
|     private setupVideoElements() { | ||||
|         this.$videoCss = document.getElementById('bx-video-css') as HTMLStyleElement; | ||||
|         if (this.$videoCss) { | ||||
|             this.$usmMatrix = this.$videoCss.querySelector('#bx-filter-usm-matrix') as any; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const $fragment = document.createDocumentFragment(); | ||||
|  | ||||
|         this.#$videoCss = CE<HTMLStyleElement>('style', {id: 'bx-video-css'}); | ||||
|         $fragment.appendChild(this.#$videoCss); | ||||
|         this.$videoCss = CE<HTMLStyleElement>('style', {id: 'bx-video-css'}); | ||||
|         $fragment.appendChild(this.$videoCss); | ||||
|  | ||||
|         // Setup SVG filters | ||||
|         const $svg = CE('svg', { | ||||
| @@ -56,7 +56,7 @@ export class StreamPlayer { | ||||
|             CE('filter', { | ||||
|                     id: 'bx-filter-usm', | ||||
|                     xmlns: 'http://www.w3.org/2000/svg', | ||||
|                 }, this.#$usmMatrix = CE('feConvolveMatrix', { | ||||
|                 }, this.$usmMatrix = CE('feConvolveMatrix', { | ||||
|                     id: 'bx-filter-usm-matrix', | ||||
|                     order: '3', | ||||
|                     xmlns: 'http://www.w3.org/2000/svg', | ||||
| @@ -67,29 +67,29 @@ export class StreamPlayer { | ||||
|         document.documentElement.appendChild($fragment); | ||||
|     } | ||||
|  | ||||
|     #getVideoPlayerFilterStyle() { | ||||
|     private getVideoPlayerFilterStyle() { | ||||
|         const filters = []; | ||||
|  | ||||
|         const sharpness = this.#options.sharpness || 0; | ||||
|         if (this.#options.processing === StreamVideoProcessing.USM && sharpness != 0) { | ||||
|         const sharpness = this.options.sharpness || 0; | ||||
|         if (this.options.processing === StreamVideoProcessing.USM && sharpness != 0) { | ||||
|             const level = (7 - ((sharpness / 2) - 1) * 0.5).toFixed(1); // 5, 5.5, 6, 6.5, 7 | ||||
|             const matrix = `0 -1 0 -1 ${level} -1 0 -1 0`; | ||||
|             this.#$usmMatrix?.setAttributeNS(null, 'kernelMatrix', matrix); | ||||
|             this.$usmMatrix?.setAttributeNS(null, 'kernelMatrix', matrix); | ||||
|  | ||||
|             filters.push(`url(#bx-filter-usm)`); | ||||
|         } | ||||
|  | ||||
|         const saturation = this.#options.saturation || 100; | ||||
|         const saturation = this.options.saturation || 100; | ||||
|         if (saturation != 100) { | ||||
|             filters.push(`saturate(${saturation}%)`); | ||||
|         } | ||||
|  | ||||
|         const contrast =  this.#options.contrast || 100; | ||||
|         const contrast =  this.options.contrast || 100; | ||||
|         if (contrast != 100) { | ||||
|             filters.push(`contrast(${contrast}%)`); | ||||
|         } | ||||
|  | ||||
|         const brightness = this.#options.brightness || 100; | ||||
|         const brightness = this.options.brightness || 100; | ||||
|         if (brightness != 100) { | ||||
|             filters.push(`brightness(${brightness}%)`); | ||||
|         } | ||||
| @@ -97,14 +97,14 @@ export class StreamPlayer { | ||||
|         return filters.join(' '); | ||||
|     } | ||||
|  | ||||
|     #resizePlayer() { | ||||
|     private resizePlayer() { | ||||
|         const PREF_RATIO = getPref(PrefKey.VIDEO_RATIO); | ||||
|         const $video = this.#$video; | ||||
|         const $video = this.$video; | ||||
|         const isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport; | ||||
|  | ||||
|         let $webGL2Canvas; | ||||
|         if (this.#playerType == StreamPlayerType.WEBGL2) { | ||||
|             $webGL2Canvas = this.#webGL2Player?.getCanvas()!; | ||||
|         if (this.playerType == StreamPlayerType.WEBGL2) { | ||||
|             $webGL2Canvas = this.webGL2Player?.getCanvas()!; | ||||
|         } | ||||
|  | ||||
|         let targetWidth; | ||||
| @@ -166,67 +166,67 @@ export class StreamPlayer { | ||||
|         } | ||||
|  | ||||
|         // Update video dimensions | ||||
|         if (isNativeTouchGame && this.#playerType == StreamPlayerType.WEBGL2) { | ||||
|         if (isNativeTouchGame && this.playerType == StreamPlayerType.WEBGL2) { | ||||
|             window.BX_EXPOSED.streamSession.updateDimensions(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     setPlayerType(type: StreamPlayerType, refreshPlayer: boolean = false) { | ||||
|         if (this.#playerType !== type) { | ||||
|         if (this.playerType !== type) { | ||||
|             // Switch from Video -> WebGL2 | ||||
|             if (type === StreamPlayerType.WEBGL2) { | ||||
|                 // Initialize WebGL2 player | ||||
|                 if (!this.#webGL2Player) { | ||||
|                     this.#webGL2Player = new WebGL2Player(this.#$video); | ||||
|                 if (!this.webGL2Player) { | ||||
|                     this.webGL2Player = new WebGL2Player(this.$video); | ||||
|                 } else { | ||||
|                     this.#webGL2Player.resume(); | ||||
|                     this.webGL2Player.resume(); | ||||
|                 } | ||||
|  | ||||
|                 this.#$videoCss!.textContent = ''; | ||||
|                 this.$videoCss!.textContent = ''; | ||||
|  | ||||
|                 this.#$video.classList.add('bx-pixel'); | ||||
|                 this.$video.classList.add('bx-pixel'); | ||||
|             } else { | ||||
|                 // Cleanup WebGL2 Player | ||||
|                 this.#webGL2Player?.stop(); | ||||
|                 this.webGL2Player?.stop(); | ||||
|  | ||||
|                 this.#$video.classList.remove('bx-pixel'); | ||||
|                 this.$video.classList.remove('bx-pixel'); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.#playerType = type; | ||||
|         this.playerType = type; | ||||
|         refreshPlayer && this.refreshPlayer(); | ||||
|     } | ||||
|  | ||||
|     setOptions(options: StreamPlayerOptions, refreshPlayer: boolean = false) { | ||||
|         this.#options = options; | ||||
|         this.options = options; | ||||
|         refreshPlayer && this.refreshPlayer(); | ||||
|     } | ||||
|  | ||||
|     updateOptions(options: StreamPlayerOptions, refreshPlayer: boolean = false) { | ||||
|         this.#options = Object.assign(this.#options, options); | ||||
|         this.options = Object.assign(this.options, options); | ||||
|         refreshPlayer && this.refreshPlayer(); | ||||
|     } | ||||
|  | ||||
|     getPlayerElement(playerType?: StreamPlayerType) { | ||||
|         if (typeof playerType === 'undefined') { | ||||
|             playerType = this.#playerType; | ||||
|             playerType = this.playerType; | ||||
|         } | ||||
|  | ||||
|         if (playerType === StreamPlayerType.WEBGL2) { | ||||
|             return this.#webGL2Player?.getCanvas(); | ||||
|             return this.webGL2Player?.getCanvas(); | ||||
|         } | ||||
|  | ||||
|         return this.#$video; | ||||
|         return this.$video; | ||||
|     } | ||||
|  | ||||
|     getWebGL2Player() { | ||||
|         return this.#webGL2Player; | ||||
|         return this.webGL2Player; | ||||
|     } | ||||
|  | ||||
|     refreshPlayer() { | ||||
|         if (this.#playerType === StreamPlayerType.WEBGL2) { | ||||
|             const options = this.#options; | ||||
|             const webGL2Player = this.#webGL2Player!; | ||||
|         if (this.playerType === StreamPlayerType.WEBGL2) { | ||||
|             const options = this.options; | ||||
|             const webGL2Player = this.webGL2Player!; | ||||
|  | ||||
|             if (options.processing === StreamVideoProcessing.USM) { | ||||
|                 webGL2Player.setFilter(1); | ||||
| @@ -241,7 +241,7 @@ export class StreamPlayer { | ||||
|             webGL2Player.setContrast(options.contrast || 100); | ||||
|             webGL2Player.setBrightness(options.brightness || 100); | ||||
|         } else { | ||||
|             let filters = this.#getVideoPlayerFilterStyle(); | ||||
|             let filters = this.getVideoPlayerFilterStyle(); | ||||
|             let videoCss = ''; | ||||
|             if (filters) { | ||||
|                 videoCss += `filter: ${filters} !important;`; | ||||
| @@ -257,26 +257,26 @@ export class StreamPlayer { | ||||
|                 css = `#game-stream video { ${videoCss} }`; | ||||
|             } | ||||
|  | ||||
|             this.#$videoCss!.textContent = css; | ||||
|             this.$videoCss!.textContent = css; | ||||
|         } | ||||
|  | ||||
|         this.#resizePlayer(); | ||||
|         this.resizePlayer(); | ||||
|     } | ||||
|  | ||||
|     reloadPlayer() { | ||||
|         this.#cleanUpWebGL2Player(); | ||||
|         this.cleanUpWebGL2Player(); | ||||
|  | ||||
|         this.#playerType = StreamPlayerType.VIDEO; | ||||
|         this.playerType = StreamPlayerType.VIDEO; | ||||
|         this.setPlayerType(StreamPlayerType.WEBGL2, false); | ||||
|     } | ||||
|  | ||||
|     #cleanUpWebGL2Player() { | ||||
|     private cleanUpWebGL2Player() { | ||||
|         // Clean up WebGL2 Player | ||||
|         this.#webGL2Player?.destroy(); | ||||
|         this.#webGL2Player = null; | ||||
|         this.webGL2Player?.destroy(); | ||||
|         this.webGL2Player = null; | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         this.#cleanUpWebGL2Player(); | ||||
|         this.cleanUpWebGL2Player(); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 redphx
					redphx