mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-06 05:08:26 +02:00
Port the rest of the code
This commit is contained in:
150
src/modules/vibration-manager.ts
Normal file
150
src/modules/vibration-manager.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { PrefKey, getPref } from "./preferences";
|
||||
|
||||
const VIBRATION_DATA_MAP = {
|
||||
'gamepadIndex': 8,
|
||||
'leftMotorPercent': 8,
|
||||
'rightMotorPercent': 8,
|
||||
'leftTriggerMotorPercent': 8,
|
||||
'rightTriggerMotorPercent': 8,
|
||||
'durationMs': 16,
|
||||
// 'delayMs': 16,
|
||||
// 'repeat': 8,
|
||||
};
|
||||
|
||||
type VibrationData = {
|
||||
[key in keyof typeof VIBRATION_DATA_MAP]?: number;
|
||||
}
|
||||
|
||||
export class VibrationManager {
|
||||
static #playDeviceVibration(data: Required<VibrationData>) {
|
||||
// console.log(+new Date, data);
|
||||
|
||||
if (AppInterface) {
|
||||
AppInterface.vibrate(JSON.stringify(data), window.BX_VIBRATION_INTENSITY);
|
||||
return;
|
||||
}
|
||||
|
||||
const intensity = Math.min(100, data.leftMotorPercent + data.rightMotorPercent / 2) * window.BX_VIBRATION_INTENSITY;
|
||||
if (intensity === 0 || intensity === 100) {
|
||||
// Stop vibration
|
||||
window.navigator.vibrate(intensity ? data.durationMs : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const pulseDuration = 200;
|
||||
const onDuration = Math.floor(pulseDuration * intensity / 100);
|
||||
const offDuration = pulseDuration - onDuration;
|
||||
|
||||
const repeats = Math.ceil(data.durationMs / pulseDuration);
|
||||
|
||||
const pulses = Array(repeats).fill([onDuration, offDuration]).flat();
|
||||
// console.log(pulses);
|
||||
|
||||
window.navigator.vibrate(pulses);
|
||||
}
|
||||
|
||||
static supportControllerVibration() {
|
||||
return Gamepad.prototype.hasOwnProperty('vibrationActuator');
|
||||
}
|
||||
|
||||
static supportDeviceVibration() {
|
||||
return !!window.navigator.vibrate;
|
||||
}
|
||||
|
||||
static updateGlobalVars() {
|
||||
window.BX_ENABLE_CONTROLLER_VIBRATION = VibrationManager.supportControllerVibration() ? getPref(PrefKey.CONTROLLER_ENABLE_VIBRATION) : false;
|
||||
window.BX_VIBRATION_INTENSITY = getPref(PrefKey.CONTROLLER_VIBRATION_INTENSITY) / 100;
|
||||
|
||||
if (!VibrationManager.supportDeviceVibration()) {
|
||||
window.BX_ENABLE_DEVICE_VIBRATION = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop vibration
|
||||
window.navigator.vibrate(0);
|
||||
|
||||
const value = getPref(PrefKey.CONTROLLER_DEVICE_VIBRATION);
|
||||
let enabled;
|
||||
|
||||
if (value === 'on') {
|
||||
enabled = true;
|
||||
} else if (value === 'auto') {
|
||||
enabled = true;
|
||||
const gamepads = window.navigator.getGamepads();
|
||||
for (const gamepad of gamepads) {
|
||||
if (gamepad) {
|
||||
enabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
window.BX_ENABLE_DEVICE_VIBRATION = enabled;
|
||||
}
|
||||
|
||||
static #onMessage(e: MessageEvent) {
|
||||
if (!window.BX_ENABLE_DEVICE_VIBRATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof e !== 'object' || !(e.data instanceof ArrayBuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataView = new DataView(e.data);
|
||||
let offset = 0;
|
||||
|
||||
let messageType;
|
||||
if (dataView.byteLength === 13) { // version >= 8
|
||||
messageType = dataView.getUint16(offset, true);
|
||||
offset += Uint16Array.BYTES_PER_ELEMENT;
|
||||
} else {
|
||||
messageType = dataView.getUint8(offset);
|
||||
offset += Uint8Array.BYTES_PER_ELEMENT;
|
||||
}
|
||||
|
||||
if (!(messageType & 128)) { // Vibration
|
||||
return;
|
||||
}
|
||||
|
||||
const vibrationType = dataView.getUint8(offset);
|
||||
offset += Uint8Array.BYTES_PER_ELEMENT;
|
||||
|
||||
if (vibrationType !== 0) { // FourMotorRumble
|
||||
return;
|
||||
}
|
||||
|
||||
const data: VibrationData = {};
|
||||
let key: keyof typeof VIBRATION_DATA_MAP;
|
||||
for (key in VIBRATION_DATA_MAP) {
|
||||
if (VIBRATION_DATA_MAP[key] === 16) {
|
||||
data[key] = dataView.getUint16(offset, true);
|
||||
offset += Uint16Array.BYTES_PER_ELEMENT;
|
||||
} else {
|
||||
data[key] = dataView.getUint8(offset);
|
||||
offset += Uint8Array.BYTES_PER_ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
VibrationManager.#playDeviceVibration(data as Required<VibrationData>);
|
||||
}
|
||||
|
||||
static initialSetup() {
|
||||
window.addEventListener('gamepadconnected', VibrationManager.updateGlobalVars);
|
||||
window.addEventListener('gamepaddisconnected', VibrationManager.updateGlobalVars);
|
||||
|
||||
VibrationManager.updateGlobalVars();
|
||||
|
||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
||||
const dataChannel = (e as any).dataChannel;
|
||||
if (!dataChannel || dataChannel.label !== 'input') {
|
||||
return;
|
||||
}
|
||||
|
||||
dataChannel.addEventListener('message', VibrationManager.#onMessage);
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user