diff --git a/public/call.html b/public/call.html
index d730a52..98fac1c 100644
--- a/public/call.html
+++ b/public/call.html
@@ -101,8 +101,25 @@
// connect to websocket
callWebsocket = new WebSocket(location.origin.replace(/^http/, 'ws') + "/call/initiate/" + destinationHash);
- callWebsocket.onopen = () => {
- console.log("connected to websocket");
+ callWebsocket.onmessage = async function(event) {
+
+ // get encoded codec2 bytes from websocket message
+ const encoded = await event.data.arrayBuffer();
+
+ // decode codec2 audio
+ const decoded = await Codec2Lib.runDecode(codecModeElement.value, encoded);
+
+ // convert decoded codec2 to wav audio
+ const wavAudio = await Codec2Lib.rawToWav(decoded);
+
+ // play wav audio buffer
+ let audioCtx = new AudioContext()
+ const audioBuffer = await audioCtx.decodeAudioData(wavAudio.buffer);
+ const sampleSource = audioCtx.createBufferSource();
+ sampleSource.buffer = audioBuffer;
+ sampleSource.connect(audioCtx.destination)
+ sampleSource.start(0);
+
};
// record mic to send to websocket
@@ -187,6 +204,28 @@
};
+ // record mic to send to websocket
+ await startRecordingMicrophone((encoded) => {
+
+ // do nothing if websocket closed
+ if(listenWebsocket.readyState !== WebSocket.OPEN){
+ return;
+ }
+
+ // do nothing when audio muted
+ if(checkboxMuteMicElement.checked){
+ return;
+ }
+
+ // send encoded audio to websocket
+ listenWebsocket.send(encoded);
+
+ // update stats
+ encodedBytesSent += encoded.length;
+ encodedBytesSentElement.innerText = formatBytes(encodedBytesSent);
+
+ });
+
}
function stopListening() {
diff --git a/web.py b/web.py
index 05f6f2a..912edda 100644
--- a/web.py
+++ b/web.py
@@ -84,6 +84,8 @@ class ReticulumWebChat:
# remember websocket clients
self.websocket_clients: List[web.WebSocketResponse] = []
+ self.link_call_audio = None
+
# web server has shutdown, likely ctrl+c, but if we don't do the following, the script never exits
async def shutdown(self, app):
@@ -193,8 +195,9 @@ class ReticulumWebChat:
# todo implement
def client_packet_received(message, packet):
- # todo, we don't send anything from the call initiator from the call receiver yet...
- pass
+
+ # send audio received from call receiver to call initiator websocket
+ asyncio.run(websocket_response.send_bytes(message))
# create link
link = RNS.Link(server_destination)
@@ -250,12 +253,14 @@ class ReticulumWebChat:
# client connected to us
def client_connected(link):
print("client connected")
+ self.link_call_audio = link
link.set_link_closed_callback(client_disconnected)
link.set_packet_callback(server_packet_received)
# client disconnected from us
def client_disconnected(link):
print("client disconnected")
+ self.link_call_audio = None
# client sent us a packet
def server_packet_received(message, packet):
@@ -275,7 +280,26 @@ class ReticulumWebChat:
# handle websocket messages until disconnected
async for msg in websocket_response:
msg: WSMessage = msg
- if msg.type == WSMsgType.ERROR:
+ if msg.type == WSMsgType.BINARY:
+ try:
+
+ # drop audio packet if it is too big to send
+ if len(msg.data) > RNS.Link.MDU:
+ print("dropping packet " + str(len(msg.data)) + " bytes exceeds the link packet MDU of " + str(RNS.Link.MDU) + " bytes")
+ continue
+
+ # send codec2 audio received from call receiver on websocket, to call initiator over reticulum link
+ if self.link_call_audio is not None:
+ print("sending bytes to call initiator: {}".format(len(msg.data)))
+ RNS.Packet(self.link_call_audio, msg.data).send()
+ else:
+ print("link to call initiator not available")
+
+ except Exception as e:
+ # ignore errors while handling message
+ print("failed to process client message")
+ print(e)
+ elif msg.type == WSMsgType.ERROR:
# ignore errors while handling message
print('ws connection error %s' % websocket_response.exception())