secutorpro commited on
Commit
0342026
·
verified ·
1 Parent(s): ac32264

reconnecting websocket client with binary PCM audio handling

Browse files
Files changed (1) hide show
  1. web/src/ws.js +80 -0
web/src/ws.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getToken } from './api.js';
2
+
3
+ const AUDIO_TYPE = 0x01;
4
+ const HDR = 9; // [1B type][4B groupId][4B memberId]
5
+
6
+ export class DispatchSocket {
7
+ constructor(handlers) {
8
+ this.handlers = handlers;
9
+ this.closed = false;
10
+ this.retries = 0;
11
+ this.connect();
12
+ }
13
+
14
+ connect() {
15
+ const proto = location.protocol === 'https:' ? 'wss' : 'ws';
16
+ this.ws = new WebSocket(`${proto}://${location.host}/ws`);
17
+ this.ws.binaryType = 'arraybuffer';
18
+
19
+ this.ws.onopen = () => {
20
+ this.retries = 0;
21
+ this.ws.send(JSON.stringify({ type: 'auth', token: getToken() }));
22
+ this.handlers.onStatus?.(true);
23
+ };
24
+
25
+ this.ws.onmessage = (ev) => {
26
+ if (typeof ev.data === 'string') {
27
+ let msg;
28
+ try { msg = JSON.parse(ev.data); } catch { return; }
29
+ switch (msg.type) {
30
+ case 'welcome': this.handlers.onWelcome?.(msg); break;
31
+ case 'members': this.handlers.onMembers?.(msg.members); break;
32
+ case 'groups': this.handlers.onGroups?.(msg.groups); break;
33
+ case 'sos_new': this.handlers.onSosNew?.(msg.alert); break;
34
+ case 'sos_update': this.handlers.onSosUpdate?.(msg.alert); break;
35
+ case 'floor': this.handlers.onFloor?.(msg); break;
36
+ case 'error': console.warn('[ws]', msg.message); break;
37
+ }
38
+ } else if (ev.data instanceof ArrayBuffer && ev.data.byteLength > HDR) {
39
+ const dv = new DataView(ev.data);
40
+ if (dv.getUint8(0) !== AUDIO_TYPE) return;
41
+ this.handlers.onAudio?.({
42
+ groupId: dv.getUint32(1),
43
+ memberId: dv.getUint32(5),
44
+ pcm: ev.data.slice(HDR),
45
+ });
46
+ }
47
+ };
48
+
49
+ this.ws.onclose = () => {
50
+ this.handlers.onStatus?.(false);
51
+ if (this.closed) return;
52
+ const delay = Math.min(10000, 500 * 2 ** this.retries++);
53
+ setTimeout(() => this.connect(), delay);
54
+ };
55
+ this.ws.onerror = () => this.ws.close();
56
+ }
57
+
58
+ send(obj) {
59
+ if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(obj));
60
+ }
61
+
62
+ sendAudio(groupId, memberId, int16Buffer) {
63
+ if (this.ws?.readyState !== WebSocket.OPEN) return;
64
+ const head = new Uint8Array(HDR);
65
+ const dv = new DataView(head.buffer);
66
+ dv.setUint8(0, AUDIO_TYPE);
67
+ dv.setUint32(1, groupId);
68
+ dv.setUint32(5, memberId);
69
+ const body = new Uint8Array(int16Buffer);
70
+ const out = new Uint8Array(HDR + body.byteLength);
71
+ out.set(head);
72
+ out.set(body, HDR);
73
+ this.ws.send(out);
74
+ }
75
+
76
+ close() {
77
+ this.closed = true;
78
+ this.ws?.close();
79
+ }
80
+ }