mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-04 22:57:19 +02:00
Update better-xcloud.user.js
This commit is contained in:
parent
2cdf92b159
commit
6150c2ea70
220
dist/better-xcloud.user.js
vendored
220
dist/better-xcloud.user.js
vendored
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 4.8.0-beta
|
||||
// @version 4.8.0-beta-2
|
||||
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
||||
// @author redphx
|
||||
// @license MIT
|
||||
@ -122,7 +122,7 @@ class UserAgent {
|
||||
}
|
||||
|
||||
// src/utils/global.ts
|
||||
var SCRIPT_VERSION = "4.8.0-beta";
|
||||
var SCRIPT_VERSION = "4.8.0-beta-2";
|
||||
var AppInterface = window.AppInterface;
|
||||
UserAgent.init();
|
||||
var userAgent = window.navigator.userAgent.toLowerCase();
|
||||
@ -354,8 +354,11 @@ var Texts = {
|
||||
activated: "Activated",
|
||||
active: "Active",
|
||||
advanced: "Advanced",
|
||||
"amd-fidelity-cas": "AMD FidelityFX CAS",
|
||||
"android-app-settings": "Android app settings",
|
||||
apply: "Apply",
|
||||
"aspect-ratio": "Aspect ratio",
|
||||
"aspect-ratio-note": "Don't use with native touch games",
|
||||
audio: "Audio",
|
||||
auto: "Auto",
|
||||
"back-to-home": "Back to home",
|
||||
@ -376,7 +379,7 @@ var Texts = {
|
||||
"can-stream-xbox-360-games": "Can stream Xbox 360 games",
|
||||
cancel: "Cancel",
|
||||
"cant-stream-xbox-360-games": "Can't stream Xbox 360 games",
|
||||
clarity: "Clarity",
|
||||
"clarity-boost": "Clarity boost",
|
||||
"clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON",
|
||||
clear: "Clear",
|
||||
close: "Close",
|
||||
@ -480,14 +483,14 @@ var Texts = {
|
||||
,
|
||||
(e) => `${e.key}: Funktion an-/ausschalten`,
|
||||
,
|
||||
,
|
||||
(e) => `Pulsa ${e.key} para alternar esta función`,
|
||||
(e) => `Appuyez sur ${e.key} pour activer cette fonctionnalité`,
|
||||
(e) => `Premi ${e.key} per attivare questa funzionalità`,
|
||||
(e) => `${e.key} でこの機能を切替`,
|
||||
(e) => `${e.key} 키를 눌러 이 기능을 켜고 끄세요`,
|
||||
(e) => `Naciśnij ${e.key} aby przełączyć tę funkcję`,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
(e) => `Нажмите ${e.key} для переключения этой функции`,
|
||||
,
|
||||
(e) => `Etkinleştirmek için ${e.key} tuşuna basın`,
|
||||
(e) => `Натисніть ${e.key} щоб перемкнути цю функцію`,
|
||||
@ -496,12 +499,12 @@ var Texts = {
|
||||
],
|
||||
"press-to-bind": "Press a key or do a mouse click to bind...",
|
||||
"prompt-preset-name": "Preset's name:",
|
||||
ratio: "Ratio",
|
||||
"reduce-animations": "Reduce UI animations",
|
||||
region: "Region",
|
||||
"reload-stream": "Reload stream",
|
||||
"remote-play": "Remote Play",
|
||||
rename: "Rename",
|
||||
renderer: "Renderer",
|
||||
"right-click-to-unbind": "Right-click on a key to unbind it",
|
||||
"right-stick": "Right stick",
|
||||
"rocket-always-hide": "Always hide",
|
||||
@ -519,6 +522,7 @@ var Texts = {
|
||||
settings: "Settings",
|
||||
"settings-reload": "Reload page to reflect changes",
|
||||
"settings-reloading": "Reloading...",
|
||||
sharpness: "Sharpness",
|
||||
"shortcut-keys": "Shortcut keys",
|
||||
show: "Show",
|
||||
"show-game-art": "Show game art",
|
||||
@ -546,7 +550,6 @@ var Texts = {
|
||||
"stream-settings": "Stream settings",
|
||||
"stream-stats": "Stream stats",
|
||||
stretch: "Stretch",
|
||||
"stretch-note": "Don't use with native touch games",
|
||||
"support-better-xcloud": "Support Better xCloud",
|
||||
"swap-buttons": "Swap buttons",
|
||||
"take-screenshot": "Take screenshot",
|
||||
@ -591,6 +594,7 @@ var Texts = {
|
||||
unknown: "Unknown",
|
||||
unlimited: "Unlimited",
|
||||
unmuted: "Unmuted",
|
||||
"unsharp-masking": "Unsharp masking",
|
||||
"use-mouse-absolute-position": "Use mouse's absolute position",
|
||||
"user-agent-profile": "User-Agent profile",
|
||||
"vertical-scroll-sensitivity": "Vertical scroll sensitivity",
|
||||
@ -605,7 +609,8 @@ var Texts = {
|
||||
"visual-quality-normal": "Normal",
|
||||
volume: "Volume",
|
||||
"wait-time-countdown": "Countdown",
|
||||
"wait-time-estimated": "Estimated finish time"
|
||||
"wait-time-estimated": "Estimated finish time",
|
||||
webgl2: "WebGL2"
|
||||
};
|
||||
|
||||
class Translations {
|
||||
@ -693,10 +698,6 @@ class Translations {
|
||||
}
|
||||
}
|
||||
var t = Translations.get;
|
||||
var ut = (text) => {
|
||||
BxLogger.warning("Untranslated text", text);
|
||||
return text;
|
||||
};
|
||||
Translations.init();
|
||||
|
||||
// src/utils/settings.ts
|
||||
@ -1062,7 +1063,11 @@ class StreamStats {
|
||||
const totalDecodeTimeDiff = stat.totalDecodeTime - lastStat.totalDecodeTime;
|
||||
const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded;
|
||||
const currentDecodeTime = totalDecodeTimeDiff / framesDecodedDiff * 1000;
|
||||
this.#$dt.textContent = `${currentDecodeTime.toFixed(2)}ms`;
|
||||
if (isNaN(currentDecodeTime)) {
|
||||
this.#$dt.textContent = "??ms";
|
||||
} else {
|
||||
this.#$dt.textContent = `${currentDecodeTime.toFixed(2)}ms`;
|
||||
}
|
||||
if (PREF_STATS_CONDITIONAL_FORMATTING) {
|
||||
grade = currentDecodeTime > 12 ? "bad" : currentDecodeTime > 9 ? "ok" : currentDecodeTime > 6 ? "good" : "";
|
||||
this.#$dt.dataset.grade = grade;
|
||||
@ -1645,23 +1650,23 @@ class Preferences {
|
||||
}
|
||||
},
|
||||
[PrefKey.VIDEO_PLAYER_TYPE]: {
|
||||
label: ut("Video player"),
|
||||
label: t("renderer"),
|
||||
default: "default",
|
||||
options: {
|
||||
[StreamPlayerType.VIDEO]: t("default"),
|
||||
[StreamPlayerType.WEBGL2]: ut("WebGL2")
|
||||
[StreamPlayerType.WEBGL2]: t("webgl2")
|
||||
}
|
||||
},
|
||||
[PrefKey.VIDEO_PROCESSING]: {
|
||||
label: ut("Clarity boost"),
|
||||
label: t("clarity-boost"),
|
||||
default: StreamVideoProcessing.USM,
|
||||
options: {
|
||||
[StreamVideoProcessing.USM]: ut("Unsharp Masking"),
|
||||
[StreamVideoProcessing.CAS]: ut("AMD FidelityFX CAS")
|
||||
[StreamVideoProcessing.USM]: t("unsharp-masking"),
|
||||
[StreamVideoProcessing.CAS]: t("amd-fidelity-cas")
|
||||
}
|
||||
},
|
||||
[PrefKey.VIDEO_SHARPNESS]: {
|
||||
label: ut("Sharpness"),
|
||||
label: t("sharpness"),
|
||||
type: SettingElementType.NUMBER_STEPPER,
|
||||
default: 0,
|
||||
min: 0,
|
||||
@ -1671,8 +1676,8 @@ class Preferences {
|
||||
}
|
||||
},
|
||||
[PrefKey.VIDEO_RATIO]: {
|
||||
label: t("ratio"),
|
||||
note: t("stretch-note"),
|
||||
label: t("aspect-ratio"),
|
||||
note: t("aspect-ratio-note"),
|
||||
default: "16:9",
|
||||
options: {
|
||||
"16:9": "16:9",
|
||||
@ -4307,7 +4312,8 @@ var BxExposed = {
|
||||
supportedInputTypes = supportedInputTypes.filter((i) => i !== InputType.CUSTOM_TOUCH_OVERLAY && i !== InputType.GENERIC_TOUCH);
|
||||
titleInfo.details.supportedTabs = [];
|
||||
}
|
||||
titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) || supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
||||
titleInfo.details.hasNativeTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH);
|
||||
titleInfo.details.hasTouchSupport = titleInfo.details.hasNativeTouchSupport || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) || supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === "all") {
|
||||
titleInfo.details.hasFakeTouchSupport = true;
|
||||
supportedInputTypes.push(InputType.GENERIC_TOUCH);
|
||||
@ -6498,6 +6504,10 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
|
||||
.bx-hidden {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
.bx-pixel {
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
}
|
||||
.bx-no-margin {
|
||||
margin: 0 !important;
|
||||
}
|
||||
@ -7892,6 +7902,9 @@ class MouseCursorHider {
|
||||
// src/modules/patches/controller-shortcuts.js
|
||||
var controller_shortcuts_default = "const 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";
|
||||
|
||||
// src/modules/patches/expose-stream-session.js
|
||||
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\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";
|
||||
|
||||
// src/modules/patches/local-co-op-enable.js
|
||||
var local_co_op_enable_default = "let match;\nlet onGamepadChangedStr = this.onGamepadChanged.toString();\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";
|
||||
|
||||
@ -8307,20 +8320,7 @@ BxLogger.info('patchRemotePlayMkb', ${configsVar});
|
||||
return false;
|
||||
}
|
||||
const newCode = `;
|
||||
window.BX_EXPOSED.streamSession = this;
|
||||
|
||||
const orgSetMicrophoneState = this.setMicrophoneState.bind(this);
|
||||
this.setMicrophoneState = state => {
|
||||
orgSetMicrophoneState(state);
|
||||
|
||||
const evt = new Event('${BxEvent.MICROPHONE_STATE_CHANGED}');
|
||||
evt.microphoneState = state;
|
||||
|
||||
window.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
window.dispatchEvent(new Event('${BxEvent.STREAM_SESSION_READY}'))
|
||||
|
||||
${expose_stream_session_default}
|
||||
true` + text;
|
||||
str2 = str2.replace(text, newCode);
|
||||
return str2;
|
||||
@ -9063,38 +9063,39 @@ function patchSdpBitrate(sdp, video, audio) {
|
||||
}
|
||||
|
||||
// src/modules/player/shaders/clarity_boost.vert
|
||||
var clarity_boost_default = "#version 300 es\n\nin vec2 position;\n\nvoid main() {\n gl_Position = vec4(position, 0, 1);\n}\n";
|
||||
var clarity_boost_default = "attribute vec2 position;\n\nvoid main() {\n gl_Position = vec4(position, 0, 1);\n}\n";
|
||||
|
||||
// src/modules/player/shaders/clarity_boost.fs
|
||||
var clarity_boost_default2 = "#version 300 es\n\n#define CONVERT_COLOR_SPACE false\n\nconst 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\nout vec4 outColor;\n\nvec3 linearToSrgb(vec3 inColor) {\n bvec3 cutoff = lessThan(inColor.rgb, vec3(0.0031308));\n vec3 higher = vec3(1.055) * pow(inColor.rgb, vec3(1.0 / 2.4)) - vec3(0.055);\n vec3 lower = inColor.rgb * vec3(12.92);\n\n return mix(higher, lower, cutoff);\n}\n\nvec3 srgbToLinear(vec3 col) {\n bvec3 cutoff = lessThan(col, vec3(0.04045));\n vec3 higher = pow((col + vec3(0.055)) / vec3(1.055), vec3(2.4));\n vec3 lower = col / vec3(12.92);\n\n return mix(higher, lower, cutoff);\n}\n\nvec4 textureAt(sampler2D tex, vec2 coord, bool convertSrgb) {\n vec4 color = texture(tex, coord / iResolution.xy);\n\n if (convertSrgb) {\n return vec4(srgbToLinear(color.rgb), 1.0);\n }\n\n return color;\n}\n\nvec4 clarityBoost(sampler2D tex, vec2 coord)\n{\n // bool convertSrgb = filterId == FILTER_CAS;\n bool convertSrgb = false;\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 vec4 a = textureAt(tex, coord + vec2(-1, 1), convertSrgb);\n vec4 b = textureAt(tex, coord + vec2(0, 1), convertSrgb);\n vec4 c = textureAt(tex, coord + vec2(1, 1), convertSrgb);\n\n vec4 d = textureAt(tex, coord + vec2(-1, 0), convertSrgb);\n vec4 e = textureAt(tex, coord, convertSrgb);\n vec4 f = textureAt(tex, coord + vec2(1, 0), convertSrgb);\n\n vec4 g = textureAt(tex, coord + vec2(-1, -1), convertSrgb);\n vec4 h = textureAt(tex, coord + vec2(0, -1), convertSrgb);\n vec4 i = textureAt(tex, coord + vec2(1, -1), convertSrgb);\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.rgb, e.rgb), min(f.rgb, b.rgb)), h.rgb);\n vec3 minRgb2 = min(min(a.rgb, c.rgb), min(g.rgb, i.rgb));\n minRgb += min(minRgb, minRgb2);\n\n vec3 maxRgb = max(max(max(d.rgb, e.rgb), max(f.rgb, b.rgb)), h.rgb);\n vec3 maxRgb2 = max(max(a.rgb, c.rgb), max(g.rgb, i.rgb));\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 //\t\t\t\t\t\t0 w 0\n // Filter shape:\t\tw 1 w\n //\t\t\t\t\t\t0 w 0\n vec3 window = (b.rgb + d.rgb) + (f.rgb + h.rgb);\n vec3 outColor = clamp((window * weightRgb + e.rgb) * reciprocalWeightRgb, 0.0, 1.0);\n\n outColor = mix(e.rgb, outColor, sharpenFactor / 2.0);\n if (convertSrgb) {\n outColor = linearToSrgb(outColor);\n }\n return vec4(outColor, 1.0);\n } else if (filterId == FILTER_UNSHARP_MASKING) {\n vec4 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;\n }\n\n return e;\n}\n\nvec4 adjustBrightness(vec4 color) {\n // color.rgb += brightness;\n color.rgb = (1.0 + brightness) * color.rgb;\n return color;\n}\n\nvec4 adjustContrast(vec4 color) {\n color.rgb = 0.5 + (1.0 + contrast) * (color.rgb - 0.5);\n return color;\n}\n\nvec4 adjustSaturation(vec4 color) {\n const vec3 luminosityFactor = vec3(0.2126, 0.7152, 0.0722);\n vec3 grayscale = vec3(dot(color.rgb, luminosityFactor));\n\n return vec4(mix(grayscale, color.rgb, 1.0 + saturation), color.a);\n}\n\n\nvoid main() {\n vec4 color;\n\n if (sharpenFactor > 0.0) {\n color = clarityBoost(data, gl_FragCoord.xy);\n } else {\n color = textureAt(data, gl_FragCoord.xy, false);\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 outColor = color;\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\nvarying vec4 outColor;\n\nvec3 linearToSrgb(vec3 inColor) {\n vec3 cutoff = step(inColor.rgb, vec3(0.0031308));\n vec3 higher = vec3(1.055) * pow(inColor.rgb, vec3(1.0 / 2.4)) - vec3(0.055);\n vec3 lower = inColor.rgb * vec3(12.92);\n\n return mix(higher, lower, cutoff);\n}\n\nvec3 srgbToLinear(vec3 col) {\n vec3 cutoff = step(col, vec3(0.04045));\n vec3 higher = pow((col + vec3(0.055)) / vec3(1.055), vec3(2.4));\n vec3 lower = col / vec3(12.92);\n\n return mix(higher, lower, cutoff);\n}\n\nvec4 textureAt(sampler2D tex, vec2 coord, bool convertSrgb) {\n vec4 color = texture2D(tex, coord / iResolution.xy);\n\n if (convertSrgb) {\n return vec4(srgbToLinear(color.rgb), 1.0);\n }\n\n return color;\n}\n\nvec4 clarityBoost(sampler2D tex, vec2 coord)\n{\n // bool convertSrgb = filterId == FILTER_CAS;\n bool convertSrgb = false;\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 vec4 a = textureAt(tex, coord + vec2(-1, 1), convertSrgb);\n vec4 b = textureAt(tex, coord + vec2(0, 1), convertSrgb);\n vec4 c = textureAt(tex, coord + vec2(1, 1), convertSrgb);\n\n vec4 d = textureAt(tex, coord + vec2(-1, 0), convertSrgb);\n vec4 e = textureAt(tex, coord, convertSrgb);\n vec4 f = textureAt(tex, coord + vec2(1, 0), convertSrgb);\n\n vec4 g = textureAt(tex, coord + vec2(-1, -1), convertSrgb);\n vec4 h = textureAt(tex, coord + vec2(0, -1), convertSrgb);\n vec4 i = textureAt(tex, coord + vec2(1, -1), convertSrgb);\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.rgb, e.rgb), min(f.rgb, b.rgb)), h.rgb);\n vec3 minRgb2 = min(min(a.rgb, c.rgb), min(g.rgb, i.rgb));\n minRgb += min(minRgb, minRgb2);\n\n vec3 maxRgb = max(max(max(d.rgb, e.rgb), max(f.rgb, b.rgb)), h.rgb);\n vec3 maxRgb2 = max(max(a.rgb, c.rgb), max(g.rgb, i.rgb));\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 //\t\t\t\t\t\t0 w 0\n // Filter shape:\t\tw 1 w\n //\t\t\t\t\t\t0 w 0\n vec3 window = (b.rgb + d.rgb) + (f.rgb + h.rgb);\n vec3 outColor = clamp((window * weightRgb + e.rgb) * reciprocalWeightRgb, 0.0, 1.0);\n\n outColor = mix(e.rgb, outColor, sharpenFactor / 2.0);\n if (convertSrgb) {\n outColor = linearToSrgb(outColor);\n }\n return vec4(outColor, 1.0);\n } else if (filterId == FILTER_UNSHARP_MASKING) {\n vec4 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;\n }\n\n return e;\n}\n\nvec4 adjustBrightness(vec4 color) {\n // color.rgb += brightness;\n color.rgb = (1.0 + brightness) * color.rgb;\n return color;\n}\n\nvec4 adjustContrast(vec4 color) {\n color.rgb = 0.5 + (1.0 + contrast) * (color.rgb - 0.5);\n return color;\n}\n\nvec4 adjustSaturation(vec4 color) {\n const vec3 luminosityFactor = vec3(0.2126, 0.7152, 0.0722);\n vec3 grayscale = vec3(dot(color.rgb, luminosityFactor));\n\n return vec4(mix(grayscale, color.rgb, 1.0 + saturation), color.a);\n}\n\n\nvoid main() {\n vec4 color;\n\n if (sharpenFactor > 0.0) {\n color = clarityBoost(data, gl_FragCoord.xy);\n } else {\n color = textureAt(data, gl_FragCoord.xy, false);\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 = color;\n}\n";
|
||||
|
||||
// src/modules/player/webgl2-player.ts
|
||||
var LOG_TAG7 = "WebGL2Player";
|
||||
|
||||
class WebGL2Player {
|
||||
#$video;
|
||||
#$canvas;
|
||||
#gl = null;
|
||||
#resources = [];
|
||||
#program = null;
|
||||
#texture = null;
|
||||
#stopped = false;
|
||||
#options = {
|
||||
filterId: 1,
|
||||
sharpenFactor: 5,
|
||||
sharpenFactor: 0,
|
||||
brightness: 0,
|
||||
contrast: 0,
|
||||
saturation: 0
|
||||
};
|
||||
#animFrameId = null;
|
||||
constructor($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();
|
||||
$video.nativePlay();
|
||||
$video.insertAdjacentElement("afterend", $canvas);
|
||||
$video.classList.add("bx-gone");
|
||||
this.#setupRendering();
|
||||
$video.insertAdjacentElement("afterend", $canvas);
|
||||
}
|
||||
setFilter(filterId, update = true) {
|
||||
this.#options.filterId = filterId;
|
||||
@ -9132,7 +9133,7 @@ class WebGL2Player {
|
||||
drawFrame() {
|
||||
const gl = this.#gl;
|
||||
const $video = this.#$video;
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, $video);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, $video);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
#setupRendering() {
|
||||
@ -9185,7 +9186,9 @@ class WebGL2Player {
|
||||
console.error(`fs info-log: ${gl.getShaderInfoLog(fShader)}`);
|
||||
}
|
||||
this.updateCanvas();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
|
||||
const buffer = gl.createBuffer();
|
||||
this.#resources.push(buffer);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
-1,
|
||||
-1,
|
||||
@ -9203,7 +9206,7 @@ class WebGL2Player {
|
||||
gl.enableVertexAttribArray(0);
|
||||
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
||||
const texture = gl.createTexture();
|
||||
this.#texture = texture;
|
||||
this.#resources.push(texture);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
@ -9212,9 +9215,17 @@ class WebGL2Player {
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.uniform1i(gl.getUniformLocation(program, "data"), 0);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
}
|
||||
destroy() {
|
||||
resume() {
|
||||
this.stop();
|
||||
this.#stopped = false;
|
||||
BxLogger.info(LOG_TAG7, "Resume");
|
||||
this.#$canvas.classList.remove("bx-gone");
|
||||
this.#setupRendering();
|
||||
}
|
||||
stop() {
|
||||
BxLogger.info(LOG_TAG7, "Stop");
|
||||
this.#$canvas.classList.add("bx-gone");
|
||||
this.#stopped = true;
|
||||
if (this.#animFrameId) {
|
||||
if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) {
|
||||
@ -9224,14 +9235,26 @@ class WebGL2Player {
|
||||
}
|
||||
this.#animFrameId = null;
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
BxLogger.info(LOG_TAG7, "Destroy");
|
||||
this.stop();
|
||||
const gl = this.#gl;
|
||||
if (gl) {
|
||||
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
||||
gl.useProgram(null);
|
||||
this.#program && gl.deleteProgram(this.#program);
|
||||
this.#program = null;
|
||||
this.#texture && gl.deleteTexture(this.#texture);
|
||||
this.#texture = null;
|
||||
for (const 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;
|
||||
}
|
||||
if (this.#$canvas.isConnected) {
|
||||
this.#$canvas.parentElement?.removeChild(this.#$canvas);
|
||||
@ -9258,6 +9281,7 @@ class StreamPlayer {
|
||||
#setupVideoElements() {
|
||||
this.#$videoCss = document.getElementById("bx-video-css");
|
||||
if (this.#$videoCss) {
|
||||
this.#$usmMatrix = this.#$videoCss.querySelector("#bx-filter-usm-matrix");
|
||||
return;
|
||||
}
|
||||
const $fragment = document.createDocumentFragment();
|
||||
@ -9303,18 +9327,21 @@ class StreamPlayer {
|
||||
}
|
||||
#resizePlayer() {
|
||||
const PREF_RATIO = getPref(PrefKey.VIDEO_RATIO);
|
||||
let $target;
|
||||
const $video = this.#$video;
|
||||
const isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport;
|
||||
let $webGL2Canvas;
|
||||
if (this.#playerType == StreamPlayerType.WEBGL2) {
|
||||
$target = this.#webGL2Player?.getCanvas();
|
||||
} else {
|
||||
$target = this.#$video;
|
||||
$webGL2Canvas = this.#webGL2Player?.getCanvas();
|
||||
}
|
||||
let targetWidth;
|
||||
let targetHeight;
|
||||
let targetObjectFit;
|
||||
if (PREF_RATIO.includes(":")) {
|
||||
const tmp = PREF_RATIO.split(":");
|
||||
const videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]);
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
const parentRect = this.#$video.parentElement.getBoundingClientRect();
|
||||
const parentRect = $video.parentElement.getBoundingClientRect();
|
||||
const parentRatio = parentRect.width / parentRect.height;
|
||||
if (parentRatio > videoRatio) {
|
||||
height = parentRect.height;
|
||||
@ -9325,25 +9352,43 @@ class StreamPlayer {
|
||||
}
|
||||
width = Math.ceil(Math.min(parentRect.width, width));
|
||||
height = Math.ceil(Math.min(parentRect.height, height));
|
||||
$target.style.width = `${width}px`;
|
||||
$target.style.height = `${height}px`;
|
||||
$target.style.objectFit = PREF_RATIO === "16:9" ? "contain" : "fill";
|
||||
$video.dataset.width = width.toString();
|
||||
$video.dataset.height = height.toString();
|
||||
targetWidth = `${width}px`;
|
||||
targetHeight = `${height}px`;
|
||||
targetObjectFit = PREF_RATIO === "16:9" ? "contain" : "fill";
|
||||
} else {
|
||||
$target.style.width = "100%";
|
||||
$target.style.height = "100%";
|
||||
$target.style.objectFit = PREF_RATIO;
|
||||
targetWidth = "100%";
|
||||
targetHeight = "100%";
|
||||
targetObjectFit = PREF_RATIO;
|
||||
$video.dataset.width = window.innerWidth.toString();
|
||||
$video.dataset.height = window.innerHeight.toString();
|
||||
}
|
||||
$video.style.width = targetWidth;
|
||||
$video.style.height = targetHeight;
|
||||
$video.style.objectFit = targetObjectFit;
|
||||
if ($webGL2Canvas) {
|
||||
$webGL2Canvas.style.width = targetWidth;
|
||||
$webGL2Canvas.style.height = targetHeight;
|
||||
$webGL2Canvas.style.objectFit = targetObjectFit;
|
||||
}
|
||||
if (isNativeTouchGame && this.#playerType == StreamPlayerType.WEBGL2) {
|
||||
window.BX_EXPOSED.streamSession.updateDimensions();
|
||||
}
|
||||
}
|
||||
setPlayerType(type, refreshPlayer = false) {
|
||||
if (this.#playerType !== type) {
|
||||
if (type === StreamPlayerType.WEBGL2) {
|
||||
this.#webGL2Player = new WebGL2Player(this.#$video);
|
||||
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?.destroy();
|
||||
this.#webGL2Player = null;
|
||||
this.#$video.nativePlay();
|
||||
this.#$video.classList.remove("bx-gone");
|
||||
this.#webGL2Player?.stop();
|
||||
this.#$video.classList.remove("bx-pixel");
|
||||
}
|
||||
}
|
||||
this.#playerType = type;
|
||||
@ -9373,7 +9418,7 @@ class StreamPlayer {
|
||||
if (this.#playerType === StreamPlayerType.WEBGL2) {
|
||||
const options = this.#options;
|
||||
const webGL2Player = this.#webGL2Player;
|
||||
if (options.processing === "usm") {
|
||||
if (options.processing === StreamVideoProcessing.USM) {
|
||||
webGL2Player.setFilter(1);
|
||||
} else {
|
||||
webGL2Player.setFilter(2);
|
||||
@ -9402,6 +9447,7 @@ class StreamPlayer {
|
||||
}
|
||||
destroy() {
|
||||
this.#webGL2Player?.destroy();
|
||||
this.#webGL2Player = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9484,18 +9530,20 @@ function patchRtcPeerConnection() {
|
||||
});
|
||||
return dataChannel;
|
||||
};
|
||||
const nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription;
|
||||
RTCPeerConnection.prototype.setLocalDescription = function(description) {
|
||||
try {
|
||||
const maxVideoBitrate = getPref(PrefKey.BITRATE_VIDEO_MAX);
|
||||
if (maxVideoBitrate > 0) {
|
||||
arguments[0].sdp = patchSdpBitrate(arguments[0].sdp, Math.round(maxVideoBitrate / 1000));
|
||||
const maxVideoBitrate = getPref(PrefKey.BITRATE_VIDEO_MAX);
|
||||
if (maxVideoBitrate > 0) {
|
||||
const nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription;
|
||||
RTCPeerConnection.prototype.setLocalDescription = function(description) {
|
||||
try {
|
||||
if (description) {
|
||||
arguments[0].sdp = patchSdpBitrate(arguments[0].sdp, Math.round(maxVideoBitrate / 1000));
|
||||
}
|
||||
} catch (e) {
|
||||
BxLogger.error("setLocalDescription", e);
|
||||
}
|
||||
} catch (e) {
|
||||
BxLogger.error("setLocalDescription", e);
|
||||
}
|
||||
return nativeSetLocalDescription.apply(this, arguments);
|
||||
};
|
||||
return nativeSetLocalDescription.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
const OrgRTCPeerConnection = window.RTCPeerConnection;
|
||||
window.RTCPeerConnection = function() {
|
||||
const conn = new OrgRTCPeerConnection;
|
||||
@ -9531,7 +9579,11 @@ function patchMeControl() {
|
||||
};
|
||||
const MSA = {
|
||||
MeControl: {
|
||||
setMobileState: () => {
|
||||
API: {
|
||||
setDisplayMode: () => {
|
||||
},
|
||||
setMobileState: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user