diff --git a/web/js/app.js b/web/js/app.js index cbb41745..1da1cdad 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -154,11 +154,6 @@ const startGame = () => { return; } - if (!webrtc.isInputReady()) { - message.show('Game is not ready yet. Please wait'); - return; - } - log.info('[control] game start'); setState(app.state.game); @@ -493,15 +488,22 @@ sub(GAME_PLAYER_IDX_SET, idx => { sub(GAME_ERROR_NO_FREE_SLOTS, () => message.show("No free slots :(", 2500)); sub(WEBRTC_NEW_CONNECTION, (data) => { workerManager.whoami(data.wid); - webrtc.onData = (x) => onMessage(api.decode(x.data)) webrtc.start(data.ice); + webrtc.modDataChannel = (ch) => { + ch.binaryType = "arraybuffer"; + if (ch.label === "data") { + ch.onmessage = (x) => onMessage(api.decode(x.data)); + pub(WEBRTC_CONNECTION_READY); + } + return ch + } 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), stream.video.el)); -sub(WEBRTC_ICE_CANDIDATE_RECEIVED, (data) => { +sub(WEBRTC_ICE_CANDIDATE_RECEIVED, (data) => { const candidate = data.candidate ? api.decodeB64(data.candidate) : '' webrtc.addCandidate(candidate) }); @@ -528,7 +530,7 @@ sub(KEY_RELEASED, onKeyRelease); sub(SETTINGS_CHANGED, () => message.show('Settings have been updated')); sub(AXIS_CHANGED, onAxisChanged); -sub(CONTROLLER_UPDATED, data => webrtc.input(data)); +sub(CONTROLLER_UPDATED, data => webrtc.send("data", data)); sub(RECORDING_TOGGLED, handleRecording); sub(RECORDING_STATUS_CHANGED, handleRecordingStatus); @@ -552,8 +554,8 @@ const wid = new URLSearchParams(document.location.search).get('wid'); socket.init(roomId, wid, zone); api.transport = { send: socket.send, - keyboard: webrtc.keyboard, - mouse: webrtc.mouse, + keyboard: (data) => webrtc.send("keyboard", data), + mouse: (data) => webrtc.send("mouse", data), } // stats diff --git a/web/js/network/webrtc.js b/web/js/network/webrtc.js index aa4f94c2..1b2e5d35 100644 --- a/web/js/network/webrtc.js +++ b/web/js/network/webrtc.js @@ -15,9 +15,8 @@ let /** @type {RTCLocalIceCandidateInit[]} */ candidates = []; let isAnswered = false; let isFlushing = false; let connected = false; -let inputReady = false; -let /** @type {(MessageEvent) => void} */ onData; +let /** @type {(channel: RTCDataChannel) => RTCDataChannel} */ modDataChannel; const ice = ((timeout = 3000) => { let timeoutId; @@ -93,6 +92,12 @@ const ice = ((timeout = 3000) => { }; })(); +// readyChan - wraps an RTCDataChannel to ensure it is ready before sending +const readyChan = (/** @type {RTCDataChannel} */ dc) => ({ + send: (/** @type {string} */ data) => + dc.readyState === "open" && dc.send(data), +}); + const flushCandidates = () => { if (isFlushing || !isAnswered) return; isFlushing = true; @@ -120,26 +125,9 @@ export const webrtc = { mediaStream = new MediaStream(); pc.ondatachannel = (ev) => { - const chan = ev.channel; - chan.binaryType = "arraybuffer"; + let chan = modDataChannel ? modDataChannel(ev.channel) : ev.channel; + channels.set(chan.label, readyChan(chan)); log.debug(`[rtc] [data-ch] push: ${chan.label}`); - - channels.set(chan.label, chan); - - if (chan.label === "data") { - chan.onopen = () => { - log.debug("[rtc] [data-ch] input channel has been opened"); - inputReady = true; - pub(WEBRTC_CONNECTION_READY); - }; - if (onData) { - chan.onmessage = onData; - } - chan.onclose = () => { - inputReady = false; - log.debug("[rtc] [data-ch] input channel has been closed"); - }; - } }; pc.oniceconnectionstatechange = ice.onIceConnectionStateChange; pc.onicegatheringstatechange = ice.onIceGatheringStateChange; @@ -165,9 +153,7 @@ export const webrtc = { log.error(`[rtc] [sdp] remote offer error: ${e}`); } - log.debug( - `[rtc] [sdp] remote Trickle ICE support: ${pc.canTrickleIceCandidates}`, - ); + log.debug(`[rtc] [sdp] Trickle ICE: ${pc.canTrickleIceCandidates}`); try { const answer = await pc.createAnswer(); @@ -193,11 +179,8 @@ export const webrtc = { } candidates.push(candidate); }, - keyboard: (data) => channels.get("keyboard")?.send(data), - mouse: (data) => channels.get("mouse")?.send(data), - input: (data) => inputReady && channels.get("data")?.send(data), + send: (chan, data) => channels.get(chan)?.send(data), isConnected: () => connected, - isInputReady: () => inputReady, stats: async () => { if (!connected) return Promise.resolve(); return await pc.getStats(); @@ -223,7 +206,7 @@ export const webrtc = { candidates = []; log.info("[rtc] WebRTC has been closed"); }, - set onData(/** @type {(MessageEvent) => void} */ fn) { - onData = fn; + set modDataChannel(fn) { + modDataChannel = fn; }, };