Xbox Cloud Gaming 游戏振动支持

让 Xbox Cloud Gaming 支持游戏力反馈(振动)功能

  1. // ==UserScript==
  2. // @name Xbox Cloud Gaming Vibration
  3. // @name:zh-CN Xbox Cloud Gaming 游戏振动支持
  4. // @name:zh-TW Xbox Cloud Gaming 游戲振動支持
  5. // @namespace http://tampermonkey.net/
  6. // @version 1.4
  7. // @description Add game force feedback (vibration or rumble) support for Xbox Cloud Gaming
  8. // @description:zh-CN 让 Xbox Cloud Gaming 支持游戏力反馈(振动)功能
  9. // @description:zh-TW 將 Xbox Cloud Gaming 支援游戲力回饋(振動)功能
  10. // @author TGSAN
  11. // @match https://www.xbox.com/*/play*
  12. // @icon data:image/png;base64,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
  13. // @inject-into page
  14. // @run-at document-start
  15. // @grant unsafeWindow
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // @grant GM_registerMenuCommand
  19. // @grant GM_unregisterMenuCommand
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. const useControllerVibration = true;
  26. const useMobileVibration = true;
  27. const lang = navigator.language.toLowerCase();
  28.  
  29. let windowCtx = self.window;
  30. if (self.unsafeWindow) {
  31. console.log("[Xbox Cloud Gaming Vibration] use unsafeWindow mode");
  32. windowCtx = self.unsafeWindow;
  33. } else {
  34. console.log("[Xbox Cloud Gaming Vibration] use window mode (your userscript extensions not support unsafeWindow)");
  35. }
  36.  
  37. let configList = {
  38. "XCLOUD_HAPTIC_IMPULSE_TRIGGERS_EMU": {
  39. "desc": {
  40. "en": "Impulse Triggers Haptic Emulation",
  41. "zh": "脈衝發射鍵觸覺回饋仿真",
  42. "zh-cn": "脉冲扳机触感反馈模拟",
  43. },
  44. "value": "1"
  45. },
  46. "XCLOUD_HAPTIC_CONTROLLER_ENABLE": {
  47. "desc": {
  48. "en": "Gamepad Haptic ",
  49. "zh": "游戲控制器觸覺回饋",
  50. "zh-cn": "游戏控制器触感反馈",
  51. },
  52. "value": "1"
  53. },
  54. "XCLOUD_HAPTIC_DEVICE_ENABLE": {
  55. "desc": {
  56. "en": "Device Haptic (Tablet or Mobile)",
  57. "zh": "裝置觸覺回饋(平板電腦或手機)",
  58. "zh-cn": "设备触感反馈(平板电脑或手机)",
  59. },
  60. "value": "1"
  61. },
  62. "XCLOUD_HAPTIC_DEVICE_AUTO_DISABLE": {
  63. "desc": {
  64. "en": "Disable Device Haptic When Using Gamepad",
  65. "zh": "使用游戲控制器時停用裝置觸覺回饋",
  66. "zh-cn": "使用游戏控制器时禁用设备触感反馈",
  67. },
  68. "value": "1"
  69. }
  70. }
  71. let menuItemList = [];
  72.  
  73. function checkSelected(key) {
  74. let value = GM_getValue(key);
  75. if (value === undefined) {
  76. GM_setValue(key, configList[key].value);
  77. }
  78. return value == "1";
  79. }
  80.  
  81. function registerSwitchMenuItem(key) {
  82. let configItem = configList[key];
  83. let name = configItem["desc"]["en"];
  84. let blurMatch = configItem["desc"][lang.substr(0, 2)];
  85. let match = configItem["desc"][lang];
  86. if (match) {
  87. name = match;
  88. } else if (blurMatch) {
  89. name = blurMatch;
  90. }
  91. let isSelected = checkSelected(key);
  92. return GM_registerMenuCommand((isSelected ? "✅" : "🔲") + " " + name, function() {
  93. GM_setValue(key, isSelected ? "0" : "1");
  94. loadAndUpdateSwitchMenuItem();
  95. });
  96. }
  97.  
  98. async function loadAndUpdateSwitchMenuItem() {
  99. for(let command of menuItemList) {
  100. await GM_unregisterMenuCommand(command);
  101. }
  102. menuItemList = [];
  103. let configKeys = Object.keys(configList);
  104. for(let configKey of configKeys) {
  105. configList[configKey].value = checkSelected(configKey) ? "1" : "0";
  106. menuItemList.push(await registerSwitchMenuItem(configKey));
  107. }
  108. // Apply
  109. haptic.enableControllerHaptic = checkSelected("XCLOUD_HAPTIC_CONTROLLER_ENABLE");
  110. haptic.enableDeviceHaptic = checkSelected("XCLOUD_HAPTIC_DEVICE_ENABLE");
  111. haptic.alwaysEnableDeviceHaptic = !checkSelected("XCLOUD_HAPTIC_DEVICE_AUTO_DISABLE");
  112. }
  113.  
  114. let haptic = null;
  115. const xinputMaxHaptic = 65535;
  116.  
  117. windowCtx.RTCPeerConnection.prototype.originalCreateDataChannelXCGV = windowCtx.RTCPeerConnection.prototype.createDataChannel;
  118. windowCtx.RTCPeerConnection.prototype.createDataChannel = function (...params) {
  119. let dc = this.originalCreateDataChannelXCGV(...params);
  120. if (dc.label == "input") {
  121. dc.addEventListener("message", function (de) {
  122. if (typeof(de.data) == "object") {
  123. let dataBytes = new Uint8Array(de.data);
  124. if (dataBytes[0] == 128) {
  125. const leftM = dataBytes[3] / 255;
  126. const rightM = dataBytes[4] / 255;
  127. const leftT = dataBytes[5] / 255;
  128. const rightT = dataBytes[6] / 255;
  129. let wLeftMotorSpeed = leftM * xinputMaxHaptic;
  130. let wRightMotorSpeed = rightM * xinputMaxHaptic;
  131. if (checkSelected("XCLOUD_HAPTIC_IMPULSE_TRIGGERS_EMU")) {
  132. wRightMotorSpeed = Math.max(wRightMotorSpeed, leftT * xinputMaxHaptic, rightT * xinputMaxHaptic);
  133. }
  134. if (haptic) {
  135. haptic.SetState(wLeftMotorSpeed, wRightMotorSpeed);
  136. }
  137. }
  138. }
  139. });
  140. dc.addEventListener("close", function () {
  141. if (haptic) haptic.SetState(0, 0);
  142. });
  143. }
  144. return dc;
  145. }
  146.  
  147. // WebHaptic.ts Compile with Webpack, using Polify, disable UglifyJS
  148. var __classPrivateFieldGet = this && this.__classPrivateFieldGet || function (t, e, i, a) {
  149. if (i === "a" && !a) throw new TypeError("Private accessor was defined without a getter");
  150. if (typeof e === "function" ? t !== e || !a : !e.has(t)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
  151. return i === "m" ? a : i === "a" ? a.call(t) : a ? a.value : e.get(t)
  152. };
  153. var __classPrivateFieldSet = this && this.__classPrivateFieldSet || function (t, e, i, a, s) {
  154. if (a === "m") throw new TypeError("Private method is not writable");
  155. if (a === "a" && !s) throw new TypeError("Private accessor was defined without a setter");
  156. if (typeof e === "function" ? t !== e || !s : !e.has(t)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
  157. return a === "a" ? s.call(t, i) : s ? s.value = i : e.set(t, i), i
  158. };
  159. var _WebHapticV2_enableControllerHaptic, _WebHapticV2_enableDeviceHaptic;
  160. class WebHapticV2 {
  161. set enableControllerHaptic(t) {
  162. var e;
  163. if (__classPrivateFieldGet(this, _WebHapticV2_enableControllerHaptic, "f") != t) {
  164. __classPrivateFieldSet(this, _WebHapticV2_enableControllerHaptic, t, "f");
  165. if (t) {
  166. this.controllerHaptic = new WebControllerHaptic
  167. } else {
  168. (e = this.controllerHaptic) === null || e === void 0 ? void 0 : e.Dispose();
  169. this.controllerHaptic = undefined
  170. }
  171. }
  172. }
  173. get enableControllerHaptic() {
  174. return __classPrivateFieldGet(this, _WebHapticV2_enableControllerHaptic, "f")
  175. }
  176. set enableDeviceHaptic(t) {
  177. var e;
  178. if (__classPrivateFieldGet(this, _WebHapticV2_enableDeviceHaptic, "f") != t) {
  179. __classPrivateFieldSet(this, _WebHapticV2_enableDeviceHaptic, t, "f");
  180. if (t) {
  181. this.deviceHaptic = new WebDeviceHaptic
  182. } else {
  183. (e = this.deviceHaptic) === null || e === void 0 ? void 0 : e.Dispose();
  184. this.deviceHaptic = undefined
  185. }
  186. }
  187. }
  188. get enableDeviceHaptic() {
  189. return __classPrivateFieldGet(this, _WebHapticV2_enableDeviceHaptic, "f")
  190. }
  191. constructor(t = 0) {
  192. _WebHapticV2_enableControllerHaptic.set(this, false);
  193. _WebHapticV2_enableDeviceHaptic.set(this, false);
  194. this.alwaysEnableDeviceHaptic = false;
  195. this.updateTimeoutMs = t;
  196. this.enableDeviceHaptic = false;
  197. this.enableControllerHaptic = false
  198. }
  199. SetState(t, e) {
  200. if (this.updateTimeoutId) {
  201. clearTimeout(this.updateTimeoutId)
  202. }
  203. let i = false;
  204. if (this.controllerHaptic !== undefined) {
  205. i = this.controllerHaptic.GetHapticGamepadsCount() > 0;
  206. this.controllerHaptic.SetState(t, e)
  207. }
  208. if (this.deviceHaptic !== undefined) {
  209. if (this.alwaysEnableDeviceHaptic || !i) {
  210. this.deviceHaptic.SetState(t, e)
  211. } else {
  212. this.deviceHaptic.SetState(0, 0)
  213. }
  214. }
  215. if (this.updateTimeoutMs > 0) {
  216. if (t > 0 || e > 0) {
  217. this.updateTimeoutId = setTimeout(() => {
  218. this.updateTimeoutId = undefined;
  219. this.SetState(0, 0)
  220. }, this.updateTimeoutMs)
  221. }
  222. }
  223. }
  224. Dispose() {
  225. this.SetState(0, 0);
  226. this.enableControllerHaptic = false;
  227. this.enableDeviceHaptic = false
  228. }
  229. }
  230. _WebHapticV2_enableControllerHaptic = new WeakMap, _WebHapticV2_enableDeviceHaptic = new WeakMap;
  231. class WebDeviceHaptic {
  232. constructor() {
  233. this.tickSliceCount = 100;
  234. this.tickSliceMs = 10;
  235. this.rangeTirm = 8;
  236. this.supportDeviceHaptic = false;
  237. this.pwmTerminateTick = 0;
  238. this.supportDeviceHaptic = WebDeviceHaptic.IsSupport()
  239. }
  240. Dispose() {
  241. this.SetState(0, 0)
  242. }
  243. SetState(t, e) {
  244. this.SetWebHapticState(t, e)
  245. }
  246. getAdvancedVibrateMotorPercent(t) {
  247. const e = .75;
  248. const i = -.1;
  249. const a = 1 / (e + i * t);
  250. return Math.pow(t, a)
  251. }
  252. SetWebHapticState(a, s) {
  253. if (this.supportDeviceHaptic) {
  254. let t = .5;
  255. let e = 65535;
  256. let i = Math.max(a, s * t);
  257. if (i > 0) {
  258. let t = this.getAdvancedVibrateMotorPercent(i / e);
  259. this.pwmTerminateTick = Math.round(this.tickSliceCount / this.rangeTirm * t);
  260. const n = this.tickSliceCount * this.tickSliceMs * this.rangeTirm;
  261. if (this.hapticPwmIntervalId === undefined) {
  262. let t = 0;
  263. this.hapticPwmIntervalId = setInterval(() => {
  264. if (t == 0) {
  265. window.navigator.vibrate(n)
  266. }
  267. if (t < this.pwmTerminateTick) {
  268. t++
  269. } else {
  270. t = 0
  271. }
  272. }, this.tickSliceMs)
  273. }
  274. } else {
  275. if (this.hapticPwmIntervalId !== undefined) {
  276. clearInterval(this.hapticPwmIntervalId);
  277. this.hapticPwmIntervalId = undefined
  278. }
  279. window.navigator.vibrate(0)
  280. }
  281. }
  282. }
  283. static IsSupport() {
  284. if (!!window.navigator.vibrate) {
  285. return true
  286. } else {
  287. return false
  288. }
  289. }
  290. }
  291. class WebControllerHaptic {
  292. constructor() {
  293. this.magnitudeDurationMs = 1e3;
  294. this.supportControllerHaptic = false;
  295. this.gamepads = [];
  296. this.hapticGamepadsCount = 0;
  297. this.supportControllerHaptic = WebControllerHaptic.IsSupport();
  298. this.onGamepadConnected = t => {
  299. console.log("A gamepad was connected:" + t.gamepad.id);
  300. this.UpdateGamepads()
  301. };
  302. this.onGamepadDisonnected = t => {
  303. console.log("A gamepad was disconnected:" + t.gamepad.id);
  304. this.UpdateGamepads()
  305. };
  306. if (this.supportControllerHaptic) {
  307. window.addEventListener("gamepadconnected", this.onGamepadConnected);
  308. window.addEventListener("gamepaddisconnected", this.onGamepadDisonnected);
  309. this.UpdateGamepads()
  310. }
  311. }
  312. GetHapticGamepadsCount() {
  313. return this.hapticGamepadsCount
  314. }
  315. Dispose() {
  316. this.SetState(0, 0);
  317. if (this.supportControllerHaptic) {
  318. window.removeEventListener("gamepadconnected", this.onGamepadConnected);
  319. window.removeEventListener("gamepaddisconnected", this.onGamepadDisonnected)
  320. }
  321. }
  322. SetState(t, e) {
  323. this.SetControllerState(t, e)
  324. }
  325. SetControllerState(a, s) {
  326. var n, o, r;
  327. if (this.hapticTimeoutId != undefined) {
  328. clearTimeout(this.hapticTimeoutId);
  329. this.hapticTimeoutId = undefined
  330. }
  331. if (this.supportControllerHaptic) {
  332. let t = 65535;
  333. let e = a / t;
  334. let i = s / t;
  335. for (const [c, l] of Object.entries(this.gamepads)) {
  336. if (l != null) {
  337. (n = l === null || l === void 0 ? void 0 : l.vibrationActuator) === null || n === void 0 ? void 0 : n.playEffect("dual-rumble", {
  338. duration: this.magnitudeDurationMs,
  339. strongMagnitude: e,
  340. weakMagnitude: i
  341. });
  342. if (l.hapticActuators != null) {
  343. (o = l.hapticActuators[0]) === null || o === void 0 ? void 0 : o.pulse(e, this.magnitudeDurationMs);
  344. (r = l.hapticActuators[1]) === null || r === void 0 ? void 0 : r.pulse(i, this.magnitudeDurationMs)
  345. }
  346. }
  347. }
  348. if (a > 0 || s > 0) {
  349. this.hapticTimeoutId = setTimeout(() => {
  350. this.hapticTimeoutId = undefined;
  351. this.SetControllerState(a, s)
  352. }, this.magnitudeDurationMs + 15)
  353. }
  354. }
  355. }
  356. UpdateGamepads() {
  357. this.gamepads = navigator.getGamepads();
  358. let e = 0;
  359. this.gamepads.forEach(t => {
  360. if (t != null) {
  361. if (t.vibrationActuator != null) {
  362. e++
  363. } else if (t.hapticActuators != null && t.hapticActuators.length > 0) {
  364. e++
  365. }
  366. }
  367. });
  368. this.hapticGamepadsCount = e
  369. }
  370. static IsSupport() {
  371. var t, e, i, a;
  372. if (!!window.Gamepad && (((e = (t = window.GamepadHapticActuator) === null || t === void 0 ? void 0 : t.prototype) === null || e === void 0 ? void 0 : e.hasOwnProperty("playEffect")) || ((a = (i = window.GamepadHapticActuator) === null || i === void 0 ? void 0 : i.prototype) === null || a === void 0 ? void 0 : a.hasOwnProperty("pulse")))) {
  373. return true
  374. } else {
  375. return false
  376. }
  377. }
  378. }
  379.  
  380. windowCtx.xcloudHaptic = new WebHapticV2();
  381. haptic = windowCtx.xcloudHaptic;
  382.  
  383. loadAndUpdateSwitchMenuItem();
  384. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址