Clean WebRTC export

This commit is contained in:
sergystepanov 2026-06-03 15:33:55 +03:00
parent fd596fa85f
commit df8d35f542
3 changed files with 29 additions and 31 deletions

17
web/js/api.js Normal file → Executable file
View file

@ -52,8 +52,6 @@ const packet = (type, payload, id) => {
transport.send(packet);
};
const decodeBytes = (b) => String.fromCharCode.apply(null, new Uint8Array(b));
const keyboardPress = (() => {
// 0 1 2 3 4 5 6
// [CODE ] P MOD
@ -319,6 +317,12 @@ const libretro = (function () {
};
})();
// data type converters
const toBase64 = (val) => btoa(JSON.stringify(val));
const fromBase64 = (val) => JSON.parse(atob(val));
const fromBytes = (val) =>
JSON.parse(String.fromCharCode.apply(null, new Uint8Array(val)));
/**
* Server API.
*
@ -330,13 +334,14 @@ export const api = {
},
endpoint: endpoints,
endpointName: endpointName,
decode: (b) => JSON.parse(decodeBytes(b)),
decodeB64: (b64) => JSON.parse(atob(b64)),
fromBytes,
fromBase64,
server: {
/** @deprecated use initWebrtcStream instead */
initWebrtc: () => packet(endpoints.INIT_WEBRTC),
sendIceCandidate: (candidate) =>
packet(endpoints.ICE_CANDIDATE, btoa(JSON.stringify(candidate))),
sendSdp: (sdp) => packet(endpoints.ANSWER, btoa(JSON.stringify(sdp))),
packet(endpoints.ICE_CANDIDATE, toBase64(candidate)),
sendSdp: (sdp) => packet(endpoints.ANSWER, toBase64(sdp)),
latencyCheck: (id, list) => packet(endpoints.LATENCY_CHECK, list, id),
getWorkerList: () => packet(endpoints.GET_WORKER_LIST),
},

39
web/js/app.js Normal file → Executable file
View file

@ -186,10 +186,13 @@ const onMessage = (m) => {
pub(WEBRTC_NEW_CONNECTION, payload);
break;
case api.endpoint.OFFER:
pub(WEBRTC_SDP_OFFER, { sdp: payload });
pub(WEBRTC_SDP_OFFER, api.fromBase64(payload));
break;
case api.endpoint.ICE_CANDIDATE:
pub(WEBRTC_ICE_CANDIDATE_RECEIVED, { candidate: payload });
pub(
WEBRTC_ICE_CANDIDATE_RECEIVED,
payload ? api.fromBase64(payload) : "",
);
break;
case api.endpoint.GAME_START:
payload.av && pub(APP_VIDEO_CHANGED, payload.av);
@ -270,7 +273,7 @@ const onKeyRelease = (data) => {
const updatePlayerIndex = (idx, not_game = false) => {
playerIndex.value = idx + 1;
!not_game && api.game.setPlayerIndex(idx);
if (!not_game) api.game.setPlayerIndex(idx);
};
// noop function for the state
@ -292,7 +295,7 @@ const onAxisChanged = (data) => {
const handleToggle = (force = false) => {
const toggle = document.getElementById("dpad-toggle");
force && toggle.setAttribute("checked", "");
if (force) toggle.setAttribute("checked", "");
toggle.checked = !toggle.checked;
pub(DPAD_TOGGLE, { checked: toggle.checked });
};
@ -483,13 +486,7 @@ document.onfullscreenchange = () =>
// subscriptions
sub(MESSAGE, onMessage);
sub(
GAME_ROOM_AVAILABLE,
async () => {
stream.play();
},
2,
);
sub(GAME_ROOM_AVAILABLE, stream.play, 2);
sub(GAME_SAVED, () => message.show("Saved"));
sub(GAME_PLAYER_IDX, (data) => {
updatePlayerIndex(+data.index, state !== app.state.game);
@ -498,13 +495,15 @@ sub(GAME_PLAYER_IDX_SET, (idx) => {
if (!isNaN(+idx)) message.show(+idx + 1);
});
sub(GAME_ERROR_NO_FREE_SLOTS, () => message.show("No free slots :(", 2500));
// WebRTC connection handling
sub(WEBRTC_NEW_CONNECTION, (data) => {
workerManager.whoami(data.wid);
webrtc.start(data.ice, stream.video.el);
webrtc.modDataChannel = (ch) => {
ch.binaryType = "arraybuffer";
if (ch.label === "data") {
ch.onmessage = (x) => onMessage(api.decode(x.data));
ch.onmessage = (x) => onMessage(api.fromBytes(x.data));
pub(WEBRTC_CONNECTION_READY);
}
return ch;
@ -512,22 +511,16 @@ sub(WEBRTC_NEW_CONNECTION, (data) => {
api.server.initWebrtc();
gameList.set(data.games);
});
sub(WEBRTC_ICE_CANDIDATE_FOUND, (data) =>
api.server.sendIceCandidate(data.candidate),
);
sub(WEBRTC_SDP_ANSWER, (data) => api.server.sendSdp(data.sdp));
sub(WEBRTC_SDP_OFFER, (data) =>
webrtc.setRemoteDescription(api.decodeB64(data.sdp)),
);
sub(WEBRTC_ICE_CANDIDATE_RECEIVED, (data) => {
const candidate = data.candidate ? api.decodeB64(data.candidate) : "";
webrtc.addCandidate(candidate);
});
sub(WEBRTC_SDP_OFFER, webrtc.setRemoteDescription);
sub(WEBRTC_SDP_ANSWER, api.server.sendSdp);
sub(WEBRTC_ICE_CANDIDATE_FOUND, api.server.sendIceCandidate);
sub(WEBRTC_ICE_CANDIDATE_RECEIVED, webrtc.addCandidate);
sub(WEBRTC_CONNECTION_READY, onConnectionReady);
sub(WEBRTC_CONNECTION_CLOSED, () => {
input.retropad.toggle(false);
webrtc.stop();
});
sub(LATENCY_CHECK_REQUESTED, onLatencyCheck);
sub(GAMEPAD_CONNECTED, () => message.show("Gamepad connected"));
sub(GAMEPAD_DISCONNECTED, () => message.show("Gamepad disconnected"));

View file

@ -27,7 +27,7 @@ const ice = ((timeout = 3000) => {
const onIceCandidate = (/** @type {RTCPeerConnectionIceEvent} */ ev) => {
if (!ev.candidate) return;
log.debug(`[rtc] [ice] local: ${ev.candidate.candidate}`);
pub(WEBRTC_ICE_CANDIDATE_FOUND, { candidate: ev.candidate });
pub(WEBRTC_ICE_CANDIDATE_FOUND, ev.candidate);
};
const onIceCandidateError = (
@ -176,7 +176,7 @@ export const webrtc = {
answer.sdp = answer.sdp.replace(/(a=fmtp:111 .*)/g, "$1;stereo=1");
await pc.setLocalDescription(answer);
log.debug("[rtc] [sdp] local answer", answer);
pub(WEBRTC_SDP_ANSWER, { sdp: answer });
pub(WEBRTC_SDP_ANSWER, answer);
} catch (e) {
log.error(`[rtc] [sdp] local answer error: ${e}`);
}