mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
Fixed multi-controllers, optimize and improve code in relay and nestri-server
This commit is contained in:
@@ -41,7 +41,7 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
|||||||
pacman -Sy --noconfirm lib32-gcc-libs
|
pacman -Sy --noconfirm lib32-gcc-libs
|
||||||
|
|
||||||
# Clone repository
|
# Clone repository
|
||||||
RUN git clone --depth 1 --rev "9e8bfd0217eeab011c5afc368d3ea67a4c239e81" https://github.com/DatCaptainHorse/vimputti.git
|
RUN git clone --depth 1 --rev "f2f21561ddcb814d74455311969d3e8934b052c6" https://github.com/DatCaptainHorse/vimputti.git
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
FROM vimputti-manager-deps AS vimputti-manager-planner
|
FROM vimputti-manager-deps AS vimputti-manager-planner
|
||||||
@@ -129,23 +129,8 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
|||||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||||
cargo install cargo-c
|
cargo install cargo-c
|
||||||
|
|
||||||
# Grab cudart from NVIDIA..
|
|
||||||
RUN wget https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/linux-x86_64/cuda_cudart-linux-x86_64-13.0.96-archive.tar.xz -O cuda_cudart.tar.xz && \
|
|
||||||
mkdir cuda_cudart && tar -xf cuda_cudart.tar.xz -C cuda_cudart --strip-components=1 && \
|
|
||||||
cp cuda_cudart/lib/libcudart.so cuda_cudart/lib/libcudart.so.* /usr/lib/ && \
|
|
||||||
rm -r cuda_cudart && \
|
|
||||||
rm cuda_cudart.tar.xz
|
|
||||||
|
|
||||||
# Grab cuda lib from NVIDIA (it's in driver package of all things..)
|
|
||||||
RUN wget https://developer.download.nvidia.com/compute/cuda/redist/nvidia_driver/linux-x86_64/nvidia_driver-linux-x86_64-580.95.05-archive.tar.xz -O nvidia_driver.tar.xz && \
|
|
||||||
mkdir nvidia_driver && tar -xf nvidia_driver.tar.xz -C nvidia_driver --strip-components=1 && \
|
|
||||||
cp nvidia_driver/lib/libcuda.so.* /usr/lib/libcuda.so && \
|
|
||||||
ln -s /usr/lib/libcuda.so /usr/lib/libcuda.so.1 && \
|
|
||||||
rm -r nvidia_driver && \
|
|
||||||
rm nvidia_driver.tar.xz
|
|
||||||
|
|
||||||
# Clone repository
|
# Clone repository
|
||||||
RUN git clone --depth 1 --rev "afa853fa03e8403c83bbb3bc0cf39147ad46c266" https://github.com/games-on-whales/gst-wayland-display.git
|
RUN git clone --depth 1 --rev "a4abcfe2cffe2d33b564d1308b58504a5e3012b1" https://github.com/games-on-whales/gst-wayland-display.git
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
FROM gst-wayland-deps AS gst-wayland-planner
|
FROM gst-wayland-deps AS gst-wayland-planner
|
||||||
@@ -214,5 +199,4 @@ COPY --from=gst-wayland-cached-builder /artifacts/include/ /artifacts/include/
|
|||||||
COPY --from=vimputti-manager-cached-builder /artifacts/vimputti-manager /artifacts/bin/
|
COPY --from=vimputti-manager-cached-builder /artifacts/vimputti-manager /artifacts/bin/
|
||||||
COPY --from=vimputti-manager-cached-builder /artifacts/libvimputti_shim_64.so /artifacts/lib64/libvimputti_shim.so
|
COPY --from=vimputti-manager-cached-builder /artifacts/libvimputti_shim_64.so /artifacts/lib64/libvimputti_shim.so
|
||||||
COPY --from=vimputti-manager-cached-builder /artifacts/libvimputti_shim_32.so /artifacts/lib32/libvimputti_shim.so
|
COPY --from=vimputti-manager-cached-builder /artifacts/libvimputti_shim_32.so /artifacts/lib32/libvimputti_shim.so
|
||||||
COPY --from=gst-wayland-deps /usr/lib/libcuda.so /usr/lib/libcuda.so.* /artifacts/lib/
|
|
||||||
COPY --from=bubblewrap-builder /artifacts/bin/bwrap /artifacts/bin/
|
COPY --from=bubblewrap-builder /artifacts/bin/bwrap /artifacts/bin/
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ interface GamepadState {
|
|||||||
|
|
||||||
export class Controller {
|
export class Controller {
|
||||||
protected wrtc: WebRTCStream;
|
protected wrtc: WebRTCStream;
|
||||||
protected slotMap: Map<number, number> = new Map(); // local slot to server slot
|
|
||||||
protected connected: boolean = false;
|
protected connected: boolean = false;
|
||||||
protected gamepad: Gamepad | null = null;
|
protected gamepad: Gamepad | null = null;
|
||||||
protected lastState: GamepadState = {
|
protected lastState: GamepadState = {
|
||||||
@@ -50,6 +49,14 @@ export class Controller {
|
|||||||
protected stickDeadzone: number = 2048; // 2048 / 32768 = ~0.06 (6% of stick range)
|
protected stickDeadzone: number = 2048; // 2048 / 32768 = ~0.06 (6% of stick range)
|
||||||
|
|
||||||
private updateInterval = 10.0; // 100 updates per second
|
private updateInterval = 10.0; // 100 updates per second
|
||||||
|
private isIdle: boolean = true;
|
||||||
|
private lastInputTime: number = Date.now();
|
||||||
|
private idleUpdateInterval: number = 150.0; // ~6-7 updates per second for keep-alive packets
|
||||||
|
private inputDetected: boolean = false;
|
||||||
|
private lastFullStateSend: number = Date.now();
|
||||||
|
private fullStateSendInterval: number = 500.0; // send full state every 0.5 seconds (helps packet loss)
|
||||||
|
private forceFullStateSend: boolean = false;
|
||||||
|
|
||||||
private _dcHandler: ((data: ArrayBuffer) => void) | null = null;
|
private _dcHandler: ((data: ArrayBuffer) => void) | null = null;
|
||||||
|
|
||||||
constructor({ webrtc, e }: Props) {
|
constructor({ webrtc, e }: Props) {
|
||||||
@@ -79,9 +86,8 @@ export class Controller {
|
|||||||
const attachMsg = messageWrapper.payload.value;
|
const attachMsg = messageWrapper.payload.value;
|
||||||
// Gamepad connected succesfully
|
// Gamepad connected succesfully
|
||||||
this.gamepad = e.gamepad;
|
this.gamepad = e.gamepad;
|
||||||
this.slotMap.set(e.gamepad.index, attachMsg.slot);
|
|
||||||
console.log(
|
console.log(
|
||||||
`Gamepad connected: ${e.gamepad.id} assigned to slot ${attachMsg.slot} on server, local slot ${e.gamepad.index}`,
|
`Gamepad connected: ${e.gamepad.id}, local slot ${e.gamepad.index}, msg: ${attachMsg.sessionSlot}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -93,6 +99,7 @@ export class Controller {
|
|||||||
const attachMsg = createMessage(
|
const attachMsg = createMessage(
|
||||||
create(ProtoControllerAttachSchema, {
|
create(ProtoControllerAttachSchema, {
|
||||||
id: this.vendor_id_to_controller(vendorId, productId),
|
id: this.vendor_id_to_controller(vendorId, productId),
|
||||||
|
sessionSlot: e.gamepad.index,
|
||||||
sessionId: this.wrtc.getSessionID(),
|
sessionId: this.wrtc.getSessionID(),
|
||||||
}),
|
}),
|
||||||
"controllerInput",
|
"controllerInput",
|
||||||
@@ -102,6 +109,10 @@ export class Controller {
|
|||||||
this.run();
|
this.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSlot(): number {
|
||||||
|
return this.gamepad.index;
|
||||||
|
}
|
||||||
|
|
||||||
// Maps vendor id and product id to supported controller type
|
// Maps vendor id and product id to supported controller type
|
||||||
// Currently supported: Sony (ps4, ps5), Microsoft (xbox360, xboxone), Nintendo (switchpro)
|
// Currently supported: Sony (ps4, ps5), Microsoft (xbox360, xboxone), Nintendo (switchpro)
|
||||||
// Default fallback to xbox360
|
// Default fallback to xbox360
|
||||||
@@ -154,6 +165,13 @@ export class Controller {
|
|||||||
private pollGamepad() {
|
private pollGamepad() {
|
||||||
// Get updated gamepad state
|
// Get updated gamepad state
|
||||||
const gamepads = navigator.getGamepads();
|
const gamepads = navigator.getGamepads();
|
||||||
|
|
||||||
|
// Periodically force send full state to clear stuck inputs
|
||||||
|
if (Date.now() - this.lastFullStateSend > this.fullStateSendInterval) {
|
||||||
|
this.forceFullStateSend = true;
|
||||||
|
this.lastFullStateSend = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.gamepad) {
|
if (this.gamepad) {
|
||||||
if (gamepads[this.gamepad.index]) {
|
if (gamepads[this.gamepad.index]) {
|
||||||
this.gamepad = gamepads[this.gamepad!.index];
|
this.gamepad = gamepads[this.gamepad!.index];
|
||||||
@@ -164,7 +182,7 @@ export class Controller {
|
|||||||
// ignore trigger buttons (6-7) as we handle those as axis
|
// ignore trigger buttons (6-7) as we handle those as axis
|
||||||
if (index === 6 || index === 7) return;
|
if (index === 6 || index === 7) return;
|
||||||
// If state differs, send
|
// If state differs, send
|
||||||
if (button.pressed !== this.lastState.buttonState.get(index)) {
|
if (button.pressed !== this.lastState.buttonState.get(index) || this.forceFullStateSend) {
|
||||||
const linuxCode = this.controllerButtonToVirtualKeyCode(index);
|
const linuxCode = this.controllerButtonToVirtualKeyCode(index);
|
||||||
if (linuxCode === undefined) {
|
if (linuxCode === undefined) {
|
||||||
// Skip unmapped button index
|
// Skip unmapped button index
|
||||||
@@ -174,13 +192,15 @@ export class Controller {
|
|||||||
|
|
||||||
const buttonMessage = createMessage(
|
const buttonMessage = createMessage(
|
||||||
create(ProtoControllerButtonSchema, {
|
create(ProtoControllerButtonSchema, {
|
||||||
slot: this.getServerSlot(),
|
sessionSlot: this.gamepad.index,
|
||||||
|
sessionId: this.wrtc.getSessionID(),
|
||||||
button: linuxCode,
|
button: linuxCode,
|
||||||
pressed: button.pressed,
|
pressed: button.pressed,
|
||||||
}),
|
}),
|
||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, buttonMessage));
|
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, buttonMessage));
|
||||||
|
this.inputDetected = true;
|
||||||
// Store button state
|
// Store button state
|
||||||
this.lastState.buttonState.set(index, button.pressed);
|
this.lastState.buttonState.set(index, button.pressed);
|
||||||
}
|
}
|
||||||
@@ -198,16 +218,18 @@ export class Controller {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
// If state differs, send
|
// If state differs, send
|
||||||
if (leftTrigger !== this.lastState.leftTrigger) {
|
if (leftTrigger !== this.lastState.leftTrigger || this.forceFullStateSend) {
|
||||||
const triggerMessage = createMessage(
|
const triggerMessage = createMessage(
|
||||||
create(ProtoControllerTriggerSchema, {
|
create(ProtoControllerTriggerSchema, {
|
||||||
slot: this.getServerSlot(),
|
sessionSlot: this.gamepad.index,
|
||||||
|
sessionId: this.wrtc.getSessionID(),
|
||||||
trigger: 0, // 0 = left, 1 = right
|
trigger: 0, // 0 = left, 1 = right
|
||||||
value: leftTrigger,
|
value: leftTrigger,
|
||||||
}),
|
}),
|
||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, triggerMessage));
|
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, triggerMessage));
|
||||||
|
this.inputDetected = true;
|
||||||
this.lastState.leftTrigger = leftTrigger;
|
this.lastState.leftTrigger = leftTrigger;
|
||||||
}
|
}
|
||||||
const rightTrigger = Math.round(
|
const rightTrigger = Math.round(
|
||||||
@@ -220,16 +242,18 @@ export class Controller {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
// If state differs, send
|
// If state differs, send
|
||||||
if (rightTrigger !== this.lastState.rightTrigger) {
|
if (rightTrigger !== this.lastState.rightTrigger || this.forceFullStateSend) {
|
||||||
const triggerMessage = createMessage(
|
const triggerMessage = createMessage(
|
||||||
create(ProtoControllerTriggerSchema, {
|
create(ProtoControllerTriggerSchema, {
|
||||||
slot: this.getServerSlot(),
|
sessionSlot: this.gamepad.index,
|
||||||
|
sessionId: this.wrtc.getSessionID(),
|
||||||
trigger: 1, // 0 = left, 1 = right
|
trigger: 1, // 0 = left, 1 = right
|
||||||
value: rightTrigger,
|
value: rightTrigger,
|
||||||
}),
|
}),
|
||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, triggerMessage));
|
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, triggerMessage));
|
||||||
|
this.inputDetected = true;
|
||||||
this.lastState.rightTrigger = rightTrigger;
|
this.lastState.rightTrigger = rightTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,32 +262,36 @@ export class Controller {
|
|||||||
const dpadLeft = this.gamepad.buttons[14]?.pressed ? 1 : 0;
|
const dpadLeft = this.gamepad.buttons[14]?.pressed ? 1 : 0;
|
||||||
const dpadRight = this.gamepad.buttons[15]?.pressed ? 1 : 0;
|
const dpadRight = this.gamepad.buttons[15]?.pressed ? 1 : 0;
|
||||||
const dpadX = dpadLeft ? -1 : dpadRight ? 1 : 0;
|
const dpadX = dpadLeft ? -1 : dpadRight ? 1 : 0;
|
||||||
if (dpadX !== this.lastState.dpadX) {
|
if (dpadX !== this.lastState.dpadX || this.forceFullStateSend) {
|
||||||
const dpadMessage = createMessage(
|
const dpadMessage = createMessage(
|
||||||
create(ProtoControllerAxisSchema, {
|
create(ProtoControllerAxisSchema, {
|
||||||
slot: this.getServerSlot(),
|
sessionSlot: this.gamepad.index,
|
||||||
|
sessionId: this.wrtc.getSessionID(),
|
||||||
axis: 0, // 0 = dpadX, 1 = dpadY
|
axis: 0, // 0 = dpadX, 1 = dpadY
|
||||||
value: dpadX,
|
value: dpadX,
|
||||||
}),
|
}),
|
||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
this.lastState.dpadX = dpadX;
|
|
||||||
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, dpadMessage));
|
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, dpadMessage));
|
||||||
|
this.inputDetected = true;
|
||||||
|
this.lastState.dpadX = dpadX;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dpadUp = this.gamepad.buttons[12]?.pressed ? 1 : 0;
|
const dpadUp = this.gamepad.buttons[12]?.pressed ? 1 : 0;
|
||||||
const dpadDown = this.gamepad.buttons[13]?.pressed ? 1 : 0;
|
const dpadDown = this.gamepad.buttons[13]?.pressed ? 1 : 0;
|
||||||
const dpadY = dpadUp ? -1 : dpadDown ? 1 : 0;
|
const dpadY = dpadUp ? -1 : dpadDown ? 1 : 0;
|
||||||
if (dpadY !== this.lastState.dpadY) {
|
if (dpadY !== this.lastState.dpadY || this.forceFullStateSend) {
|
||||||
const dpadMessage = createMessage(
|
const dpadMessage = createMessage(
|
||||||
create(ProtoControllerAxisSchema, {
|
create(ProtoControllerAxisSchema, {
|
||||||
slot: this.getServerSlot(),
|
sessionSlot: this.gamepad.index,
|
||||||
|
sessionId: this.wrtc.getSessionID(),
|
||||||
axis: 1, // 0 = dpadX, 1 = dpadY
|
axis: 1, // 0 = dpadX, 1 = dpadY
|
||||||
value: dpadY,
|
value: dpadY,
|
||||||
}),
|
}),
|
||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, dpadMessage));
|
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, dpadMessage));
|
||||||
|
this.inputDetected = true;
|
||||||
this.lastState.dpadY = dpadY;
|
this.lastState.dpadY = dpadY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,10 +320,12 @@ export class Controller {
|
|||||||
// if moves inside deadzone, zero it if not inside deadzone last time
|
// if moves inside deadzone, zero it if not inside deadzone last time
|
||||||
if (
|
if (
|
||||||
sendLeftX !== this.lastState.leftX ||
|
sendLeftX !== this.lastState.leftX ||
|
||||||
sendLeftY !== this.lastState.leftY
|
sendLeftY !== this.lastState.leftY || this.forceFullStateSend
|
||||||
) {
|
) {
|
||||||
const stickMessage = createMessage(
|
const stickMessage = createMessage(
|
||||||
create(ProtoControllerStickSchema, {
|
create(ProtoControllerStickSchema, {
|
||||||
|
sessionSlot: this.gamepad.index,
|
||||||
|
sessionId: this.wrtc.getSessionID(),
|
||||||
stick: 0, // 0 = left, 1 = right
|
stick: 0, // 0 = left, 1 = right
|
||||||
x: sendLeftX,
|
x: sendLeftX,
|
||||||
y: sendLeftY,
|
y: sendLeftY,
|
||||||
@@ -303,6 +333,7 @@ export class Controller {
|
|||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, stickMessage));
|
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, stickMessage));
|
||||||
|
this.inputDetected = true;
|
||||||
this.lastState.leftX = sendLeftX;
|
this.lastState.leftX = sendLeftX;
|
||||||
this.lastState.leftY = sendLeftY;
|
this.lastState.leftY = sendLeftY;
|
||||||
}
|
}
|
||||||
@@ -328,10 +359,12 @@ export class Controller {
|
|||||||
Math.abs(rightY) > this.stickDeadzone ? Math.round(rightY) : 0;
|
Math.abs(rightY) > this.stickDeadzone ? Math.round(rightY) : 0;
|
||||||
if (
|
if (
|
||||||
sendRightX !== this.lastState.rightX ||
|
sendRightX !== this.lastState.rightX ||
|
||||||
sendRightY !== this.lastState.rightY
|
sendRightY !== this.lastState.rightY || this.forceFullStateSend
|
||||||
) {
|
) {
|
||||||
const stickMessage = createMessage(
|
const stickMessage = createMessage(
|
||||||
create(ProtoControllerStickSchema, {
|
create(ProtoControllerStickSchema, {
|
||||||
|
sessionSlot: this.gamepad.index,
|
||||||
|
sessionId: this.wrtc.getSessionID(),
|
||||||
stick: 1, // 0 = left, 1 = right
|
stick: 1, // 0 = left, 1 = right
|
||||||
x: sendRightX,
|
x: sendRightX,
|
||||||
y: sendRightY,
|
y: sendRightY,
|
||||||
@@ -339,11 +372,14 @@ export class Controller {
|
|||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, stickMessage));
|
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, stickMessage));
|
||||||
|
this.inputDetected = true;
|
||||||
this.lastState.rightX = sendRightX;
|
this.lastState.rightX = sendRightX;
|
||||||
this.lastState.rightY = sendRightY;
|
this.lastState.rightY = sendRightY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.forceFullStateSend = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private loopInterval: any = null;
|
private loopInterval: any = null;
|
||||||
@@ -352,11 +388,35 @@ export class Controller {
|
|||||||
if (this.connected) this.stop();
|
if (this.connected) this.stop();
|
||||||
|
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
// Poll gamepads in setInterval loop
|
this.isIdle = true;
|
||||||
|
this.lastInputTime = Date.now();
|
||||||
|
|
||||||
|
this.loopInterval = setInterval(() => {
|
||||||
|
if (this.connected) {
|
||||||
|
this.inputDetected = false; // Reset before poll
|
||||||
|
this.pollGamepad();
|
||||||
|
|
||||||
|
// Switch polling rate based on input
|
||||||
|
if (this.inputDetected) {
|
||||||
|
this.lastInputTime = Date.now();
|
||||||
|
if (this.isIdle) {
|
||||||
|
this.isIdle = false;
|
||||||
|
clearInterval(this.loopInterval);
|
||||||
this.loopInterval = setInterval(() => {
|
this.loopInterval = setInterval(() => {
|
||||||
if (this.connected) this.pollGamepad();
|
if (this.connected) this.pollGamepad();
|
||||||
}, this.updateInterval);
|
}, this.updateInterval);
|
||||||
}
|
}
|
||||||
|
} else if (!this.isIdle && Date.now() - this.lastInputTime > 200) {
|
||||||
|
// Switch to idle polling after 200ms of no input
|
||||||
|
this.isIdle = true;
|
||||||
|
clearInterval(this.loopInterval);
|
||||||
|
this.loopInterval = setInterval(() => {
|
||||||
|
if (this.connected) this.pollGamepad();
|
||||||
|
}, this.idleUpdateInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this.isIdle ? this.idleUpdateInterval : this.updateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
public stop() {
|
public stop() {
|
||||||
if (this.loopInterval) {
|
if (this.loopInterval) {
|
||||||
@@ -366,21 +426,6 @@ export class Controller {
|
|||||||
this.connected = false;
|
this.connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLocalSlot(): number {
|
|
||||||
if (this.gamepad) {
|
|
||||||
return this.gamepad.index;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getServerSlot(): number {
|
|
||||||
if (this.gamepad) {
|
|
||||||
const slot = this.slotMap.get(this.gamepad.index);
|
|
||||||
if (slot !== undefined) return slot;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
this.stop();
|
this.stop();
|
||||||
// Remove callback
|
// Remove callback
|
||||||
@@ -391,7 +436,7 @@ export class Controller {
|
|||||||
// Gamepad disconnected
|
// Gamepad disconnected
|
||||||
const detachMsg = createMessage(
|
const detachMsg = createMessage(
|
||||||
create(ProtoControllerDetachSchema, {
|
create(ProtoControllerDetachSchema, {
|
||||||
slot: this.getServerSlot(),
|
sessionSlot: this.gamepad.index,
|
||||||
}),
|
}),
|
||||||
"controllerInput",
|
"controllerInput",
|
||||||
);
|
);
|
||||||
@@ -407,7 +452,9 @@ export class Controller {
|
|||||||
if (!this.connected) return;
|
if (!this.connected) return;
|
||||||
|
|
||||||
// Check if aimed at this controller slot
|
// Check if aimed at this controller slot
|
||||||
if (rumbleMsg.slot !== this.getServerSlot()) return;
|
if (rumbleMsg.sessionId !== this.wrtc.getSessionID() &&
|
||||||
|
rumbleMsg.sessionSlot !== this.gamepad.index)
|
||||||
|
return;
|
||||||
|
|
||||||
// Trigger actual rumble
|
// Trigger actual rumble
|
||||||
// Need to remap from 0-65535 to 0.0-1.0 ranges
|
// Need to remap from 0-65535 to 0.0-1.0 ranges
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
|
// @generated by protoc-gen-es v2.10.0 with parameter "target=ts"
|
||||||
// @generated from file latency_tracker.proto (package proto, syntax proto3)
|
// @generated from file latency_tracker.proto (package proto, syntax proto3)
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
|
// @generated by protoc-gen-es v2.10.0 with parameter "target=ts"
|
||||||
// @generated from file messages.proto (package proto, syntax proto3)
|
// @generated from file messages.proto (package proto, syntax proto3)
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
|
// @generated by protoc-gen-es v2.10.0 with parameter "target=ts"
|
||||||
// @generated from file types.proto (package proto, syntax proto3)
|
// @generated from file types.proto (package proto, syntax proto3)
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
|
|||||||
* Describes the file types.proto.
|
* Describes the file types.proto.
|
||||||
*/
|
*/
|
||||||
export const file_types: GenFile = /*@__PURE__*/
|
export const file_types: GenFile = /*@__PURE__*/
|
||||||
fileDesc("Cgt0eXBlcy5wcm90bxIFcHJvdG8iJgoOUHJvdG9Nb3VzZU1vdmUSCQoBeBgBIAEoBRIJCgF5GAIgASgFIikKEVByb3RvTW91c2VNb3ZlQWJzEgkKAXgYASABKAUSCQoBeRgCIAEoBSInCg9Qcm90b01vdXNlV2hlZWwSCQoBeBgBIAEoBRIJCgF5GAIgASgFIiAKEVByb3RvTW91c2VLZXlEb3duEgsKA2tleRgBIAEoBSIeCg9Qcm90b01vdXNlS2V5VXASCwoDa2V5GAEgASgFIhsKDFByb3RvS2V5RG93bhILCgNrZXkYASABKAUiGQoKUHJvdG9LZXlVcBILCgNrZXkYASABKAUiRQoVUHJvdG9Db250cm9sbGVyQXR0YWNoEgoKAmlkGAEgASgJEgwKBHNsb3QYAiABKAUSEgoKc2Vzc2lvbl9pZBgDIAEoCSIlChVQcm90b0NvbnRyb2xsZXJEZXRhY2gSDAoEc2xvdBgBIAEoBSJGChVQcm90b0NvbnRyb2xsZXJCdXR0b24SDAoEc2xvdBgBIAEoBRIOCgZidXR0b24YAiABKAUSDwoHcHJlc3NlZBgDIAEoCCJGChZQcm90b0NvbnRyb2xsZXJUcmlnZ2VyEgwKBHNsb3QYASABKAUSDwoHdHJpZ2dlchgCIAEoBRINCgV2YWx1ZRgDIAEoBSJJChRQcm90b0NvbnRyb2xsZXJTdGljaxIMCgRzbG90GAEgASgFEg0KBXN0aWNrGAIgASgFEgkKAXgYAyABKAUSCQoBeRgEIAEoBSJAChNQcm90b0NvbnRyb2xsZXJBeGlzEgwKBHNsb3QYASABKAUSDAoEYXhpcxgCIAEoBRINCgV2YWx1ZRgDIAEoBSJmChVQcm90b0NvbnRyb2xsZXJSdW1ibGUSDAoEc2xvdBgBIAEoBRIVCg1sb3dfZnJlcXVlbmN5GAIgASgFEhYKDmhpZ2hfZnJlcXVlbmN5GAMgASgFEhAKCGR1cmF0aW9uGAQgASgFIqoBChNSVENJY2VDYW5kaWRhdGVJbml0EhEKCWNhbmRpZGF0ZRgBIAEoCRIaCg1zZHBNTGluZUluZGV4GAIgASgNSACIAQESEwoGc2RwTWlkGAMgASgJSAGIAQESHQoQdXNlcm5hbWVGcmFnbWVudBgEIAEoCUgCiAEBQhAKDl9zZHBNTGluZUluZGV4QgkKB19zZHBNaWRCEwoRX3VzZXJuYW1lRnJhZ21lbnQiNgoZUlRDU2Vzc2lvbkRlc2NyaXB0aW9uSW5pdBILCgNzZHAYASABKAkSDAoEdHlwZRgCIAEoCSI5CghQcm90b0lDRRItCgljYW5kaWRhdGUYASABKAsyGi5wcm90by5SVENJY2VDYW5kaWRhdGVJbml0IjkKCFByb3RvU0RQEi0KA3NkcBgBIAEoCzIgLnByb3RvLlJUQ1Nlc3Npb25EZXNjcmlwdGlvbkluaXQiGAoIUHJvdG9SYXcSDAoEZGF0YRgBIAEoCSJFChxQcm90b0NsaWVudFJlcXVlc3RSb29tU3RyZWFtEhEKCXJvb21fbmFtZRgBIAEoCRISCgpzZXNzaW9uX2lkGAIgASgJIkcKF1Byb3RvQ2xpZW50RGlzY29ubmVjdGVkEhIKCnNlc3Npb25faWQYASABKAkSGAoQY29udHJvbGxlcl9zbG90cxgCIAMoBSIqChVQcm90b1NlcnZlclB1c2hTdHJlYW0SEQoJcm9vbV9uYW1lGAEgASgJQhZaFHJlbGF5L2ludGVybmFsL3Byb3RvYgZwcm90bzM");
|
fileDesc("Cgt0eXBlcy5wcm90bxIFcHJvdG8iJgoOUHJvdG9Nb3VzZU1vdmUSCQoBeBgBIAEoBRIJCgF5GAIgASgFIikKEVByb3RvTW91c2VNb3ZlQWJzEgkKAXgYASABKAUSCQoBeRgCIAEoBSInCg9Qcm90b01vdXNlV2hlZWwSCQoBeBgBIAEoBRIJCgF5GAIgASgFIiAKEVByb3RvTW91c2VLZXlEb3duEgsKA2tleRgBIAEoBSIeCg9Qcm90b01vdXNlS2V5VXASCwoDa2V5GAEgASgFIhsKDFByb3RvS2V5RG93bhILCgNrZXkYASABKAUiGQoKUHJvdG9LZXlVcBILCgNrZXkYASABKAUiTQoVUHJvdG9Db250cm9sbGVyQXR0YWNoEgoKAmlkGAEgASgJEhQKDHNlc3Npb25fc2xvdBgCIAEoBRISCgpzZXNzaW9uX2lkGAMgASgJIkEKFVByb3RvQ29udHJvbGxlckRldGFjaBIUCgxzZXNzaW9uX3Nsb3QYASABKAUSEgoKc2Vzc2lvbl9pZBgCIAEoCSJiChVQcm90b0NvbnRyb2xsZXJCdXR0b24SFAoMc2Vzc2lvbl9zbG90GAEgASgFEhIKCnNlc3Npb25faWQYAiABKAkSDgoGYnV0dG9uGAMgASgFEg8KB3ByZXNzZWQYBCABKAgiYgoWUHJvdG9Db250cm9sbGVyVHJpZ2dlchIUCgxzZXNzaW9uX3Nsb3QYASABKAUSEgoKc2Vzc2lvbl9pZBgCIAEoCRIPCgd0cmlnZ2VyGAMgASgFEg0KBXZhbHVlGAQgASgFImUKFFByb3RvQ29udHJvbGxlclN0aWNrEhQKDHNlc3Npb25fc2xvdBgBIAEoBRISCgpzZXNzaW9uX2lkGAIgASgJEg0KBXN0aWNrGAMgASgFEgkKAXgYBCABKAUSCQoBeRgFIAEoBSJcChNQcm90b0NvbnRyb2xsZXJBeGlzEhQKDHNlc3Npb25fc2xvdBgBIAEoBRISCgpzZXNzaW9uX2lkGAIgASgJEgwKBGF4aXMYAyABKAUSDQoFdmFsdWUYBCABKAUiggEKFVByb3RvQ29udHJvbGxlclJ1bWJsZRIUCgxzZXNzaW9uX3Nsb3QYASABKAUSEgoKc2Vzc2lvbl9pZBgCIAEoCRIVCg1sb3dfZnJlcXVlbmN5GAMgASgFEhYKDmhpZ2hfZnJlcXVlbmN5GAQgASgFEhAKCGR1cmF0aW9uGAUgASgFIqoBChNSVENJY2VDYW5kaWRhdGVJbml0EhEKCWNhbmRpZGF0ZRgBIAEoCRIaCg1zZHBNTGluZUluZGV4GAIgASgNSACIAQESEwoGc2RwTWlkGAMgASgJSAGIAQESHQoQdXNlcm5hbWVGcmFnbWVudBgEIAEoCUgCiAEBQhAKDl9zZHBNTGluZUluZGV4QgkKB19zZHBNaWRCEwoRX3VzZXJuYW1lRnJhZ21lbnQiNgoZUlRDU2Vzc2lvbkRlc2NyaXB0aW9uSW5pdBILCgNzZHAYASABKAkSDAoEdHlwZRgCIAEoCSI5CghQcm90b0lDRRItCgljYW5kaWRhdGUYASABKAsyGi5wcm90by5SVENJY2VDYW5kaWRhdGVJbml0IjkKCFByb3RvU0RQEi0KA3NkcBgBIAEoCzIgLnByb3RvLlJUQ1Nlc3Npb25EZXNjcmlwdGlvbkluaXQiGAoIUHJvdG9SYXcSDAoEZGF0YRgBIAEoCSJFChxQcm90b0NsaWVudFJlcXVlc3RSb29tU3RyZWFtEhEKCXJvb21fbmFtZRgBIAEoCRISCgpzZXNzaW9uX2lkGAIgASgJIkcKF1Byb3RvQ2xpZW50RGlzY29ubmVjdGVkEhIKCnNlc3Npb25faWQYASABKAkSGAoQY29udHJvbGxlcl9zbG90cxgCIAMoBSIqChVQcm90b1NlcnZlclB1c2hTdHJlYW0SEQoJcm9vbV9uYW1lGAEgASgJQhZaFHJlbGF5L2ludGVybmFsL3Byb3RvYgZwcm90bzM");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MouseMove message
|
* MouseMove message
|
||||||
@@ -174,14 +174,14 @@ export type ProtoControllerAttach = Message<"proto.ProtoControllerAttach"> & {
|
|||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slot number (0-3)
|
* Session specific slot number (0-3)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 slot = 2;
|
* @generated from field: int32 session_slot = 2;
|
||||||
*/
|
*/
|
||||||
slot: number;
|
sessionSlot: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session ID of the client attaching the controller
|
* Session ID of the client
|
||||||
*
|
*
|
||||||
* @generated from field: string session_id = 3;
|
* @generated from field: string session_id = 3;
|
||||||
*/
|
*/
|
||||||
@@ -202,11 +202,18 @@ export const ProtoControllerAttachSchema: GenMessage<ProtoControllerAttach> = /*
|
|||||||
*/
|
*/
|
||||||
export type ProtoControllerDetach = Message<"proto.ProtoControllerDetach"> & {
|
export type ProtoControllerDetach = Message<"proto.ProtoControllerDetach"> & {
|
||||||
/**
|
/**
|
||||||
* Slot number (0-3)
|
* Session specific slot number (0-3)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 slot = 1;
|
* @generated from field: int32 session_slot = 1;
|
||||||
*/
|
*/
|
||||||
slot: number;
|
sessionSlot: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session ID of the client
|
||||||
|
*
|
||||||
|
* @generated from field: string session_id = 2;
|
||||||
|
*/
|
||||||
|
sessionId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,23 +230,30 @@ export const ProtoControllerDetachSchema: GenMessage<ProtoControllerDetach> = /*
|
|||||||
*/
|
*/
|
||||||
export type ProtoControllerButton = Message<"proto.ProtoControllerButton"> & {
|
export type ProtoControllerButton = Message<"proto.ProtoControllerButton"> & {
|
||||||
/**
|
/**
|
||||||
* Slot number (0-3)
|
* Session specific slot number (0-3)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 slot = 1;
|
* @generated from field: int32 session_slot = 1;
|
||||||
*/
|
*/
|
||||||
slot: number;
|
sessionSlot: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session ID of the client
|
||||||
|
*
|
||||||
|
* @generated from field: string session_id = 2;
|
||||||
|
*/
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button code (linux input event code)
|
* Button code (linux input event code)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 button = 2;
|
* @generated from field: int32 button = 3;
|
||||||
*/
|
*/
|
||||||
button: number;
|
button: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* true if pressed, false if released
|
* true if pressed, false if released
|
||||||
*
|
*
|
||||||
* @generated from field: bool pressed = 3;
|
* @generated from field: bool pressed = 4;
|
||||||
*/
|
*/
|
||||||
pressed: boolean;
|
pressed: boolean;
|
||||||
};
|
};
|
||||||
@@ -258,23 +272,30 @@ export const ProtoControllerButtonSchema: GenMessage<ProtoControllerButton> = /*
|
|||||||
*/
|
*/
|
||||||
export type ProtoControllerTrigger = Message<"proto.ProtoControllerTrigger"> & {
|
export type ProtoControllerTrigger = Message<"proto.ProtoControllerTrigger"> & {
|
||||||
/**
|
/**
|
||||||
* Slot number (0-3)
|
* Session specific slot number (0-3)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 slot = 1;
|
* @generated from field: int32 session_slot = 1;
|
||||||
*/
|
*/
|
||||||
slot: number;
|
sessionSlot: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session ID of the client
|
||||||
|
*
|
||||||
|
* @generated from field: string session_id = 2;
|
||||||
|
*/
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger number (0 for left, 1 for right)
|
* Trigger number (0 for left, 1 for right)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 trigger = 2;
|
* @generated from field: int32 trigger = 3;
|
||||||
*/
|
*/
|
||||||
trigger: number;
|
trigger: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* trigger value (-32768 to 32767)
|
* trigger value (-32768 to 32767)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 value = 3;
|
* @generated from field: int32 value = 4;
|
||||||
*/
|
*/
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
@@ -293,30 +314,37 @@ export const ProtoControllerTriggerSchema: GenMessage<ProtoControllerTrigger> =
|
|||||||
*/
|
*/
|
||||||
export type ProtoControllerStick = Message<"proto.ProtoControllerStick"> & {
|
export type ProtoControllerStick = Message<"proto.ProtoControllerStick"> & {
|
||||||
/**
|
/**
|
||||||
* Slot number (0-3)
|
* Session specific slot number (0-3)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 slot = 1;
|
* @generated from field: int32 session_slot = 1;
|
||||||
*/
|
*/
|
||||||
slot: number;
|
sessionSlot: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session ID of the client
|
||||||
|
*
|
||||||
|
* @generated from field: string session_id = 2;
|
||||||
|
*/
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stick number (0 for left, 1 for right)
|
* Stick number (0 for left, 1 for right)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 stick = 2;
|
* @generated from field: int32 stick = 3;
|
||||||
*/
|
*/
|
||||||
stick: number;
|
stick: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* X axis value (-32768 to 32767)
|
* X axis value (-32768 to 32767)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 x = 3;
|
* @generated from field: int32 x = 4;
|
||||||
*/
|
*/
|
||||||
x: number;
|
x: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Y axis value (-32768 to 32767)
|
* Y axis value (-32768 to 32767)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 y = 4;
|
* @generated from field: int32 y = 5;
|
||||||
*/
|
*/
|
||||||
y: number;
|
y: number;
|
||||||
};
|
};
|
||||||
@@ -335,23 +363,30 @@ export const ProtoControllerStickSchema: GenMessage<ProtoControllerStick> = /*@_
|
|||||||
*/
|
*/
|
||||||
export type ProtoControllerAxis = Message<"proto.ProtoControllerAxis"> & {
|
export type ProtoControllerAxis = Message<"proto.ProtoControllerAxis"> & {
|
||||||
/**
|
/**
|
||||||
* Slot number (0-3)
|
* Session specific slot number (0-3)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 slot = 1;
|
* @generated from field: int32 session_slot = 1;
|
||||||
*/
|
*/
|
||||||
slot: number;
|
sessionSlot: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session ID of the client
|
||||||
|
*
|
||||||
|
* @generated from field: string session_id = 2;
|
||||||
|
*/
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
* Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 axis = 2;
|
* @generated from field: int32 axis = 3;
|
||||||
*/
|
*/
|
||||||
axis: number;
|
axis: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* axis value (-1 to 1)
|
* axis value (-1 to 1)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 value = 3;
|
* @generated from field: int32 value = 4;
|
||||||
*/
|
*/
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
@@ -370,30 +405,37 @@ export const ProtoControllerAxisSchema: GenMessage<ProtoControllerAxis> = /*@__P
|
|||||||
*/
|
*/
|
||||||
export type ProtoControllerRumble = Message<"proto.ProtoControllerRumble"> & {
|
export type ProtoControllerRumble = Message<"proto.ProtoControllerRumble"> & {
|
||||||
/**
|
/**
|
||||||
* Slot number (0-3)
|
* Session specific slot number (0-3)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 slot = 1;
|
* @generated from field: int32 session_slot = 1;
|
||||||
*/
|
*/
|
||||||
slot: number;
|
sessionSlot: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session ID of the client
|
||||||
|
*
|
||||||
|
* @generated from field: string session_id = 2;
|
||||||
|
*/
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Low frequency rumble (0-65535)
|
* Low frequency rumble (0-65535)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 low_frequency = 2;
|
* @generated from field: int32 low_frequency = 3;
|
||||||
*/
|
*/
|
||||||
lowFrequency: number;
|
lowFrequency: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* High frequency rumble (0-65535)
|
* High frequency rumble (0-65535)
|
||||||
*
|
*
|
||||||
* @generated from field: int32 high_frequency = 3;
|
* @generated from field: int32 high_frequency = 4;
|
||||||
*/
|
*/
|
||||||
highFrequency: number;
|
highFrequency: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration in milliseconds
|
* Duration in milliseconds
|
||||||
*
|
*
|
||||||
* @generated from field: int32 duration = 4;
|
* @generated from field: int32 duration = 5;
|
||||||
*/
|
*/
|
||||||
duration: number;
|
duration: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { P2PMessageStream } from "./streamwrapper";
|
|||||||
const NESTRI_PROTOCOL_STREAM_REQUEST = "/nestri-relay/stream-request/1.0.0";
|
const NESTRI_PROTOCOL_STREAM_REQUEST = "/nestri-relay/stream-request/1.0.0";
|
||||||
|
|
||||||
export class WebRTCStream {
|
export class WebRTCStream {
|
||||||
|
private _sessionId: string | null = null;
|
||||||
private _p2p: Libp2p | undefined = undefined;
|
private _p2p: Libp2p | undefined = undefined;
|
||||||
private _p2pConn: Connection | undefined = undefined;
|
private _p2pConn: Connection | undefined = undefined;
|
||||||
private _msgStream: P2PMessageStream | undefined = undefined;
|
private _msgStream: P2PMessageStream | undefined = undefined;
|
||||||
@@ -128,9 +129,9 @@ export class WebRTCStream {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._msgStream.on("session-assigned", (data: ProtoClientRequestRoomStream) => {
|
this._msgStream.on("session-assigned", (data: ProtoClientRequestRoomStream) => {
|
||||||
const sessionId = data.sessionId;
|
this._sessionId = data.sessionId;
|
||||||
localStorage.setItem("nestri-session-id", sessionId);
|
localStorage.setItem("nestri-session-id", this._sessionId);
|
||||||
console.log("Session ID assigned:", sessionId, "for room:", data.roomName);
|
console.log("Session ID assigned:", this._sessionId, "for room:", data.roomName);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._msgStream.on("offer", async (data: ProtoSDP) => {
|
this._msgStream.on("offer", async (data: ProtoSDP) => {
|
||||||
@@ -162,7 +163,7 @@ export class WebRTCStream {
|
|||||||
this._onConnected?.(null);
|
this._onConnected?.(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
const clientId = localStorage.getItem("nestri-session-id");
|
const clientId = this.getSessionID();
|
||||||
if (clientId) {
|
if (clientId) {
|
||||||
console.debug("Using existing session ID:", clientId);
|
console.debug("Using existing session ID:", clientId);
|
||||||
}
|
}
|
||||||
@@ -180,8 +181,10 @@ export class WebRTCStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSessionID(): string {
|
public getSessionID(): string | null {
|
||||||
return localStorage.getItem("nestri-session-id") || "";
|
if (this._sessionId === null)
|
||||||
|
this._sessionId = localStorage.getItem("nestri-session-id");
|
||||||
|
return this._sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forces opus to stereo in Chromium browsers, because of course
|
// Forces opus to stereo in Chromium browsers, because of course
|
||||||
@@ -298,7 +301,7 @@ export class WebRTCStream {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
receiver.jitterBufferTarget = receiver.jitterBufferDelayHint = receiver.playoutDelayHint = 0;
|
receiver.jitterBufferTarget = receiver.jitterBufferDelayHint = receiver.playoutDelayHint = 0;
|
||||||
}
|
}
|
||||||
}, 15);
|
}, 50);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,7 @@ if (envs_map.size > 0) {
|
|||||||
let nestriControllers: Controller[] = [];
|
let nestriControllers: Controller[] = [];
|
||||||
|
|
||||||
window.addEventListener("gamepadconnected", (e) => {
|
window.addEventListener("gamepadconnected", (e) => {
|
||||||
// Ignore gamepads with id including "nestri"
|
|
||||||
console.log("Gamepad connected:", e.gamepad);
|
console.log("Gamepad connected:", e.gamepad);
|
||||||
if (e.gamepad.id.toLowerCase().includes("nestri"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
const controller = new Controller({
|
const controller = new Controller({
|
||||||
webrtc: stream,
|
webrtc: stream,
|
||||||
e: e,
|
e: e,
|
||||||
@@ -106,7 +102,7 @@ if (envs_map.size > 0) {
|
|||||||
if (e.gamepad.id.toLowerCase().includes("nestri"))
|
if (e.gamepad.id.toLowerCase().includes("nestri"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let disconnected = nestriControllers.find((c) => c.getLocalSlot() === e.gamepad.index);
|
let disconnected = nestriControllers.find((c) => c.getSlot() === e.gamepad.index);
|
||||||
if (disconnected) {
|
if (disconnected) {
|
||||||
disconnected.dispose();
|
disconnected.dispose();
|
||||||
nestriControllers = nestriControllers.filter((c) => c !== disconnected);
|
nestriControllers = nestriControllers.filter((c) => c !== disconnected);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func InitWebRTCAPI() error {
|
|||||||
mediaEngine := &webrtc.MediaEngine{}
|
mediaEngine := &webrtc.MediaEngine{}
|
||||||
|
|
||||||
// Register our extensions
|
// Register our extensions
|
||||||
if err := RegisterExtensions(mediaEngine); err != nil {
|
if err = RegisterExtensions(mediaEngine); err != nil {
|
||||||
return fmt.Errorf("failed to register extensions: %w", err)
|
return fmt.Errorf("failed to register extensions: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"math"
|
||||||
"relay/internal/common"
|
"relay/internal/common"
|
||||||
"relay/internal/connections"
|
"relay/internal/connections"
|
||||||
"relay/internal/shared"
|
"relay/internal/shared"
|
||||||
@@ -176,7 +177,7 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
|||||||
|
|
||||||
// Create participant for this viewer
|
// Create participant for this viewer
|
||||||
participant, err := shared.NewParticipant(
|
participant, err := shared.NewParticipant(
|
||||||
"", // session ID will be set if this is a client session
|
"",
|
||||||
stream.Conn().RemotePeer(),
|
stream.Conn().RemotePeer(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -189,101 +190,36 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
|||||||
participant.SessionID = session.SessionID
|
participant.SessionID = session.SessionID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign peer connection
|
||||||
participant.PeerConnection = pc
|
participant.PeerConnection = pc
|
||||||
|
|
||||||
// Create per-participant tracks
|
// Add audio/video tracks
|
||||||
if room.VideoTrack != nil {
|
{
|
||||||
participant.VideoTrack, err = webrtc.NewTrackLocalStaticRTP(
|
localTrack, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
room.VideoTrack.Codec(),
|
room.AudioCodec,
|
||||||
"video-"+participant.ID.String(),
|
"participant-"+participant.ID.String(),
|
||||||
"nestri-"+reqMsg.RoomName+"-video",
|
"participant-"+participant.ID.String()+"-audio",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to create participant video track", "room", reqMsg.RoomName, "err", err)
|
slog.Error("Failed to create track for stream request", "err", err)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rtpSender, err := pc.AddTrack(participant.VideoTrack)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to add participant video track", "room", reqMsg.RoomName, "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Added video track for participant",
|
|
||||||
"room", reqMsg.RoomName,
|
|
||||||
"participant", participant.ID,
|
|
||||||
"sender_id", fmt.Sprintf("%p", rtpSender))
|
|
||||||
|
|
||||||
// Relay packets from channel to track (VIDEO)
|
|
||||||
go func() {
|
|
||||||
for pkt := range participant.VideoChan {
|
|
||||||
// Use a context with timeout
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
done <- participant.VideoTrack.WriteRTP(pkt)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-done:
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, io.ErrClosedPipe) {
|
|
||||||
slog.Debug("Failed to write video", "room", reqMsg.RoomName, "err", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
participant.SetTrack(webrtc.RTPCodecTypeAudio, localTrack)
|
||||||
cancel()
|
slog.Debug("Set audio track for requested stream", "room", room.Name)
|
||||||
slog.Error("WriteRTP BLOCKED for >100ms!",
|
|
||||||
"participant", participant.ID,
|
|
||||||
"room", reqMsg.RoomName)
|
|
||||||
// Don't return, continue processing
|
|
||||||
}
|
}
|
||||||
}
|
{
|
||||||
}()
|
localTrack, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
}
|
room.VideoCodec,
|
||||||
if room.AudioTrack != nil {
|
"participant-"+participant.ID.String(),
|
||||||
participant.AudioTrack, err = webrtc.NewTrackLocalStaticRTP(
|
"participant-"+participant.ID.String()+"-video",
|
||||||
room.AudioTrack.Codec(),
|
|
||||||
"audio-"+participant.ID.String(),
|
|
||||||
"nestri-"+reqMsg.RoomName+"-audio",
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to create participant audio track", "room", reqMsg.RoomName, "err", err)
|
slog.Error("Failed to create track for stream request", "err", err)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := pc.AddTrack(participant.AudioTrack)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to add participant audio track", "room", reqMsg.RoomName, "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relay packets from channel to track (AUDIO)
|
|
||||||
go func() {
|
|
||||||
for pkt := range participant.AudioChan {
|
|
||||||
start := time.Now()
|
|
||||||
if err := participant.AudioTrack.WriteRTP(pkt); err != nil {
|
|
||||||
if !errors.Is(err, io.ErrClosedPipe) {
|
|
||||||
slog.Debug("Failed to write audio to participant", "room", reqMsg.RoomName, "err", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
duration := time.Since(start)
|
participant.SetTrack(webrtc.RTPCodecTypeVideo, localTrack)
|
||||||
if duration > 50*time.Millisecond {
|
slog.Debug("Set video track for requested stream", "room", room.Name)
|
||||||
slog.Warn("Slow audio WriteRTP detected",
|
|
||||||
"duration", duration,
|
|
||||||
"participant", participant.ID,
|
|
||||||
"room", reqMsg.RoomName)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add participant to room
|
|
||||||
room.AddParticipant(participant)
|
|
||||||
|
|
||||||
// Cleanup on disconnect
|
// Cleanup on disconnect
|
||||||
cleanupParticipantID := participant.ID
|
cleanupParticipantID := participant.ID
|
||||||
@@ -294,6 +230,9 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
|||||||
slog.Info("Participant disconnected from room", "room", reqMsg.RoomName, "participant", cleanupParticipantID)
|
slog.Info("Participant disconnected from room", "room", reqMsg.RoomName, "participant", cleanupParticipantID)
|
||||||
room.RemoveParticipantByID(cleanupParticipantID)
|
room.RemoveParticipantByID(cleanupParticipantID)
|
||||||
participant.Close()
|
participant.Close()
|
||||||
|
} else if state == webrtc.PeerConnectionStateConnected {
|
||||||
|
// Add participant to room when connection is established
|
||||||
|
room.AddParticipant(participant)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -334,33 +273,33 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
|||||||
peerID := stream.Conn().RemotePeer()
|
peerID := stream.Conn().RemotePeer()
|
||||||
|
|
||||||
// Check if it's a controller attach with assigned slot
|
// Check if it's a controller attach with assigned slot
|
||||||
if attach := msgWrapper.GetControllerAttach(); attach != nil && attach.Slot >= 0 {
|
if attach := msgWrapper.GetControllerAttach(); attach != nil && attach.SessionSlot >= 0 {
|
||||||
if session, ok := sp.relay.ClientSessions.Get(peerID); ok {
|
if session, ok := sp.relay.ClientSessions.Get(peerID); ok {
|
||||||
// Check if slot already tracked
|
// Check if slot already tracked
|
||||||
hasSlot := false
|
hasSlot := false
|
||||||
for _, slot := range session.ControllerSlots {
|
for _, slot := range session.ControllerSlots {
|
||||||
if slot == attach.Slot {
|
if slot == attach.SessionSlot {
|
||||||
hasSlot = true
|
hasSlot = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasSlot {
|
if !hasSlot {
|
||||||
session.ControllerSlots = append(session.ControllerSlots, attach.Slot)
|
session.ControllerSlots = append(session.ControllerSlots, attach.SessionSlot)
|
||||||
session.LastActivity = time.Now()
|
session.LastActivity = time.Now()
|
||||||
slog.Info("Controller slot assigned to client session",
|
slog.Info("Controller slot assigned to client session",
|
||||||
"session", session.SessionID,
|
"session", session.SessionID,
|
||||||
"slot", attach.Slot,
|
"slot", attach.SessionSlot,
|
||||||
"total_slots", len(session.ControllerSlots))
|
"total_slots", len(session.ControllerSlots))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a controller detach
|
// Check if it's a controller detach
|
||||||
if detach := msgWrapper.GetControllerDetach(); detach != nil && detach.Slot >= 0 {
|
if detach := msgWrapper.GetControllerDetach(); detach != nil && detach.SessionSlot >= 0 {
|
||||||
if session, ok := sp.relay.ClientSessions.Get(peerID); ok {
|
if session, ok := sp.relay.ClientSessions.Get(peerID); ok {
|
||||||
newSlots := make([]int32, 0, len(session.ControllerSlots))
|
newSlots := make([]int32, 0, len(session.ControllerSlots))
|
||||||
for _, slot := range session.ControllerSlots {
|
for _, slot := range session.ControllerSlots {
|
||||||
if slot != detach.Slot {
|
if slot != detach.SessionSlot {
|
||||||
newSlots = append(newSlots, slot)
|
newSlots = append(newSlots, slot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +307,7 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
|||||||
session.LastActivity = time.Now()
|
session.LastActivity = time.Now()
|
||||||
slog.Info("Controller slot removed from client session",
|
slog.Info("Controller slot removed from client session",
|
||||||
"session", session.SessionID,
|
"session", session.SessionID,
|
||||||
"slot", detach.Slot,
|
"slot", detach.SessionSlot,
|
||||||
"remaining_slots", len(session.ControllerSlots))
|
"remaining_slots", len(session.ControllerSlots))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,19 +476,25 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) || errors.Is(err, network.ErrReset) {
|
if errors.Is(err, io.EOF) || errors.Is(err, network.ErrReset) {
|
||||||
slog.Debug("Stream push connection closed by peer", "peer", stream.Conn().RemotePeer(), "error", err)
|
slog.Debug("Stream push connection closed by peer", "peer", stream.Conn().RemotePeer(), "error", err)
|
||||||
|
if room != nil {
|
||||||
|
room.Close()
|
||||||
|
sp.incomingConns.Set(room.Name, nil)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Error("Failed to receive data for stream push", "err", err)
|
slog.Error("Failed to receive data for stream push", "err", err)
|
||||||
_ = stream.Reset()
|
_ = stream.Reset()
|
||||||
|
if room != nil {
|
||||||
|
room.Close()
|
||||||
|
sp.incomingConns.Set(room.Name, nil)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if msgWrapper.MessageBase == nil {
|
if msgWrapper.MessageBase == nil {
|
||||||
slog.Error("No MessageBase in stream push")
|
slog.Error("No MessageBase in stream push")
|
||||||
_ = stream.Reset()
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msgWrapper.MessageBase.PayloadType {
|
switch msgWrapper.MessageBase.PayloadType {
|
||||||
@@ -606,7 +551,7 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
|||||||
slog.Error("Failed to add ICE candidate for pushed stream", "err", err)
|
slog.Error("Failed to add ICE candidate for pushed stream", "err", err)
|
||||||
}
|
}
|
||||||
for _, heldIce := range iceHolder {
|
for _, heldIce := range iceHolder {
|
||||||
if err := conn.pc.AddICECandidate(heldIce); err != nil {
|
if err = conn.pc.AddICECandidate(heldIce); err != nil {
|
||||||
slog.Error("Failed to add held ICE candidate for pushed stream", "err", err)
|
slog.Error("Failed to add held ICE candidate for pushed stream", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,6 +590,9 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign room peer connection
|
||||||
|
room.PeerConnection = pc
|
||||||
|
|
||||||
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
|
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
|
||||||
// TODO: Is this the best way to handle DataChannel? Should we just use the map directly?
|
// TODO: Is this the best way to handle DataChannel? Should we just use the map directly?
|
||||||
room.DataChannel = connections.NewNestriDataChannel(dc)
|
room.DataChannel = connections.NewNestriDataChannel(dc)
|
||||||
@@ -708,17 +656,6 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
pc.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
pc.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
||||||
localTrack, err := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, remoteTrack.Kind().String(), fmt.Sprintf("nestri-%s-%s", room.Name, remoteTrack.Kind().String()))
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create local track for pushed stream", "room", room.Name, "track_kind", remoteTrack.Kind().String(), "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("Received track for pushed stream", "room", room.Name, "track_kind", remoteTrack.Kind().String())
|
|
||||||
|
|
||||||
// Set track for Room
|
|
||||||
room.SetTrack(remoteTrack.Kind(), localTrack)
|
|
||||||
|
|
||||||
// Prepare PlayoutDelayExtension so we don't need to recreate it for each packet
|
// Prepare PlayoutDelayExtension so we don't need to recreate it for each packet
|
||||||
playoutExt := &rtp.PlayoutDelayExtension{
|
playoutExt := &rtp.PlayoutDelayExtension{
|
||||||
MinDelay: 0,
|
MinDelay: 0,
|
||||||
@@ -730,6 +667,12 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remoteTrack.Kind() == webrtc.RTPCodecTypeAudio {
|
||||||
|
room.AudioCodec = remoteTrack.Codec().RTPCodecCapability
|
||||||
|
} else if remoteTrack.Kind() == webrtc.RTPCodecTypeVideo {
|
||||||
|
room.VideoCodec = remoteTrack.Codec().RTPCodecCapability
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
rtpPacket, _, err := remoteTrack.ReadRTP()
|
rtpPacket, _, err := remoteTrack.ReadRTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -741,19 +684,61 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
|||||||
|
|
||||||
// Use PlayoutDelayExtension for low latency, if set for this track kind
|
// Use PlayoutDelayExtension for low latency, if set for this track kind
|
||||||
if extID, ok := common.GetExtension(remoteTrack.Kind(), common.ExtensionPlayoutDelay); ok {
|
if extID, ok := common.GetExtension(remoteTrack.Kind(), common.ExtensionPlayoutDelay); ok {
|
||||||
if err := rtpPacket.SetExtension(extID, playoutPayload); err != nil {
|
if err = rtpPacket.SetExtension(extID, playoutPayload); err != nil {
|
||||||
slog.Error("Failed to set PlayoutDelayExtension for room", "room", room.Name, "err", err)
|
slog.Error("Failed to set PlayoutDelayExtension for room", "room", room.Name, "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
room.BroadcastPacket(remoteTrack.Kind(), rtpPacket)
|
// Calculate differences
|
||||||
|
var timeDiff int64
|
||||||
|
var sequenceDiff int
|
||||||
|
|
||||||
|
if remoteTrack.Kind() == webrtc.RTPCodecTypeVideo {
|
||||||
|
timeDiff = int64(rtpPacket.Timestamp) - int64(room.LastVideoTimestamp)
|
||||||
|
if !room.VideoTimestampSet {
|
||||||
|
timeDiff = 0
|
||||||
|
room.VideoTimestampSet = true
|
||||||
|
} else if timeDiff < -(math.MaxUint32 / 10) {
|
||||||
|
timeDiff += math.MaxUint32 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sequenceDiff = int(rtpPacket.SequenceNumber) - int(room.LastVideoSequenceNumber)
|
||||||
|
if !room.VideoSequenceSet {
|
||||||
|
sequenceDiff = 0
|
||||||
|
room.VideoSequenceSet = true
|
||||||
|
} else if sequenceDiff < -(math.MaxUint16 / 10) {
|
||||||
|
sequenceDiff += math.MaxUint16 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
room.LastVideoTimestamp = rtpPacket.Timestamp
|
||||||
|
room.LastVideoSequenceNumber = rtpPacket.SequenceNumber
|
||||||
|
} else { // Audio
|
||||||
|
timeDiff = int64(rtpPacket.Timestamp) - int64(room.LastAudioTimestamp)
|
||||||
|
if !room.AudioTimestampSet {
|
||||||
|
timeDiff = 0
|
||||||
|
room.AudioTimestampSet = true
|
||||||
|
} else if timeDiff < -(math.MaxUint32 / 10) {
|
||||||
|
timeDiff += math.MaxUint32 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sequenceDiff = int(rtpPacket.SequenceNumber) - int(room.LastAudioSequenceNumber)
|
||||||
|
if !room.AudioSequenceSet {
|
||||||
|
sequenceDiff = 0
|
||||||
|
room.AudioSequenceSet = true
|
||||||
|
} else if sequenceDiff < -(math.MaxUint16 / 10) {
|
||||||
|
sequenceDiff += math.MaxUint16 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
room.LastAudioTimestamp = rtpPacket.Timestamp
|
||||||
|
room.LastAudioSequenceNumber = rtpPacket.SequenceNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast with differences
|
||||||
|
room.BroadcastPacketRetimed(remoteTrack.Kind(), rtpPacket, timeDiff, sequenceDiff)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("Track closed for room", "room", room.Name, "track_kind", remoteTrack.Kind().String())
|
slog.Debug("Track closed for room", "room", room.Name, "track_kind", remoteTrack.Kind().String())
|
||||||
|
|
||||||
// Cleanup the track from the room
|
|
||||||
room.SetTrack(remoteTrack.Kind(), nil)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set the remote description
|
// Set the remote description
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (r *Relay) DeleteRoomIfEmpty(room *shared.Room) {
|
|||||||
if room == nil {
|
if room == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if room.Participants.Len() == 0 && r.LocalRooms.Has(room.ID) {
|
if len(room.Participants) <= 0 && r.LocalRooms.Has(room.ID) {
|
||||||
slog.Debug("Deleting empty room without participants", "room", room.Name)
|
slog.Debug("Deleting empty room without participants", "room", room.Name)
|
||||||
r.LocalRooms.Delete(room.ID)
|
r.LocalRooms.Delete(room.ID)
|
||||||
err := room.PeerConnection.Close()
|
err := room.PeerConnection.Close()
|
||||||
|
|||||||
@@ -195,18 +195,18 @@ func (r *Relay) updateMeshRoomStates(peerID peer.ID, states []shared.RoomInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If previously did not exist, but does now, request a connection if participants exist for our room
|
// If previously did not exist, but does now, request a connection if participants exist for our room
|
||||||
existed := r.Rooms.Has(state.ID.String())
|
/*existed := r.Rooms.Has(state.ID.String())
|
||||||
if !existed {
|
if !existed {
|
||||||
// Request connection to this peer if we have participants in our local room
|
// Request connection to this peer if we have participants in our local room
|
||||||
if room, ok := r.LocalRooms.Get(state.ID); ok {
|
if room, ok := r.LocalRooms.Get(state.ID); ok {
|
||||||
if room.Participants.Len() > 0 {
|
if len(room.Participants) > 0 {
|
||||||
slog.Debug("Got new remote room state, we locally have participants for, requesting stream", "room_name", room.Name, "peer", peerID)
|
slog.Debug("Got new remote room state, we locally have participants for, requesting stream", "room_name", room.Name, "peer", peerID)
|
||||||
if err := r.StreamProtocol.RequestStream(context.Background(), room, peerID); err != nil {
|
if err := r.StreamProtocol.RequestStream(context.Background(), room, peerID); err != nil {
|
||||||
slog.Error("Failed to request stream for new remote room state", "room_name", room.Name, "peer", peerID, "err", err)
|
slog.Error("Failed to request stream for new remote room state", "room_name", room.Name, "peer", peerID, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
r.Rooms.Set(state.ID.String(), state)
|
r.Rooms.Set(state.ID.String(), state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,8 +364,8 @@ func (x *ProtoKeyUp) GetKey() int32 {
|
|||||||
type ProtoControllerAttach struct {
|
type ProtoControllerAttach struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // One of the following enums: "ps", "xbox" or "switch"
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // One of the following enums: "ps", "xbox" or "switch"
|
||||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
SessionSlot int32 `protobuf:"varint,2,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3)
|
||||||
SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client attaching the controller
|
SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -407,9 +407,9 @@ func (x *ProtoControllerAttach) GetId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerAttach) GetSlot() int32 {
|
func (x *ProtoControllerAttach) GetSessionSlot() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Slot
|
return x.SessionSlot
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -424,7 +424,8 @@ func (x *ProtoControllerAttach) GetSessionId() string {
|
|||||||
// ControllerDetach message
|
// ControllerDetach message
|
||||||
type ProtoControllerDetach struct {
|
type ProtoControllerDetach struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Slot int32 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3)
|
||||||
|
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -459,19 +460,27 @@ func (*ProtoControllerDetach) Descriptor() ([]byte, []int) {
|
|||||||
return file_types_proto_rawDescGZIP(), []int{8}
|
return file_types_proto_rawDescGZIP(), []int{8}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerDetach) GetSlot() int32 {
|
func (x *ProtoControllerDetach) GetSessionSlot() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Slot
|
return x.SessionSlot
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProtoControllerDetach) GetSessionId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SessionId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// ControllerButton message
|
// ControllerButton message
|
||||||
type ProtoControllerButton struct {
|
type ProtoControllerButton struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Slot int32 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3)
|
||||||
Button int32 `protobuf:"varint,2,opt,name=button,proto3" json:"button,omitempty"` // Button code (linux input event code)
|
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client
|
||||||
Pressed bool `protobuf:"varint,3,opt,name=pressed,proto3" json:"pressed,omitempty"` // true if pressed, false if released
|
Button int32 `protobuf:"varint,3,opt,name=button,proto3" json:"button,omitempty"` // Button code (linux input event code)
|
||||||
|
Pressed bool `protobuf:"varint,4,opt,name=pressed,proto3" json:"pressed,omitempty"` // true if pressed, false if released
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -506,13 +515,20 @@ func (*ProtoControllerButton) Descriptor() ([]byte, []int) {
|
|||||||
return file_types_proto_rawDescGZIP(), []int{9}
|
return file_types_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerButton) GetSlot() int32 {
|
func (x *ProtoControllerButton) GetSessionSlot() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Slot
|
return x.SessionSlot
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProtoControllerButton) GetSessionId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SessionId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerButton) GetButton() int32 {
|
func (x *ProtoControllerButton) GetButton() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Button
|
return x.Button
|
||||||
@@ -530,9 +546,10 @@ func (x *ProtoControllerButton) GetPressed() bool {
|
|||||||
// ControllerTriggers message
|
// ControllerTriggers message
|
||||||
type ProtoControllerTrigger struct {
|
type ProtoControllerTrigger struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Slot int32 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3)
|
||||||
Trigger int32 `protobuf:"varint,2,opt,name=trigger,proto3" json:"trigger,omitempty"` // Trigger number (0 for left, 1 for right)
|
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client
|
||||||
Value int32 `protobuf:"varint,3,opt,name=value,proto3" json:"value,omitempty"` // trigger value (-32768 to 32767)
|
Trigger int32 `protobuf:"varint,3,opt,name=trigger,proto3" json:"trigger,omitempty"` // Trigger number (0 for left, 1 for right)
|
||||||
|
Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // trigger value (-32768 to 32767)
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -567,13 +584,20 @@ func (*ProtoControllerTrigger) Descriptor() ([]byte, []int) {
|
|||||||
return file_types_proto_rawDescGZIP(), []int{10}
|
return file_types_proto_rawDescGZIP(), []int{10}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerTrigger) GetSlot() int32 {
|
func (x *ProtoControllerTrigger) GetSessionSlot() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Slot
|
return x.SessionSlot
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProtoControllerTrigger) GetSessionId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SessionId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerTrigger) GetTrigger() int32 {
|
func (x *ProtoControllerTrigger) GetTrigger() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Trigger
|
return x.Trigger
|
||||||
@@ -591,10 +615,11 @@ func (x *ProtoControllerTrigger) GetValue() int32 {
|
|||||||
// ControllerSticks message
|
// ControllerSticks message
|
||||||
type ProtoControllerStick struct {
|
type ProtoControllerStick struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Slot int32 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3)
|
||||||
Stick int32 `protobuf:"varint,2,opt,name=stick,proto3" json:"stick,omitempty"` // Stick number (0 for left, 1 for right)
|
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client
|
||||||
X int32 `protobuf:"varint,3,opt,name=x,proto3" json:"x,omitempty"` // X axis value (-32768 to 32767)
|
Stick int32 `protobuf:"varint,3,opt,name=stick,proto3" json:"stick,omitempty"` // Stick number (0 for left, 1 for right)
|
||||||
Y int32 `protobuf:"varint,4,opt,name=y,proto3" json:"y,omitempty"` // Y axis value (-32768 to 32767)
|
X int32 `protobuf:"varint,4,opt,name=x,proto3" json:"x,omitempty"` // X axis value (-32768 to 32767)
|
||||||
|
Y int32 `protobuf:"varint,5,opt,name=y,proto3" json:"y,omitempty"` // Y axis value (-32768 to 32767)
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -629,13 +654,20 @@ func (*ProtoControllerStick) Descriptor() ([]byte, []int) {
|
|||||||
return file_types_proto_rawDescGZIP(), []int{11}
|
return file_types_proto_rawDescGZIP(), []int{11}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerStick) GetSlot() int32 {
|
func (x *ProtoControllerStick) GetSessionSlot() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Slot
|
return x.SessionSlot
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProtoControllerStick) GetSessionId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SessionId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerStick) GetStick() int32 {
|
func (x *ProtoControllerStick) GetStick() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Stick
|
return x.Stick
|
||||||
@@ -660,9 +692,10 @@ func (x *ProtoControllerStick) GetY() int32 {
|
|||||||
// ControllerAxis message
|
// ControllerAxis message
|
||||||
type ProtoControllerAxis struct {
|
type ProtoControllerAxis struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Slot int32 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3)
|
||||||
Axis int32 `protobuf:"varint,2,opt,name=axis,proto3" json:"axis,omitempty"` // Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client
|
||||||
Value int32 `protobuf:"varint,3,opt,name=value,proto3" json:"value,omitempty"` // axis value (-1 to 1)
|
Axis int32 `protobuf:"varint,3,opt,name=axis,proto3" json:"axis,omitempty"` // Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||||
|
Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // axis value (-1 to 1)
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -697,13 +730,20 @@ func (*ProtoControllerAxis) Descriptor() ([]byte, []int) {
|
|||||||
return file_types_proto_rawDescGZIP(), []int{12}
|
return file_types_proto_rawDescGZIP(), []int{12}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerAxis) GetSlot() int32 {
|
func (x *ProtoControllerAxis) GetSessionSlot() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Slot
|
return x.SessionSlot
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProtoControllerAxis) GetSessionId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SessionId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerAxis) GetAxis() int32 {
|
func (x *ProtoControllerAxis) GetAxis() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Axis
|
return x.Axis
|
||||||
@@ -721,10 +761,11 @@ func (x *ProtoControllerAxis) GetValue() int32 {
|
|||||||
// ControllerRumble message
|
// ControllerRumble message
|
||||||
type ProtoControllerRumble struct {
|
type ProtoControllerRumble struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Slot int32 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3)
|
||||||
LowFrequency int32 `protobuf:"varint,2,opt,name=low_frequency,json=lowFrequency,proto3" json:"low_frequency,omitempty"` // Low frequency rumble (0-65535)
|
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client
|
||||||
HighFrequency int32 `protobuf:"varint,3,opt,name=high_frequency,json=highFrequency,proto3" json:"high_frequency,omitempty"` // High frequency rumble (0-65535)
|
LowFrequency int32 `protobuf:"varint,3,opt,name=low_frequency,json=lowFrequency,proto3" json:"low_frequency,omitempty"` // Low frequency rumble (0-65535)
|
||||||
Duration int32 `protobuf:"varint,4,opt,name=duration,proto3" json:"duration,omitempty"` // Duration in milliseconds
|
HighFrequency int32 `protobuf:"varint,4,opt,name=high_frequency,json=highFrequency,proto3" json:"high_frequency,omitempty"` // High frequency rumble (0-65535)
|
||||||
|
Duration int32 `protobuf:"varint,5,opt,name=duration,proto3" json:"duration,omitempty"` // Duration in milliseconds
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -759,13 +800,20 @@ func (*ProtoControllerRumble) Descriptor() ([]byte, []int) {
|
|||||||
return file_types_proto_rawDescGZIP(), []int{13}
|
return file_types_proto_rawDescGZIP(), []int{13}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerRumble) GetSlot() int32 {
|
func (x *ProtoControllerRumble) GetSessionSlot() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Slot
|
return x.SessionSlot
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProtoControllerRumble) GetSessionId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SessionId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ProtoControllerRumble) GetLowFrequency() int32 {
|
func (x *ProtoControllerRumble) GetLowFrequency() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.LowFrequency
|
return x.LowFrequency
|
||||||
@@ -1215,36 +1263,48 @@ const file_types_proto_rawDesc = "" +
|
|||||||
"\x03key\x18\x01 \x01(\x05R\x03key\"\x1e\n" +
|
"\x03key\x18\x01 \x01(\x05R\x03key\"\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"ProtoKeyUp\x12\x10\n" +
|
"ProtoKeyUp\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\x05R\x03key\"Z\n" +
|
"\x03key\x18\x01 \x01(\x05R\x03key\"i\n" +
|
||||||
"\x15ProtoControllerAttach\x12\x0e\n" +
|
"\x15ProtoControllerAttach\x12\x0e\n" +
|
||||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
"\x02id\x18\x01 \x01(\tR\x02id\x12!\n" +
|
||||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x1d\n" +
|
"\fsession_slot\x18\x02 \x01(\x05R\vsessionSlot\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"session_id\x18\x03 \x01(\tR\tsessionId\"+\n" +
|
"session_id\x18\x03 \x01(\tR\tsessionId\"Y\n" +
|
||||||
"\x15ProtoControllerDetach\x12\x12\n" +
|
"\x15ProtoControllerDetach\x12!\n" +
|
||||||
"\x04slot\x18\x01 \x01(\x05R\x04slot\"]\n" +
|
"\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" +
|
||||||
"\x15ProtoControllerButton\x12\x12\n" +
|
"\n" +
|
||||||
"\x04slot\x18\x01 \x01(\x05R\x04slot\x12\x16\n" +
|
"session_id\x18\x02 \x01(\tR\tsessionId\"\x8b\x01\n" +
|
||||||
"\x06button\x18\x02 \x01(\x05R\x06button\x12\x18\n" +
|
"\x15ProtoControllerButton\x12!\n" +
|
||||||
"\apressed\x18\x03 \x01(\bR\apressed\"\\\n" +
|
"\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" +
|
||||||
"\x16ProtoControllerTrigger\x12\x12\n" +
|
"\n" +
|
||||||
"\x04slot\x18\x01 \x01(\x05R\x04slot\x12\x18\n" +
|
"session_id\x18\x02 \x01(\tR\tsessionId\x12\x16\n" +
|
||||||
"\atrigger\x18\x02 \x01(\x05R\atrigger\x12\x14\n" +
|
"\x06button\x18\x03 \x01(\x05R\x06button\x12\x18\n" +
|
||||||
"\x05value\x18\x03 \x01(\x05R\x05value\"\\\n" +
|
"\apressed\x18\x04 \x01(\bR\apressed\"\x8a\x01\n" +
|
||||||
"\x14ProtoControllerStick\x12\x12\n" +
|
"\x16ProtoControllerTrigger\x12!\n" +
|
||||||
"\x04slot\x18\x01 \x01(\x05R\x04slot\x12\x14\n" +
|
"\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" +
|
||||||
"\x05stick\x18\x02 \x01(\x05R\x05stick\x12\f\n" +
|
"\n" +
|
||||||
"\x01x\x18\x03 \x01(\x05R\x01x\x12\f\n" +
|
"session_id\x18\x02 \x01(\tR\tsessionId\x12\x18\n" +
|
||||||
"\x01y\x18\x04 \x01(\x05R\x01y\"S\n" +
|
"\atrigger\x18\x03 \x01(\x05R\atrigger\x12\x14\n" +
|
||||||
"\x13ProtoControllerAxis\x12\x12\n" +
|
"\x05value\x18\x04 \x01(\x05R\x05value\"\x8a\x01\n" +
|
||||||
"\x04slot\x18\x01 \x01(\x05R\x04slot\x12\x12\n" +
|
"\x14ProtoControllerStick\x12!\n" +
|
||||||
"\x04axis\x18\x02 \x01(\x05R\x04axis\x12\x14\n" +
|
"\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" +
|
||||||
"\x05value\x18\x03 \x01(\x05R\x05value\"\x93\x01\n" +
|
"\n" +
|
||||||
"\x15ProtoControllerRumble\x12\x12\n" +
|
"session_id\x18\x02 \x01(\tR\tsessionId\x12\x14\n" +
|
||||||
"\x04slot\x18\x01 \x01(\x05R\x04slot\x12#\n" +
|
"\x05stick\x18\x03 \x01(\x05R\x05stick\x12\f\n" +
|
||||||
"\rlow_frequency\x18\x02 \x01(\x05R\flowFrequency\x12%\n" +
|
"\x01x\x18\x04 \x01(\x05R\x01x\x12\f\n" +
|
||||||
"\x0ehigh_frequency\x18\x03 \x01(\x05R\rhighFrequency\x12\x1a\n" +
|
"\x01y\x18\x05 \x01(\x05R\x01y\"\x81\x01\n" +
|
||||||
"\bduration\x18\x04 \x01(\x05R\bduration\"\xde\x01\n" +
|
"\x13ProtoControllerAxis\x12!\n" +
|
||||||
|
"\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"session_id\x18\x02 \x01(\tR\tsessionId\x12\x12\n" +
|
||||||
|
"\x04axis\x18\x03 \x01(\x05R\x04axis\x12\x14\n" +
|
||||||
|
"\x05value\x18\x04 \x01(\x05R\x05value\"\xc1\x01\n" +
|
||||||
|
"\x15ProtoControllerRumble\x12!\n" +
|
||||||
|
"\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"session_id\x18\x02 \x01(\tR\tsessionId\x12#\n" +
|
||||||
|
"\rlow_frequency\x18\x03 \x01(\x05R\flowFrequency\x12%\n" +
|
||||||
|
"\x0ehigh_frequency\x18\x04 \x01(\x05R\rhighFrequency\x12\x1a\n" +
|
||||||
|
"\bduration\x18\x05 \x01(\x05R\bduration\"\xde\x01\n" +
|
||||||
"\x13RTCIceCandidateInit\x12\x1c\n" +
|
"\x13RTCIceCandidateInit\x12\x1c\n" +
|
||||||
"\tcandidate\x18\x01 \x01(\tR\tcandidate\x12)\n" +
|
"\tcandidate\x18\x01 \x01(\tR\tcandidate\x12)\n" +
|
||||||
"\rsdpMLineIndex\x18\x02 \x01(\rH\x00R\rsdpMLineIndex\x88\x01\x01\x12\x1b\n" +
|
"\rsdpMLineIndex\x18\x02 \x01(\rH\x00R\rsdpMLineIndex\x88\x01\x01\x12\x1b\n" +
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package shared
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"relay/internal/common"
|
"relay/internal/common"
|
||||||
"relay/internal/connections"
|
"relay/internal/connections"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/libp2p/go-libp2p/core/peer"
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
"github.com/oklog/ulid/v2"
|
"github.com/oklog/ulid/v2"
|
||||||
"github.com/pion/rtp"
|
|
||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,8 +24,15 @@ type Participant struct {
|
|||||||
// Per-viewer tracks and channels
|
// Per-viewer tracks and channels
|
||||||
VideoTrack *webrtc.TrackLocalStaticRTP
|
VideoTrack *webrtc.TrackLocalStaticRTP
|
||||||
AudioTrack *webrtc.TrackLocalStaticRTP
|
AudioTrack *webrtc.TrackLocalStaticRTP
|
||||||
VideoChan chan *rtp.Packet
|
|
||||||
AudioChan chan *rtp.Packet
|
// Per-viewer RTP state for retiming
|
||||||
|
VideoSequenceNumber uint16
|
||||||
|
VideoTimestamp uint32
|
||||||
|
AudioSequenceNumber uint16
|
||||||
|
AudioTimestamp uint32
|
||||||
|
|
||||||
|
packetQueue chan *participantPacket
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParticipant(sessionID string, peerID peer.ID) (*Participant, error) {
|
func NewParticipant(sessionID string, peerID peer.ID) (*Participant, error) {
|
||||||
@@ -31,24 +40,50 @@ func NewParticipant(sessionID string, peerID peer.ID) (*Participant, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create ULID for Participant: %w", err)
|
return nil, fmt.Errorf("failed to create ULID for Participant: %w", err)
|
||||||
}
|
}
|
||||||
return &Participant{
|
p := &Participant{
|
||||||
ID: id,
|
ID: id,
|
||||||
SessionID: sessionID,
|
SessionID: sessionID,
|
||||||
PeerID: peerID,
|
PeerID: peerID,
|
||||||
VideoChan: make(chan *rtp.Packet, 500),
|
VideoSequenceNumber: 0,
|
||||||
AudioChan: make(chan *rtp.Packet, 100),
|
VideoTimestamp: 0,
|
||||||
}, nil
|
AudioSequenceNumber: 0,
|
||||||
|
AudioTimestamp: 0,
|
||||||
|
packetQueue: make(chan *participantPacket, 1000),
|
||||||
|
}
|
||||||
|
|
||||||
|
go p.packetWriter()
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrack sets audio/video track for Participant
|
||||||
|
func (p *Participant) SetTrack(trackType webrtc.RTPCodecType, track *webrtc.TrackLocalStaticRTP) {
|
||||||
|
switch trackType {
|
||||||
|
case webrtc.RTPCodecTypeAudio:
|
||||||
|
p.AudioTrack = track
|
||||||
|
_, err := p.PeerConnection.AddTrack(track)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to add Participant audio track", err)
|
||||||
|
}
|
||||||
|
case webrtc.RTPCodecTypeVideo:
|
||||||
|
p.VideoTrack = track
|
||||||
|
_, err := p.PeerConnection.AddTrack(track)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to add Participant video track", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
slog.Warn("Unknown track type", "participant", p.ID, "trackType", trackType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close cleans up participant resources
|
// Close cleans up participant resources
|
||||||
func (p *Participant) Close() {
|
func (p *Participant) Close() {
|
||||||
if p.VideoChan != nil {
|
if p.DataChannel != nil {
|
||||||
close(p.VideoChan)
|
err := p.DataChannel.Close()
|
||||||
p.VideoChan = nil
|
if err != nil {
|
||||||
|
slog.Error("Failed to close Participant DataChannel", err)
|
||||||
}
|
}
|
||||||
if p.AudioChan != nil {
|
p.DataChannel = nil
|
||||||
close(p.AudioChan)
|
|
||||||
p.AudioChan = nil
|
|
||||||
}
|
}
|
||||||
if p.PeerConnection != nil {
|
if p.PeerConnection != nil {
|
||||||
err := p.PeerConnection.Close()
|
err := p.PeerConnection.Close()
|
||||||
@@ -57,4 +92,45 @@ func (p *Participant) Close() {
|
|||||||
}
|
}
|
||||||
p.PeerConnection = nil
|
p.PeerConnection = nil
|
||||||
}
|
}
|
||||||
|
if p.VideoTrack != nil {
|
||||||
|
p.VideoTrack = nil
|
||||||
|
}
|
||||||
|
if p.AudioTrack != nil {
|
||||||
|
p.AudioTrack = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Participant) packetWriter() {
|
||||||
|
for pkt := range p.packetQueue {
|
||||||
|
var track *webrtc.TrackLocalStaticRTP
|
||||||
|
var sequenceNumber uint16
|
||||||
|
var timestamp uint32
|
||||||
|
|
||||||
|
// No mutex needed - only this goroutine modifies these
|
||||||
|
if pkt.kind == webrtc.RTPCodecTypeAudio {
|
||||||
|
track = p.AudioTrack
|
||||||
|
p.AudioSequenceNumber = uint16(int(p.AudioSequenceNumber) + pkt.sequenceDiff)
|
||||||
|
p.AudioTimestamp = uint32(int64(p.AudioTimestamp) + pkt.timeDiff)
|
||||||
|
sequenceNumber = p.AudioSequenceNumber
|
||||||
|
timestamp = p.AudioTimestamp
|
||||||
|
} else {
|
||||||
|
track = p.VideoTrack
|
||||||
|
p.VideoSequenceNumber = uint16(int(p.VideoSequenceNumber) + pkt.sequenceDiff)
|
||||||
|
p.VideoTimestamp = uint32(int64(p.VideoTimestamp) + pkt.timeDiff)
|
||||||
|
sequenceNumber = p.VideoSequenceNumber
|
||||||
|
timestamp = p.VideoTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
if track != nil {
|
||||||
|
pkt.packet.SequenceNumber = sequenceNumber
|
||||||
|
pkt.packet.Timestamp = timestamp
|
||||||
|
|
||||||
|
if err := track.WriteRTP(pkt.packet); err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
||||||
|
slog.Error("WriteRTP failed", "participant", p.ID, "kind", pkt.kind, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return packet struct to pool
|
||||||
|
participantPacketPool.Put(pkt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package shared
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"relay/internal/common"
|
|
||||||
"relay/internal/connections"
|
"relay/internal/connections"
|
||||||
"time"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/libp2p/go-libp2p/core/peer"
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
"github.com/oklog/ulid/v2"
|
"github.com/oklog/ulid/v2"
|
||||||
@@ -12,6 +12,19 @@ import (
|
|||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var participantPacketPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &participantPacket{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type participantPacket struct {
|
||||||
|
kind webrtc.RTPCodecType
|
||||||
|
packet *rtp.Packet
|
||||||
|
timeDiff int64
|
||||||
|
sequenceDiff int
|
||||||
|
}
|
||||||
|
|
||||||
type RoomInfo struct {
|
type RoomInfo struct {
|
||||||
ID ulid.ULID `json:"id"`
|
ID ulid.ULID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -20,16 +33,27 @@ type RoomInfo struct {
|
|||||||
|
|
||||||
type Room struct {
|
type Room struct {
|
||||||
RoomInfo
|
RoomInfo
|
||||||
|
AudioCodec webrtc.RTPCodecCapability
|
||||||
|
VideoCodec webrtc.RTPCodecCapability
|
||||||
PeerConnection *webrtc.PeerConnection
|
PeerConnection *webrtc.PeerConnection
|
||||||
AudioTrack *webrtc.TrackLocalStaticRTP
|
|
||||||
VideoTrack *webrtc.TrackLocalStaticRTP
|
|
||||||
DataChannel *connections.NestriDataChannel
|
DataChannel *connections.NestriDataChannel
|
||||||
Participants *common.SafeMap[ulid.ULID, *Participant]
|
|
||||||
|
|
||||||
// Broadcast queues (unbuffered, fan-out happens async)
|
// Atomic pointer to slice of participant channels
|
||||||
videoBroadcastChan chan *rtp.Packet
|
participantChannels atomic.Pointer[[]chan<- *participantPacket]
|
||||||
audioBroadcastChan chan *rtp.Packet
|
participantsMtx sync.Mutex // Use only for add/remove
|
||||||
broadcastStop chan struct{}
|
|
||||||
|
Participants map[ulid.ULID]*Participant // Keep general track of Participant(s)
|
||||||
|
|
||||||
|
// Track last seen values to calculate diffs
|
||||||
|
LastVideoTimestamp uint32
|
||||||
|
LastVideoSequenceNumber uint16
|
||||||
|
LastAudioTimestamp uint32
|
||||||
|
LastAudioSequenceNumber uint16
|
||||||
|
|
||||||
|
VideoTimestampSet bool
|
||||||
|
VideoSequenceSet bool
|
||||||
|
AudioTimestampSet bool
|
||||||
|
AudioSequenceSet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoom(name string, roomID ulid.ULID, ownerID peer.ID) *Room {
|
func NewRoom(name string, roomID ulid.ULID, ownerID peer.ID) *Room {
|
||||||
@@ -39,133 +63,109 @@ func NewRoom(name string, roomID ulid.ULID, ownerID peer.ID) *Room {
|
|||||||
Name: name,
|
Name: name,
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
},
|
},
|
||||||
Participants: common.NewSafeMap[ulid.ULID, *Participant](),
|
PeerConnection: nil,
|
||||||
videoBroadcastChan: make(chan *rtp.Packet, 1000), // Large buffer for incoming packets
|
DataChannel: nil,
|
||||||
audioBroadcastChan: make(chan *rtp.Packet, 500),
|
Participants: make(map[ulid.ULID]*Participant),
|
||||||
broadcastStop: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start async broadcasters
|
emptyChannels := make([]chan<- *participantPacket, 0)
|
||||||
go r.videoBroadcaster()
|
r.participantChannels.Store(&emptyChannels)
|
||||||
go r.audioBroadcaster()
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes up Room (stream ended)
|
||||||
|
func (r *Room) Close() {
|
||||||
|
if r.DataChannel != nil {
|
||||||
|
err := r.DataChannel.Close()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to close Room DataChannel", err)
|
||||||
|
}
|
||||||
|
r.DataChannel = nil
|
||||||
|
}
|
||||||
|
if r.PeerConnection != nil {
|
||||||
|
err := r.PeerConnection.Close()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to close Room PeerConnection", err)
|
||||||
|
}
|
||||||
|
r.PeerConnection = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AddParticipant adds a Participant to a Room
|
// AddParticipant adds a Participant to a Room
|
||||||
func (r *Room) AddParticipant(participant *Participant) {
|
func (r *Room) AddParticipant(participant *Participant) {
|
||||||
slog.Debug("Adding participant to room", "participant", participant.ID, "room", r.Name)
|
r.participantsMtx.Lock()
|
||||||
r.Participants.Set(participant.ID, participant)
|
defer r.participantsMtx.Unlock()
|
||||||
|
|
||||||
|
r.Participants[participant.ID] = participant
|
||||||
|
|
||||||
|
// Update channel slice atomically
|
||||||
|
current := r.participantChannels.Load()
|
||||||
|
newChannels := make([]chan<- *participantPacket, len(*current)+1)
|
||||||
|
copy(newChannels, *current)
|
||||||
|
newChannels[len(*current)] = participant.packetQueue
|
||||||
|
|
||||||
|
r.participantChannels.Store(&newChannels)
|
||||||
|
|
||||||
|
slog.Debug("Added participant", "participant", participant.ID, "room", r.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveParticipantByID removes a Participant from a Room by participant's ID
|
// RemoveParticipantByID removes a Participant from a Room by participant's ID
|
||||||
func (r *Room) RemoveParticipantByID(pID ulid.ULID) {
|
func (r *Room) RemoveParticipantByID(pID ulid.ULID) {
|
||||||
if _, ok := r.Participants.Get(pID); ok {
|
r.participantsMtx.Lock()
|
||||||
r.Participants.Delete(pID)
|
defer r.participantsMtx.Unlock()
|
||||||
|
|
||||||
|
participant, ok := r.Participants[pID]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(r.Participants, pID)
|
||||||
|
|
||||||
|
// Update channel slice
|
||||||
|
current := r.participantChannels.Load()
|
||||||
|
newChannels := make([]chan<- *participantPacket, 0, len(*current)-1)
|
||||||
|
for _, ch := range *current {
|
||||||
|
if ch != participant.packetQueue {
|
||||||
|
newChannels = append(newChannels, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.participantChannels.Store(&newChannels)
|
||||||
|
|
||||||
|
slog.Debug("Removed participant", "participant", pID, "room", r.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOnline checks if the room is online (has both audio and video tracks)
|
// IsOnline checks if the room is online
|
||||||
func (r *Room) IsOnline() bool {
|
func (r *Room) IsOnline() bool {
|
||||||
return r.AudioTrack != nil && r.VideoTrack != nil
|
return r.PeerConnection != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Room) SetTrack(trackType webrtc.RTPCodecType, track *webrtc.TrackLocalStaticRTP) {
|
func (r *Room) BroadcastPacketRetimed(kind webrtc.RTPCodecType, pkt *rtp.Packet, timeDiff int64, sequenceDiff int) {
|
||||||
switch trackType {
|
// Lock-free load of channel slice
|
||||||
case webrtc.RTPCodecTypeAudio:
|
channels := r.participantChannels.Load()
|
||||||
r.AudioTrack = track
|
|
||||||
case webrtc.RTPCodecTypeVideo:
|
|
||||||
r.VideoTrack = track
|
|
||||||
default:
|
|
||||||
slog.Warn("Unknown track type", "room", r.Name, "trackType", trackType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BroadcastPacket enqueues packet for async broadcast (non-blocking)
|
// no participants..
|
||||||
func (r *Room) BroadcastPacket(kind webrtc.RTPCodecType, pkt *rtp.Packet) {
|
if len(*channels) == 0 {
|
||||||
start := time.Now()
|
|
||||||
if kind == webrtc.RTPCodecTypeVideo {
|
|
||||||
select {
|
|
||||||
case r.videoBroadcastChan <- pkt:
|
|
||||||
duration := time.Since(start)
|
|
||||||
if duration > 10*time.Millisecond {
|
|
||||||
slog.Warn("Slow video broadcast enqueue", "duration", duration, "room", r.Name)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Broadcast queue full - system overload, drop packet globally
|
|
||||||
slog.Warn("Video broadcast queue full, dropping packet", "room", r.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case r.audioBroadcastChan <- pkt:
|
|
||||||
duration := time.Since(start)
|
|
||||||
if duration > 10*time.Millisecond {
|
|
||||||
slog.Warn("Slow audio broadcast enqueue", "duration", duration, "room", r.Name)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
slog.Warn("Audio broadcast queue full, dropping packet", "room", r.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stops the broadcasters
|
|
||||||
func (r *Room) Close() {
|
|
||||||
close(r.broadcastStop)
|
|
||||||
close(r.videoBroadcastChan)
|
|
||||||
close(r.audioBroadcastChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
// videoBroadcaster runs async fan-out for video packets
|
|
||||||
func (r *Room) videoBroadcaster() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case pkt := <-r.videoBroadcastChan:
|
|
||||||
// Fan out to all participants without blocking
|
|
||||||
r.Participants.Range(func(_ ulid.ULID, participant *Participant) bool {
|
|
||||||
if participant.VideoChan != nil {
|
|
||||||
// Clone packet for each participant to avoid shared pointer issues
|
|
||||||
clonedPkt := pkt.Clone()
|
|
||||||
select {
|
|
||||||
case participant.VideoChan <- clonedPkt:
|
|
||||||
// Sent
|
|
||||||
default:
|
|
||||||
// Participant slow, drop packet
|
|
||||||
slog.Debug("Dropped video packet for slow participant",
|
|
||||||
"room", r.Name,
|
|
||||||
"participant", participant.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
case <-r.broadcastStop:
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// audioBroadcaster runs async fan-out for audio packets
|
// Send to each participant channel (non-blocking)
|
||||||
func (r *Room) audioBroadcaster() {
|
for i, ch := range *channels {
|
||||||
for {
|
// Get packet struct from pool
|
||||||
|
pp := participantPacketPool.Get().(*participantPacket)
|
||||||
|
pp.kind = kind
|
||||||
|
pp.packet = pkt.Clone()
|
||||||
|
pp.timeDiff = timeDiff
|
||||||
|
pp.sequenceDiff = sequenceDiff
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case pkt := <-r.audioBroadcastChan:
|
case ch <- pp:
|
||||||
r.Participants.Range(func(_ ulid.ULID, participant *Participant) bool {
|
// Sent successfully
|
||||||
if participant.AudioChan != nil {
|
|
||||||
// Clone packet for each participant to avoid shared pointer issues
|
|
||||||
clonedPkt := pkt.Clone()
|
|
||||||
select {
|
|
||||||
case participant.AudioChan <- clonedPkt:
|
|
||||||
// Sent
|
|
||||||
default:
|
default:
|
||||||
// Participant slow, drop packet
|
// Channel full, drop packet, log?
|
||||||
slog.Debug("Dropped audio packet for slow participant",
|
slog.Warn("Channel full, dropping packet", "channel_index", i)
|
||||||
"room", r.Name,
|
participantPacketPool.Put(pp)
|
||||||
"participant", participant.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
case <-r.broadcastStop:
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ NVIDIA_INSTALLER_DIR="/tmp"
|
|||||||
TIMEOUT_SECONDS=10
|
TIMEOUT_SECONDS=10
|
||||||
ENTCMD_PREFIX=""
|
ENTCMD_PREFIX=""
|
||||||
|
|
||||||
# Ensures user directory ownership
|
# Ensures user ownership across directories
|
||||||
chown_user_directory() {
|
handle_user_permissions() {
|
||||||
if ! $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}" 2>/dev/null; then
|
if ! $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}" 2>/dev/null; then
|
||||||
echo "Error: Failed to change ownership of ${NESTRI_HOME} to ${NESTRI_USER}:${NESTRI_USER}" >&2
|
echo "Error: Failed to change ownership of ${NESTRI_HOME} to ${NESTRI_USER}:${NESTRI_USER}" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
# Also apply to .cache separately
|
# Also apply to .cache
|
||||||
if [[ -d "${NESTRI_HOME}/.cache" ]]; then
|
if [[ -d "${NESTRI_HOME}/.cache" ]]; then
|
||||||
if ! $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}/.cache" 2>/dev/null; then
|
if ! $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}/.cache" 2>/dev/null; then
|
||||||
echo "Error: Failed to change ownership of ${NESTRI_HOME}/.cache to ${NESTRI_USER}:${NESTRI_USER}" >&2
|
echo "Error: Failed to change ownership of ${NESTRI_HOME}/.cache to ${NESTRI_USER}:${NESTRI_USER}" >&2
|
||||||
@@ -324,9 +324,23 @@ main() {
|
|||||||
log "Skipping CAP_SYS_NICE for gamescope, capability not available"
|
log "Skipping CAP_SYS_NICE for gamescope, capability not available"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Handle user directory permissions
|
# Make sure /tmp/.X11-unix exists..
|
||||||
log "Ensuring user directory permissions..."
|
if [[ ! -d "/tmp/.X11-unix" ]]; then
|
||||||
chown_user_directory || exit 1
|
log "Creating /tmp/.X11-unix directory.."
|
||||||
|
$ENTCMD_PREFIX mkdir -p /tmp/.X11-unix || {
|
||||||
|
log "Error: Failed to create /tmp/.X11-unix directory"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
# Set required perms..
|
||||||
|
$ENTCMD_PREFIX chmod 1777 /tmp/.X11-unix || {
|
||||||
|
log "Error: Failed to chmod /tmp/.X11-unix to 1777"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle user permissions
|
||||||
|
log "Ensuring user permissions..."
|
||||||
|
handle_user_permissions || exit 1
|
||||||
|
|
||||||
# Setup namespaceless env if needed for container runtime
|
# Setup namespaceless env if needed for container runtime
|
||||||
if [[ "$container_runtime" != "podman" ]]; then
|
if [[ "$container_runtime" != "podman" ]]; then
|
||||||
@@ -336,7 +350,7 @@ main() {
|
|||||||
|
|
||||||
# Make sure /run/udev/ directory exists with /run/udev/control, needed for virtual controller support
|
# Make sure /run/udev/ directory exists with /run/udev/control, needed for virtual controller support
|
||||||
if [[ ! -d "/run/udev" || ! -e "/run/udev/control" ]]; then
|
if [[ ! -d "/run/udev" || ! -e "/run/udev/control" ]]; then
|
||||||
log "Creating /run/udev directory and control file..."
|
log "Creating /run/udev directory and control file.."
|
||||||
$ENTCMD_PREFIX mkdir -p /run/udev || {
|
$ENTCMD_PREFIX mkdir -p /run/udev || {
|
||||||
log "Error: Failed to create /run/udev directory"
|
log "Error: Failed to create /run/udev directory"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ start_compositor() {
|
|||||||
|
|
||||||
if [[ -n "${NESTRI_LAUNCH_CMD}" ]]; then
|
if [[ -n "${NESTRI_LAUNCH_CMD}" ]]; then
|
||||||
log "Starting application: $NESTRI_LAUNCH_CMD"
|
log "Starting application: $NESTRI_LAUNCH_CMD"
|
||||||
WAYLAND_DISPLAY=wayland-0 /bin/bash -c "$NESTRI_LAUNCH_CMD" &
|
WAYLAND_DISPLAY="$COMPOSITOR_SOCKET" /bin/bash -c "$NESTRI_LAUNCH_CMD" &
|
||||||
APP_PID=$!
|
APP_PID=$!
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|||||||
128
packages/server/Cargo.lock
generated
128
packages/server/Cargo.lock
generated
@@ -181,7 +181,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -246,7 +246,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -257,7 +257,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -395,7 +395,7 @@ version = "0.72.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
@@ -406,7 +406,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"shlex",
|
"shlex",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -417,9 +417,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.4"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blake2"
|
name = "blake2"
|
||||||
@@ -603,9 +603,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.49"
|
version = "4.5.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
|
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -613,9 +613,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.49"
|
version = "4.5.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
|
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -632,7 +632,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -839,7 +839,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -879,7 +879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
|
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -956,7 +956,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1090,7 +1090,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1262,7 +1262,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1396,7 +1396,7 @@ version = "0.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1f2cbc4577536c849335878552f42086bfd25a8dcd6f54a18655cf818b20c8f"
|
checksum = "e1f2cbc4577536c849335878552f42086bfd25a8dcd6f54a18655cf818b20c8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
@@ -1421,7 +1421,7 @@ dependencies = [
|
|||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2299,9 +2299,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.11.4"
|
version = "2.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.0",
|
"hashbrown 0.16.0",
|
||||||
@@ -2368,9 +2368,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
@@ -2828,7 +2828,7 @@ checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3239,7 +3239,7 @@ version = "0.30.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -3355,9 +3355,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
@@ -3499,7 +3499,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3604,7 +3604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3627,9 +3627,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.101"
|
version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -3654,7 +3654,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3677,7 +3677,7 @@ dependencies = [
|
|||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3861,7 +3861,7 @@ version = "0.5.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4038,7 +4038,7 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -4047,9 +4047,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.33"
|
version = "0.23.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c"
|
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"log",
|
"log",
|
||||||
@@ -4180,7 +4180,7 @@ version = "3.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -4240,7 +4240,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4481,9 +4481,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -4507,7 +4507,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4516,7 +4516,7 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.9.4",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys",
|
||||||
]
|
]
|
||||||
@@ -4574,7 +4574,7 @@ checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4603,7 +4603,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4614,7 +4614,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4707,7 +4707,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4817,7 +4817,7 @@ version = "0.6.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.10.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
@@ -4861,7 +4861,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4986,9 +4986,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "universal-hash"
|
||||||
@@ -5079,9 +5079,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vimputti"
|
name = "vimputti"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5839a89185ccec572f746ccc02e37702cc6c0b62a6aa0d9bcd6e5921edba12"
|
checksum = "ffb370ee43e3ee4ca5329886e64dc5b27c83dc8cced5a63c2418777dac9a41a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -5177,7 +5177,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5212,7 +5212,7 @@ checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@@ -5502,7 +5502,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5513,7 +5513,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5959,7 +5959,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5980,7 +5980,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6000,7 +6000,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6021,7 +6021,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6054,5 +6054,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ rand = "0.9"
|
|||||||
rustls = { version = "0.23", features = ["ring"] }
|
rustls = { version = "0.23", features = ["ring"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
vimputti = "0.1.3"
|
vimputti = "0.1.4"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
prost = "0.14"
|
prost = "0.14"
|
||||||
prost-types = "0.14"
|
prost-types = "0.14"
|
||||||
|
|||||||
@@ -211,6 +211,14 @@ impl Args {
|
|||||||
.value_parser(value_parser!(u32).range(1..))
|
.value_parser(value_parser!(u32).range(1..))
|
||||||
.default_value("192"),
|
.default_value("192"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("software-render")
|
||||||
|
.long("software-render")
|
||||||
|
.env("SOFTWARE_RENDER")
|
||||||
|
.help("Use software rendering for wayland")
|
||||||
|
.value_parser(BoolishValueParser::new())
|
||||||
|
.default_value("false"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("zero-copy")
|
Arg::new("zero-copy")
|
||||||
.long("zero-copy")
|
.long("zero-copy")
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ pub struct AppArgs {
|
|||||||
/// vimputti socket path
|
/// vimputti socket path
|
||||||
pub vimputti_path: Option<String>,
|
pub vimputti_path: Option<String>,
|
||||||
|
|
||||||
|
/// Use software rendering for wayland display
|
||||||
|
pub software_render: bool,
|
||||||
|
|
||||||
/// Experimental zero-copy pipeline support
|
/// Experimental zero-copy pipeline support
|
||||||
/// TODO: Move to video encoding flags
|
/// TODO: Move to video encoding flags
|
||||||
pub zero_copy: bool,
|
pub zero_copy: bool,
|
||||||
@@ -51,6 +54,10 @@ impl AppArgs {
|
|||||||
vimputti_path: matches
|
vimputti_path: matches
|
||||||
.get_one::<String>("vimputti-path")
|
.get_one::<String>("vimputti-path")
|
||||||
.map(|s| s.clone()),
|
.map(|s| s.clone()),
|
||||||
|
software_render: matches
|
||||||
|
.get_one::<bool>("software-render")
|
||||||
|
.unwrap_or(&false)
|
||||||
|
.clone(),
|
||||||
zero_copy: matches
|
zero_copy: matches
|
||||||
.get_one::<bool>("zero-copy")
|
.get_one::<bool>("zero-copy")
|
||||||
.unwrap_or(&false)
|
.unwrap_or(&false)
|
||||||
@@ -73,6 +80,7 @@ impl AppArgs {
|
|||||||
"> vimputti_path: '{}'",
|
"> vimputti_path: '{}'",
|
||||||
self.vimputti_path.as_ref().map_or("None", |s| s.as_str())
|
self.vimputti_path.as_ref().map_or("None", |s| s.as_str())
|
||||||
);
|
);
|
||||||
|
tracing::info!("> software_render: {}", self.software_render);
|
||||||
tracing::info!("> zero_copy: {}", self.zero_copy);
|
tracing::info!("> zero_copy: {}", self.zero_copy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -585,7 +585,6 @@ pub fn get_best_working_encoder(
|
|||||||
encoders: &Vec<VideoEncoderInfo>,
|
encoders: &Vec<VideoEncoderInfo>,
|
||||||
codec: &Codec,
|
codec: &Codec,
|
||||||
encoder_type: &EncoderType,
|
encoder_type: &EncoderType,
|
||||||
zero_copy: bool,
|
|
||||||
) -> Result<VideoEncoderInfo, Box<dyn Error>> {
|
) -> Result<VideoEncoderInfo, Box<dyn Error>> {
|
||||||
let mut candidates = get_encoders_by_videocodec(
|
let mut candidates = get_encoders_by_videocodec(
|
||||||
encoders,
|
encoders,
|
||||||
@@ -601,7 +600,7 @@ pub fn get_best_working_encoder(
|
|||||||
while !candidates.is_empty() {
|
while !candidates.is_empty() {
|
||||||
let best = get_best_compatible_encoder(&candidates, codec, encoder_type)?;
|
let best = get_best_compatible_encoder(&candidates, codec, encoder_type)?;
|
||||||
tracing::info!("Testing encoder: {}", best.name,);
|
tracing::info!("Testing encoder: {}", best.name,);
|
||||||
if test_encoder(&best, zero_copy).is_ok() {
|
if test_encoder(&best).is_ok() {
|
||||||
return Ok(best);
|
return Ok(best);
|
||||||
} else {
|
} else {
|
||||||
// Remove this encoder and try next best
|
// Remove this encoder and try next best
|
||||||
@@ -613,25 +612,10 @@ pub fn get_best_working_encoder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Test if a pipeline with the given encoder can be created and set to Playing
|
/// Test if a pipeline with the given encoder can be created and set to Playing
|
||||||
pub fn test_encoder(encoder: &VideoEncoderInfo, zero_copy: bool) -> Result<(), Box<dyn Error>> {
|
pub fn test_encoder(encoder: &VideoEncoderInfo) -> Result<(), Box<dyn Error>> {
|
||||||
let src = gstreamer::ElementFactory::make("waylanddisplaysrc").build()?;
|
let src = gstreamer::ElementFactory::make("videotestsrc").build()?;
|
||||||
if let Some(gpu_info) = &encoder.gpu_info {
|
|
||||||
src.set_property_from_str("render-node", gpu_info.render_path());
|
|
||||||
}
|
|
||||||
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||||
let caps = gstreamer::Caps::from_str(&format!(
|
let caps = gstreamer::Caps::from_str("video/x-raw,width=1280,height=720,framerate=30/1")?;
|
||||||
"{},width=1280,height=720,framerate=30/1{}",
|
|
||||||
if zero_copy {
|
|
||||||
if encoder.encoder_api == EncoderAPI::NVENC {
|
|
||||||
"video/x-raw(memory:CUDAMemory)"
|
|
||||||
} else {
|
|
||||||
"video/x-raw(memory:DMABuf)"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"video/x-raw"
|
|
||||||
},
|
|
||||||
if zero_copy { "" } else { ",format=RGBx" }
|
|
||||||
))?;
|
|
||||||
caps_filter.set_property("caps", &caps);
|
caps_filter.set_property("caps", &caps);
|
||||||
|
|
||||||
let enc = gstreamer::ElementFactory::make(&encoder.name).build()?;
|
let enc = gstreamer::ElementFactory::make(&encoder.name).build()?;
|
||||||
@@ -642,41 +626,9 @@ pub fn test_encoder(encoder: &VideoEncoderInfo, zero_copy: bool) -> Result<(), B
|
|||||||
// Create pipeline and link elements
|
// Create pipeline and link elements
|
||||||
let pipeline = gstreamer::Pipeline::new();
|
let pipeline = gstreamer::Pipeline::new();
|
||||||
|
|
||||||
if zero_copy {
|
|
||||||
if encoder.encoder_api == EncoderAPI::NVENC {
|
|
||||||
// NVENC zero-copy path
|
|
||||||
pipeline.add_many(&[&src, &caps_filter, &enc, &sink])?;
|
|
||||||
gstreamer::Element::link_many(&[&src, &caps_filter, &enc, &sink])?;
|
|
||||||
} else {
|
|
||||||
// VA-API/QSV zero-copy path
|
|
||||||
let vapostproc = gstreamer::ElementFactory::make("vapostproc").build()?;
|
|
||||||
let va_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
|
||||||
let va_caps = gstreamer::Caps::from_str("video/x-raw(memory:VAMemory),format=NV12")?;
|
|
||||||
va_caps_filter.set_property("caps", &va_caps);
|
|
||||||
|
|
||||||
pipeline.add_many(&[
|
|
||||||
&src,
|
|
||||||
&caps_filter,
|
|
||||||
&vapostproc,
|
|
||||||
&va_caps_filter,
|
|
||||||
&enc,
|
|
||||||
&sink,
|
|
||||||
])?;
|
|
||||||
gstreamer::Element::link_many(&[
|
|
||||||
&src,
|
|
||||||
&caps_filter,
|
|
||||||
&vapostproc,
|
|
||||||
&va_caps_filter,
|
|
||||||
&enc,
|
|
||||||
&sink,
|
|
||||||
])?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Non-zero-copy path for all encoders - needs videoconvert
|
|
||||||
let videoconvert = gstreamer::ElementFactory::make("videoconvert").build()?;
|
let videoconvert = gstreamer::ElementFactory::make("videoconvert").build()?;
|
||||||
pipeline.add_many(&[&src, &caps_filter, &videoconvert, &enc, &sink])?;
|
pipeline.add_many(&[&src, &caps_filter, &videoconvert, &enc, &sink])?;
|
||||||
gstreamer::Element::link_many(&[&src, &caps_filter, &videoconvert, &enc, &sink])?;
|
gstreamer::Element::link_many(&[&src, &caps_filter, &videoconvert, &enc, &sink])?;
|
||||||
}
|
|
||||||
|
|
||||||
let bus = pipeline.bus().ok_or("Pipeline has no bus")?;
|
let bus = pipeline.bus().ok_or("Pipeline has no bus")?;
|
||||||
pipeline.set_state(gstreamer::State::Playing)?;
|
pipeline.set_state(gstreamer::State::Playing)?;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ impl ControllerInput {
|
|||||||
pub struct ControllerManager {
|
pub struct ControllerManager {
|
||||||
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
||||||
cmd_tx: mpsc::Sender<Payload>,
|
cmd_tx: mpsc::Sender<Payload>,
|
||||||
rumble_tx: mpsc::Sender<(u32, u16, u16, u16)>, // (slot, strong, weak, duration_ms)
|
rumble_tx: mpsc::Sender<(u32, u16, u16, u16, String)>, // (slot, strong, weak, duration_ms, session_id)
|
||||||
attach_tx: mpsc::Sender<ProtoControllerAttach>,
|
attach_tx: mpsc::Sender<ProtoControllerAttach>,
|
||||||
}
|
}
|
||||||
impl ControllerManager {
|
impl ControllerManager {
|
||||||
@@ -55,7 +55,7 @@ impl ControllerManager {
|
|||||||
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
||||||
) -> Result<(
|
) -> Result<(
|
||||||
Self,
|
Self,
|
||||||
mpsc::Receiver<(u32, u16, u16, u16)>,
|
mpsc::Receiver<(u32, u16, u16, u16, String)>,
|
||||||
mpsc::Receiver<ProtoControllerAttach>,
|
mpsc::Receiver<ProtoControllerAttach>,
|
||||||
)> {
|
)> {
|
||||||
let (cmd_tx, cmd_rx) = mpsc::channel(512);
|
let (cmd_tx, cmd_rx) = mpsc::channel(512);
|
||||||
@@ -88,12 +88,12 @@ impl ControllerManager {
|
|||||||
struct ControllerSlot {
|
struct ControllerSlot {
|
||||||
controller: ControllerInput,
|
controller: ControllerInput,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
last_activity: std::time::Instant,
|
session_slot: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns first free controller slot from 0-7
|
// Returns first free controller slot from 0-16
|
||||||
fn get_free_slot(controllers: &HashMap<u32, ControllerSlot>) -> Option<u32> {
|
fn get_free_slot(controllers: &HashMap<u32, ControllerSlot>) -> Option<u32> {
|
||||||
for slot in 0..8 {
|
for slot in 0..17 {
|
||||||
if !controllers.contains_key(&slot) {
|
if !controllers.contains_key(&slot) {
|
||||||
return Some(slot);
|
return Some(slot);
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ fn get_free_slot(controllers: &HashMap<u32, ControllerSlot>) -> Option<u32> {
|
|||||||
async fn command_loop(
|
async fn command_loop(
|
||||||
mut cmd_rx: mpsc::Receiver<Payload>,
|
mut cmd_rx: mpsc::Receiver<Payload>,
|
||||||
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
||||||
rumble_tx: mpsc::Sender<(u32, u16, u16, u16)>,
|
rumble_tx: mpsc::Sender<(u32, u16, u16, u16, String)>,
|
||||||
attach_tx: mpsc::Sender<ProtoControllerAttach>,
|
attach_tx: mpsc::Sender<ProtoControllerAttach>,
|
||||||
) {
|
) {
|
||||||
let mut controllers: HashMap<u32, ControllerSlot> = HashMap::new();
|
let mut controllers: HashMap<u32, ControllerSlot> = HashMap::new();
|
||||||
@@ -112,13 +112,15 @@ async fn command_loop(
|
|||||||
match payload {
|
match payload {
|
||||||
Payload::ControllerAttach(data) => {
|
Payload::ControllerAttach(data) => {
|
||||||
let session_id = data.session_id.clone();
|
let session_id = data.session_id.clone();
|
||||||
|
let session_slot = data.session_slot.clone();
|
||||||
|
|
||||||
// Check if this session already has a slot (reconnection)
|
// Check if this session already has a slot (reconnection)
|
||||||
let existing_slot = controllers
|
let existing_slot = controllers
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, slot)| slot.session_id == session_id && !session_id.is_empty())
|
.find(|(_, slot)| {
|
||||||
|
slot.session_id == session_id && slot.session_slot == session_slot as u32
|
||||||
|
})
|
||||||
.map(|(slot_num, _)| *slot_num);
|
.map(|(slot_num, _)| *slot_num);
|
||||||
|
|
||||||
let slot = existing_slot.or_else(|| get_free_slot(&controllers));
|
let slot = existing_slot.or_else(|| get_free_slot(&controllers));
|
||||||
|
|
||||||
if let Some(slot) = slot {
|
if let Some(slot) = slot {
|
||||||
@@ -131,7 +133,7 @@ async fn command_loop(
|
|||||||
controller
|
controller
|
||||||
.device_mut()
|
.device_mut()
|
||||||
.on_rumble(move |strong, weak, duration_ms| {
|
.on_rumble(move |strong, weak, duration_ms| {
|
||||||
let _ = rumble_tx.try_send((slot, strong, weak, duration_ms));
|
let _ = rumble_tx.try_send((slot, strong, weak, duration_ms, data.session_id.clone()));
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -146,7 +148,7 @@ async fn command_loop(
|
|||||||
// Return to attach_tx what slot was assigned
|
// Return to attach_tx what slot was assigned
|
||||||
let attach_info = ProtoControllerAttach {
|
let attach_info = ProtoControllerAttach {
|
||||||
id: data.id.clone(),
|
id: data.id.clone(),
|
||||||
slot: slot as i32,
|
session_slot: slot as i32,
|
||||||
session_id: session_id.clone(),
|
session_id: session_id.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,7 +159,7 @@ async fn command_loop(
|
|||||||
ControllerSlot {
|
ControllerSlot {
|
||||||
controller,
|
controller,
|
||||||
session_id: session_id.clone(),
|
session_id: session_id.clone(),
|
||||||
last_activity: std::time::Instant::now(),
|
session_slot: session_slot.clone() as u32,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -185,25 +187,25 @@ async fn command_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Payload::ControllerDetach(data) => {
|
Payload::ControllerDetach(data) => {
|
||||||
if controllers.remove(&(data.slot as u32)).is_some() {
|
if controllers.remove(&(data.session_slot as u32)).is_some() {
|
||||||
tracing::info!("Controller detached from slot {}", data.slot);
|
tracing::info!("Controller detached from slot {}", data.session_slot);
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("No controller found in slot {} to detach", data.slot);
|
tracing::warn!("No controller found in slot {} to detach", data.session_slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Payload::ControllerButton(data) => {
|
Payload::ControllerButton(data) => {
|
||||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||||
if let Some(button) = vimputti::Button::from_ev_code(data.button as u16) {
|
if let Some(button) = vimputti::Button::from_ev_code(data.button as u16) {
|
||||||
let device = controller.controller.device();
|
let device = controller.controller.device();
|
||||||
device.button(button, data.pressed);
|
device.button(button, data.pressed);
|
||||||
device.sync();
|
device.sync();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("Controller slot {} not found for button event", data.slot);
|
tracing::warn!("Controller slot {} not found for button event", data.session_slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Payload::ControllerStick(data) => {
|
Payload::ControllerStick(data) => {
|
||||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||||
let device = controller.controller.device();
|
let device = controller.controller.device();
|
||||||
if data.stick == 0 {
|
if data.stick == 0 {
|
||||||
// Left stick
|
// Left stick
|
||||||
@@ -218,11 +220,11 @@ async fn command_loop(
|
|||||||
}
|
}
|
||||||
device.sync();
|
device.sync();
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("Controller slot {} not found for stick event", data.slot);
|
tracing::warn!("Controller slot {} not found for stick event", data.session_slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Payload::ControllerTrigger(data) => {
|
Payload::ControllerTrigger(data) => {
|
||||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||||
let device = controller.controller.device();
|
let device = controller.controller.device();
|
||||||
if data.trigger == 0 {
|
if data.trigger == 0 {
|
||||||
// Left trigger
|
// Left trigger
|
||||||
@@ -233,11 +235,11 @@ async fn command_loop(
|
|||||||
}
|
}
|
||||||
device.sync();
|
device.sync();
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("Controller slot {} not found for trigger event", data.slot);
|
tracing::warn!("Controller slot {} not found for trigger event", data.session_slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Payload::ControllerAxis(data) => {
|
Payload::ControllerAxis(data) => {
|
||||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||||
let device = controller.controller.device();
|
let device = controller.controller.device();
|
||||||
if data.axis == 0 {
|
if data.axis == 0 {
|
||||||
// dpad x
|
// dpad x
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use tracing_subscriber::EnvFilter;
|
|||||||
use tracing_subscriber::filter::LevelFilter;
|
use tracing_subscriber::filter::LevelFilter;
|
||||||
|
|
||||||
// Handles gathering GPU information and selecting the most suitable GPU
|
// Handles gathering GPU information and selecting the most suitable GPU
|
||||||
fn handle_gpus(args: &args::Args) -> Result<Vec<gpu::GPUInfo>, Box<dyn Error>> {
|
fn handle_gpus(args: &args::Args) -> Result<Vec<GPUInfo>, Box<dyn Error>> {
|
||||||
tracing::info!("Gathering GPU information..");
|
tracing::info!("Gathering GPU information..");
|
||||||
let mut gpus = gpu::get_gpus()?;
|
let mut gpus = gpu::get_gpus()?;
|
||||||
if gpus.is_empty() {
|
if gpus.is_empty() {
|
||||||
@@ -119,7 +119,6 @@ fn handle_encoder_video(
|
|||||||
&video_encoders,
|
&video_encoders,
|
||||||
&args.encoding.video.codec,
|
&args.encoding.video.codec,
|
||||||
&args.encoding.video.encoder_type,
|
&args.encoding.video.encoder_type,
|
||||||
args.app.zero_copy,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
tracing::info!("Selected video encoder: '{}'", video_encoder.name);
|
tracing::info!("Selected video encoder: '{}'", video_encoder.name);
|
||||||
@@ -323,7 +322,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
/* Video */
|
/* Video */
|
||||||
// Video Source Element
|
// Video Source Element
|
||||||
let video_source = Arc::new(gstreamer::ElementFactory::make("waylanddisplaysrc").build()?);
|
let video_source = Arc::new(gstreamer::ElementFactory::make("waylanddisplaysrc").build()?);
|
||||||
if let Some(gpu_info) = &video_encoder_info.gpu_info {
|
if args.app.software_render {
|
||||||
|
video_source.set_property_from_str("render-node", "software");
|
||||||
|
} else if let Some(gpu_info) = &video_encoder_info.gpu_info {
|
||||||
video_source.set_property_from_str("render-node", gpu_info.render_path());
|
video_source.set_property_from_str("render-node", gpu_info.render_path());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,20 +429,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
webrtcsink.set_property("do-retransmission", false);
|
webrtcsink.set_property("do-retransmission", false);
|
||||||
|
|
||||||
/* Queues */
|
/* Queues */
|
||||||
let video_source_queue = gstreamer::ElementFactory::make("queue")
|
|
||||||
.property("max-size-buffers", 5u32)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let audio_source_queue = gstreamer::ElementFactory::make("queue")
|
|
||||||
.property("max-size-buffers", 5u32)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let video_queue = gstreamer::ElementFactory::make("queue")
|
let video_queue = gstreamer::ElementFactory::make("queue")
|
||||||
.property("max-size-buffers", 5u32)
|
.property("max-size-buffers", 2u32)
|
||||||
|
.property("max-size-time", 0u64)
|
||||||
|
.property("max-size-bytes", 0u32)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let audio_queue = gstreamer::ElementFactory::make("queue")
|
let audio_queue = gstreamer::ElementFactory::make("queue")
|
||||||
.property("max-size-buffers", 5u32)
|
.property("max-size-buffers", 2u32)
|
||||||
|
.property("max-size-time", 0u64)
|
||||||
|
.property("max-size-bytes", 0u32)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
/* Clock Sync */
|
/* Clock Sync */
|
||||||
@@ -460,7 +457,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
&caps_filter,
|
&caps_filter,
|
||||||
&video_queue,
|
&video_queue,
|
||||||
&video_clocksync,
|
&video_clocksync,
|
||||||
&video_source_queue,
|
|
||||||
&video_source,
|
&video_source,
|
||||||
&audio_encoder,
|
&audio_encoder,
|
||||||
&audio_capsfilter,
|
&audio_capsfilter,
|
||||||
@@ -468,7 +464,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
&audio_clocksync,
|
&audio_clocksync,
|
||||||
&audio_rate,
|
&audio_rate,
|
||||||
&audio_converter,
|
&audio_converter,
|
||||||
&audio_source_queue,
|
|
||||||
&audio_source,
|
&audio_source,
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
@@ -495,7 +490,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
// Link main audio branch
|
// Link main audio branch
|
||||||
gstreamer::Element::link_many(&[
|
gstreamer::Element::link_many(&[
|
||||||
&audio_source,
|
&audio_source,
|
||||||
&audio_source_queue,
|
|
||||||
&audio_converter,
|
&audio_converter,
|
||||||
&audio_rate,
|
&audio_rate,
|
||||||
&audio_capsfilter,
|
&audio_capsfilter,
|
||||||
@@ -517,7 +511,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
if let (Some(vapostproc), Some(va_caps_filter)) = (&vapostproc, &va_caps_filter) {
|
if let (Some(vapostproc), Some(va_caps_filter)) = (&vapostproc, &va_caps_filter) {
|
||||||
gstreamer::Element::link_many(&[
|
gstreamer::Element::link_many(&[
|
||||||
&video_source,
|
&video_source,
|
||||||
&video_source_queue,
|
|
||||||
&caps_filter,
|
&caps_filter,
|
||||||
&video_queue,
|
&video_queue,
|
||||||
&video_clocksync,
|
&video_clocksync,
|
||||||
@@ -529,7 +522,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
// NVENC pipeline
|
// NVENC pipeline
|
||||||
gstreamer::Element::link_many(&[
|
gstreamer::Element::link_many(&[
|
||||||
&video_source,
|
&video_source,
|
||||||
&video_source_queue,
|
|
||||||
&caps_filter,
|
&caps_filter,
|
||||||
&video_encoder,
|
&video_encoder,
|
||||||
])?;
|
])?;
|
||||||
@@ -537,7 +529,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
} else {
|
} else {
|
||||||
gstreamer::Element::link_many(&[
|
gstreamer::Element::link_many(&[
|
||||||
&video_source,
|
&video_source,
|
||||||
&video_source_queue,
|
|
||||||
&caps_filter,
|
&caps_filter,
|
||||||
&video_queue,
|
&video_queue,
|
||||||
&video_clocksync,
|
&video_clocksync,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub struct Signaller {
|
|||||||
wayland_src: PLRwLock<Option<Arc<gstreamer::Element>>>,
|
wayland_src: PLRwLock<Option<Arc<gstreamer::Element>>>,
|
||||||
data_channel: PLRwLock<Option<Arc<gstreamer_webrtc::WebRTCDataChannel>>>,
|
data_channel: PLRwLock<Option<Arc<gstreamer_webrtc::WebRTCDataChannel>>>,
|
||||||
controller_manager: PLRwLock<Option<Arc<ControllerManager>>>,
|
controller_manager: PLRwLock<Option<Arc<ControllerManager>>>,
|
||||||
rumble_rx: Mutex<Option<mpsc::Receiver<(u32, u16, u16, u16)>>>,
|
rumble_rx: Mutex<Option<mpsc::Receiver<(u32, u16, u16, u16, String)>>>,
|
||||||
attach_rx: Mutex<Option<mpsc::Receiver<ProtoControllerAttach>>>,
|
attach_rx: Mutex<Option<mpsc::Receiver<ProtoControllerAttach>>>,
|
||||||
}
|
}
|
||||||
impl Default for Signaller {
|
impl Default for Signaller {
|
||||||
@@ -70,11 +70,11 @@ impl Signaller {
|
|||||||
self.controller_manager.read().clone()
|
self.controller_manager.read().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_rumble_rx(&self, rumble_rx: mpsc::Receiver<(u32, u16, u16, u16)>) {
|
pub async fn set_rumble_rx(&self, rumble_rx: mpsc::Receiver<(u32, u16, u16, u16, String)>) {
|
||||||
*self.rumble_rx.lock().await = Some(rumble_rx);
|
*self.rumble_rx.lock().await = Some(rumble_rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn take_rumble_rx(&self) -> Option<mpsc::Receiver<(u32, u16, u16, u16)>> {
|
pub async fn take_rumble_rx(&self) -> Option<mpsc::Receiver<(u32, u16, u16, u16, String)>> {
|
||||||
self.rumble_rx.lock().await.take()
|
self.rumble_rx.lock().await.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ impl ObjectImpl for Signaller {
|
|||||||
|
|
||||||
fn setup_data_channel(
|
fn setup_data_channel(
|
||||||
controller_manager: Option<Arc<ControllerManager>>,
|
controller_manager: Option<Arc<ControllerManager>>,
|
||||||
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16)>>, // (slot, strong, weak, duration_ms)
|
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16, String)>>, // (slot, strong, weak, duration_ms, session_id)
|
||||||
attach_rx: Option<mpsc::Receiver<ProtoControllerAttach>>,
|
attach_rx: Option<mpsc::Receiver<ProtoControllerAttach>>,
|
||||||
data_channel: Arc<gstreamer_webrtc::WebRTCDataChannel>,
|
data_channel: Arc<gstreamer_webrtc::WebRTCDataChannel>,
|
||||||
wayland_src: &gstreamer::Element,
|
wayland_src: &gstreamer::Element,
|
||||||
@@ -423,10 +423,11 @@ fn setup_data_channel(
|
|||||||
if let Some(mut rumble_rx) = rumble_rx {
|
if let Some(mut rumble_rx) = rumble_rx {
|
||||||
let data_channel_clone = data_channel.clone();
|
let data_channel_clone = data_channel.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some((slot, strong, weak, duration_ms)) = rumble_rx.recv().await {
|
while let Some((slot, strong, weak, duration_ms, session_id)) = rumble_rx.recv().await {
|
||||||
let rumble_msg = crate::proto::create_message(
|
let rumble_msg = crate::proto::create_message(
|
||||||
Payload::ControllerRumble(ProtoControllerRumble {
|
Payload::ControllerRumble(ProtoControllerRumble {
|
||||||
slot: slot as i32,
|
session_slot: slot as i32,
|
||||||
|
session_id: session_id,
|
||||||
low_frequency: weak as i32,
|
low_frequency: weak as i32,
|
||||||
high_frequency: strong as i32,
|
high_frequency: strong as i32,
|
||||||
duration: duration_ms as i32,
|
duration: duration_ms as i32,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ impl NestriSignaller {
|
|||||||
nestri_conn: NestriConnection,
|
nestri_conn: NestriConnection,
|
||||||
wayland_src: Arc<gstreamer::Element>,
|
wayland_src: Arc<gstreamer::Element>,
|
||||||
controller_manager: Option<Arc<ControllerManager>>,
|
controller_manager: Option<Arc<ControllerManager>>,
|
||||||
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16)>>,
|
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16, String)>>,
|
||||||
attach_rx: Option<mpsc::Receiver<crate::proto::proto::ProtoControllerAttach>>,
|
attach_rx: Option<mpsc::Receiver<crate::proto::proto::ProtoControllerAttach>>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let obj: Self = glib::Object::new();
|
let obj: Self = glib::Object::new();
|
||||||
|
|||||||
@@ -84,95 +84,113 @@ pub struct ProtoControllerAttach {
|
|||||||
/// One of the following enums: "ps", "xbox" or "switch"
|
/// One of the following enums: "ps", "xbox" or "switch"
|
||||||
#[prost(string, tag="1")]
|
#[prost(string, tag="1")]
|
||||||
pub id: ::prost::alloc::string::String,
|
pub id: ::prost::alloc::string::String,
|
||||||
/// Slot number (0-3)
|
/// Session specific slot number (0-3)
|
||||||
#[prost(int32, tag="2")]
|
#[prost(int32, tag="2")]
|
||||||
pub slot: i32,
|
pub session_slot: i32,
|
||||||
/// Session ID of the client attaching the controller
|
/// Session ID of the client
|
||||||
#[prost(string, tag="3")]
|
#[prost(string, tag="3")]
|
||||||
pub session_id: ::prost::alloc::string::String,
|
pub session_id: ::prost::alloc::string::String,
|
||||||
}
|
}
|
||||||
/// ControllerDetach message
|
/// ControllerDetach message
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct ProtoControllerDetach {
|
pub struct ProtoControllerDetach {
|
||||||
/// Slot number (0-3)
|
/// Session specific slot number (0-3)
|
||||||
#[prost(int32, tag="1")]
|
#[prost(int32, tag="1")]
|
||||||
pub slot: i32,
|
pub session_slot: i32,
|
||||||
|
/// Session ID of the client
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub session_id: ::prost::alloc::string::String,
|
||||||
}
|
}
|
||||||
/// ControllerButton message
|
/// ControllerButton message
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct ProtoControllerButton {
|
pub struct ProtoControllerButton {
|
||||||
/// Slot number (0-3)
|
/// Session specific slot number (0-3)
|
||||||
#[prost(int32, tag="1")]
|
#[prost(int32, tag="1")]
|
||||||
pub slot: i32,
|
pub session_slot: i32,
|
||||||
|
/// Session ID of the client
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub session_id: ::prost::alloc::string::String,
|
||||||
/// Button code (linux input event code)
|
/// Button code (linux input event code)
|
||||||
#[prost(int32, tag="2")]
|
#[prost(int32, tag="3")]
|
||||||
pub button: i32,
|
pub button: i32,
|
||||||
/// true if pressed, false if released
|
/// true if pressed, false if released
|
||||||
#[prost(bool, tag="3")]
|
#[prost(bool, tag="4")]
|
||||||
pub pressed: bool,
|
pub pressed: bool,
|
||||||
}
|
}
|
||||||
/// ControllerTriggers message
|
/// ControllerTriggers message
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct ProtoControllerTrigger {
|
pub struct ProtoControllerTrigger {
|
||||||
/// Slot number (0-3)
|
/// Session specific slot number (0-3)
|
||||||
#[prost(int32, tag="1")]
|
#[prost(int32, tag="1")]
|
||||||
pub slot: i32,
|
pub session_slot: i32,
|
||||||
|
/// Session ID of the client
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub session_id: ::prost::alloc::string::String,
|
||||||
/// Trigger number (0 for left, 1 for right)
|
/// Trigger number (0 for left, 1 for right)
|
||||||
#[prost(int32, tag="2")]
|
#[prost(int32, tag="3")]
|
||||||
pub trigger: i32,
|
pub trigger: i32,
|
||||||
/// trigger value (-32768 to 32767)
|
/// trigger value (-32768 to 32767)
|
||||||
#[prost(int32, tag="3")]
|
#[prost(int32, tag="4")]
|
||||||
pub value: i32,
|
pub value: i32,
|
||||||
}
|
}
|
||||||
/// ControllerSticks message
|
/// ControllerSticks message
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct ProtoControllerStick {
|
pub struct ProtoControllerStick {
|
||||||
/// Slot number (0-3)
|
/// Session specific slot number (0-3)
|
||||||
#[prost(int32, tag="1")]
|
#[prost(int32, tag="1")]
|
||||||
pub slot: i32,
|
pub session_slot: i32,
|
||||||
|
/// Session ID of the client
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub session_id: ::prost::alloc::string::String,
|
||||||
/// Stick number (0 for left, 1 for right)
|
/// Stick number (0 for left, 1 for right)
|
||||||
#[prost(int32, tag="2")]
|
#[prost(int32, tag="3")]
|
||||||
pub stick: i32,
|
pub stick: i32,
|
||||||
/// X axis value (-32768 to 32767)
|
/// X axis value (-32768 to 32767)
|
||||||
#[prost(int32, tag="3")]
|
#[prost(int32, tag="4")]
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
/// Y axis value (-32768 to 32767)
|
/// Y axis value (-32768 to 32767)
|
||||||
#[prost(int32, tag="4")]
|
#[prost(int32, tag="5")]
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
/// ControllerAxis message
|
/// ControllerAxis message
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct ProtoControllerAxis {
|
pub struct ProtoControllerAxis {
|
||||||
/// Slot number (0-3)
|
/// Session specific slot number (0-3)
|
||||||
#[prost(int32, tag="1")]
|
#[prost(int32, tag="1")]
|
||||||
pub slot: i32,
|
pub session_slot: i32,
|
||||||
|
/// Session ID of the client
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub session_id: ::prost::alloc::string::String,
|
||||||
/// Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
/// Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||||
#[prost(int32, tag="2")]
|
#[prost(int32, tag="3")]
|
||||||
pub axis: i32,
|
pub axis: i32,
|
||||||
/// axis value (-1 to 1)
|
/// axis value (-1 to 1)
|
||||||
#[prost(int32, tag="3")]
|
#[prost(int32, tag="4")]
|
||||||
pub value: i32,
|
pub value: i32,
|
||||||
}
|
}
|
||||||
/// ControllerRumble message
|
/// ControllerRumble message
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct ProtoControllerRumble {
|
pub struct ProtoControllerRumble {
|
||||||
/// Slot number (0-3)
|
/// Session specific slot number (0-3)
|
||||||
#[prost(int32, tag="1")]
|
#[prost(int32, tag="1")]
|
||||||
pub slot: i32,
|
pub session_slot: i32,
|
||||||
|
/// Session ID of the client
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub session_id: ::prost::alloc::string::String,
|
||||||
/// Low frequency rumble (0-65535)
|
/// Low frequency rumble (0-65535)
|
||||||
#[prost(int32, tag="2")]
|
#[prost(int32, tag="3")]
|
||||||
pub low_frequency: i32,
|
pub low_frequency: i32,
|
||||||
/// High frequency rumble (0-65535)
|
/// High frequency rumble (0-65535)
|
||||||
#[prost(int32, tag="3")]
|
#[prost(int32, tag="4")]
|
||||||
pub high_frequency: i32,
|
pub high_frequency: i32,
|
||||||
/// Duration in milliseconds
|
/// Duration in milliseconds
|
||||||
#[prost(int32, tag="4")]
|
#[prost(int32, tag="5")]
|
||||||
pub duration: i32,
|
pub duration: i32,
|
||||||
}
|
}
|
||||||
// WebRTC + signaling
|
// WebRTC + signaling
|
||||||
|
|||||||
@@ -51,50 +51,56 @@ message ProtoKeyUp {
|
|||||||
// ControllerAttach message
|
// ControllerAttach message
|
||||||
message ProtoControllerAttach {
|
message ProtoControllerAttach {
|
||||||
string id = 1; // One of the following enums: "ps", "xbox" or "switch"
|
string id = 1; // One of the following enums: "ps", "xbox" or "switch"
|
||||||
int32 slot = 2; // Slot number (0-3)
|
int32 session_slot = 2; // Session specific slot number (0-3)
|
||||||
string session_id = 3; // Session ID of the client attaching the controller
|
string session_id = 3; // Session ID of the client
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerDetach message
|
// ControllerDetach message
|
||||||
message ProtoControllerDetach {
|
message ProtoControllerDetach {
|
||||||
int32 slot = 1; // Slot number (0-3)
|
int32 session_slot = 1; // Session specific slot number (0-3)
|
||||||
|
string session_id = 2; // Session ID of the client
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerButton message
|
// ControllerButton message
|
||||||
message ProtoControllerButton {
|
message ProtoControllerButton {
|
||||||
int32 slot = 1; // Slot number (0-3)
|
int32 session_slot = 1; // Session specific slot number (0-3)
|
||||||
int32 button = 2; // Button code (linux input event code)
|
string session_id = 2; // Session ID of the client
|
||||||
bool pressed = 3; // true if pressed, false if released
|
int32 button = 3; // Button code (linux input event code)
|
||||||
|
bool pressed = 4; // true if pressed, false if released
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerTriggers message
|
// ControllerTriggers message
|
||||||
message ProtoControllerTrigger {
|
message ProtoControllerTrigger {
|
||||||
int32 slot = 1; // Slot number (0-3)
|
int32 session_slot = 1; // Session specific slot number (0-3)
|
||||||
int32 trigger = 2; // Trigger number (0 for left, 1 for right)
|
string session_id = 2; // Session ID of the client
|
||||||
int32 value = 3; // trigger value (-32768 to 32767)
|
int32 trigger = 3; // Trigger number (0 for left, 1 for right)
|
||||||
|
int32 value = 4; // trigger value (-32768 to 32767)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerSticks message
|
// ControllerSticks message
|
||||||
message ProtoControllerStick {
|
message ProtoControllerStick {
|
||||||
int32 slot = 1; // Slot number (0-3)
|
int32 session_slot = 1; // Session specific slot number (0-3)
|
||||||
int32 stick = 2; // Stick number (0 for left, 1 for right)
|
string session_id = 2; // Session ID of the client
|
||||||
int32 x = 3; // X axis value (-32768 to 32767)
|
int32 stick = 3; // Stick number (0 for left, 1 for right)
|
||||||
int32 y = 4; // Y axis value (-32768 to 32767)
|
int32 x = 4; // X axis value (-32768 to 32767)
|
||||||
|
int32 y = 5; // Y axis value (-32768 to 32767)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerAxis message
|
// ControllerAxis message
|
||||||
message ProtoControllerAxis {
|
message ProtoControllerAxis {
|
||||||
int32 slot = 1; // Slot number (0-3)
|
int32 session_slot = 1; // Session specific slot number (0-3)
|
||||||
int32 axis = 2; // Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
string session_id = 2; // Session ID of the client
|
||||||
int32 value = 3; // axis value (-1 to 1)
|
int32 axis = 3; // Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||||
|
int32 value = 4; // axis value (-1 to 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerRumble message
|
// ControllerRumble message
|
||||||
message ProtoControllerRumble {
|
message ProtoControllerRumble {
|
||||||
int32 slot = 1; // Slot number (0-3)
|
int32 session_slot = 1; // Session specific slot number (0-3)
|
||||||
int32 low_frequency = 2; // Low frequency rumble (0-65535)
|
string session_id = 2; // Session ID of the client
|
||||||
int32 high_frequency = 3; // High frequency rumble (0-65535)
|
int32 low_frequency = 3; // Low frequency rumble (0-65535)
|
||||||
int32 duration = 4; // Duration in milliseconds
|
int32 high_frequency = 4; // High frequency rumble (0-65535)
|
||||||
|
int32 duration = 5; // Duration in milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
/* WebRTC + signaling */
|
/* WebRTC + signaling */
|
||||||
|
|||||||
Reference in New Issue
Block a user