@4players/odin-plugin-web 1.0.0-alpha.13 → 1.0.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/lib/common/msgpack.d.ts +2 -0
  2. package/lib/common/msgpack.js +117 -0
  3. package/lib/plugin/config.d.ts +5 -3
  4. package/lib/plugin/config.js +15 -3
  5. package/lib/plugin/connectionPool.d.ts +3 -2
  6. package/lib/plugin/connectionPool.js +31 -13
  7. package/lib/plugin/deviceManager.d.ts +12 -0
  8. package/lib/plugin/deviceManager.js +120 -0
  9. package/lib/plugin/h3/mainConnection.d.ts +40 -0
  10. package/lib/plugin/h3/mainConnection.js +197 -0
  11. package/lib/plugin/index.d.ts +17 -6
  12. package/lib/plugin/index.js +158 -56
  13. package/lib/plugin/mainConnection.d.ts +23 -30
  14. package/lib/plugin/mainConnection.js +20 -291
  15. package/lib/plugin/{audioController.d.ts → mediaController.d.ts} +6 -46
  16. package/lib/plugin/mediaController.js +215 -0
  17. package/lib/plugin/medias/audio-capture.d.ts +29 -0
  18. package/lib/plugin/medias/audio-capture.js +203 -0
  19. package/lib/plugin/medias/audio-playback.d.ts +13 -0
  20. package/lib/plugin/medias/audio-playback.js +24 -0
  21. package/lib/plugin/medias/video-capture.d.ts +15 -0
  22. package/lib/plugin/medias/video-capture.js +70 -0
  23. package/lib/plugin/medias/video-playback.d.ts +13 -0
  24. package/lib/plugin/medias/video-playback.js +20 -0
  25. package/lib/plugin/roomConnection.d.ts +5 -2
  26. package/lib/plugin/roomConnection.js +54 -31
  27. package/lib/plugin/rpcConnection.d.ts +13 -7
  28. package/lib/plugin/rpcConnection.js +103 -33
  29. package/lib/plugin/token.d.ts +0 -2
  30. package/lib/plugin/token.js +0 -11
  31. package/lib/plugin/webrtc/mainConnection.d.ts +52 -0
  32. package/lib/plugin/webrtc/mainConnection.js +455 -0
  33. package/lib/plugin/{websocket.js → webrtc/websocket.js} +2 -2
  34. package/lib/version.d.ts +1 -0
  35. package/lib/version.js +2 -0
  36. package/lib/worker/index.js +1 -1
  37. package/package.json +18 -11
  38. package/lib/plugin/audioController.js +0 -513
  39. /package/lib/plugin/{websocket.d.ts → webrtc/websocket.d.ts} +0 -0
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  import { failure, success, msgpackEncode, msgpackDecode, MessagePackRpcSchema, } from '@4players/odin-common';
12
+ import { getByteSize } from '../common/msgpack';
12
13
  export class RpcConnection {
13
14
  get closed() {
14
15
  return this._Close.signal.aborted;
@@ -16,17 +17,24 @@ export class RpcConnection {
16
17
  get closeSignal() {
17
18
  return this._Close.signal;
18
19
  }
19
- constructor(webSocket) {
20
- this.webSocket = webSocket;
20
+ constructor(stream) {
21
+ this.stream = stream;
21
22
  this._SendQueue = [];
22
23
  this._RecvQueue = [];
24
+ this._EventQueue = [];
23
25
  this._Requests = new Map();
24
26
  this._Close = new AbortController();
25
27
  this._NextId = 1;
26
- this.webSocket.addEventListener('open', this._OnWebSocketOpen.bind(this));
27
- this.webSocket.addEventListener('close', this.close.bind(this));
28
- this.webSocket.addEventListener('message', this._OnWebSocketMessage.bind(this));
29
- this.webSocket.binaryType = 'arraybuffer';
28
+ if (stream instanceof WebSocket) {
29
+ stream.addEventListener('open', this._OnWebSocketOpen.bind(this));
30
+ stream.addEventListener('close', this.close.bind(this));
31
+ stream.addEventListener('message', (event) => this._OnRpcMessage(event.data));
32
+ stream.binaryType = 'arraybuffer';
33
+ }
34
+ else {
35
+ readRpcFromStream(stream.readable.getReader(), this._OnRpcMessage.bind(this)).finally(this.close.bind(this));
36
+ this._Writer = stream.writable.getWriter();
37
+ }
30
38
  this._Close.signal.addEventListener('abort', this._Cleanup.bind(this), {
31
39
  once: true,
32
40
  });
@@ -48,16 +56,20 @@ export class RpcConnection {
48
56
  });
49
57
  }
50
58
  notify(name, properties) {
51
- if (this.closed === false) {
59
+ if (!this.closed) {
52
60
  this._Send([2, name, properties]);
53
61
  }
54
62
  }
55
63
  recv() {
56
- if (this.closed === false) {
57
- return new Promise((accept) => this._RecvQueue.push(accept));
64
+ if (this.closed) {
65
+ return Promise.resolve('closed');
66
+ }
67
+ const event = this._EventQueue.shift();
68
+ if (event !== undefined) {
69
+ return Promise.resolve(event);
58
70
  }
59
71
  else {
60
- return Promise.resolve('closed');
72
+ return new Promise((accept) => this._RecvQueue.push(accept));
61
73
  }
62
74
  }
63
75
  close() {
@@ -72,18 +84,26 @@ export class RpcConnection {
72
84
  request.accept('timeout');
73
85
  }
74
86
  _Send(message) {
75
- this.webSocket.send(msgpackEncode(message));
87
+ const encoded = message instanceof Uint8Array
88
+ ? message
89
+ : new Uint8Array(msgpackEncode(message));
90
+ if (this.stream instanceof WebSocket) {
91
+ this.stream.send(encoded);
92
+ }
93
+ else {
94
+ this._Writer.write(encoded);
95
+ }
76
96
  }
77
97
  _OnWebSocketOpen(ev) {
78
- for (const data in this._SendQueue) {
79
- this.webSocket.send(data);
98
+ for (const data of this._SendQueue) {
99
+ this._Send(data);
80
100
  }
81
101
  this._SendQueue.length = 0;
82
102
  }
83
- _OnWebSocketMessage(ev) {
84
- const rpc = parseRpc(ev.data);
103
+ _OnRpcMessage(bytes) {
104
+ const rpc = parseRpc(bytes);
85
105
  if (rpc.type !== 'Success') {
86
- this.webSocket.close();
106
+ this.close();
87
107
  return;
88
108
  }
89
109
  switch (rpc.value[0]) {
@@ -106,14 +126,16 @@ export class RpcConnection {
106
126
  request.accept(error === null ? success(result) : failure(error));
107
127
  }
108
128
  _OnEvent(name, properties) {
109
- console.log('event', name, properties);
110
129
  const receiver = this._RecvQueue.shift();
111
130
  if (receiver !== undefined) {
112
131
  receiver({ name, properties });
113
132
  }
133
+ else {
134
+ this._EventQueue.push({ name, properties });
135
+ }
114
136
  }
115
137
  _Cleanup() {
116
- console.log(`cleanup ${this.webSocket.url}`);
138
+ var _a;
117
139
  for (const request of this._Requests.values()) {
118
140
  clearTimeout(request.timeoutId);
119
141
  request.accept('closed');
@@ -124,24 +146,34 @@ export class RpcConnection {
124
146
  }
125
147
  this._RecvQueue.length = 0;
126
148
  this._SendQueue.length = 0;
127
- this.webSocket.close();
149
+ if (this.stream instanceof WebSocket) {
150
+ this.stream.close();
151
+ }
152
+ else {
153
+ (_a = this._Writer) === null || _a === void 0 ? void 0 : _a.close().catch(() => {
154
+ /* drop silently */
155
+ });
156
+ }
128
157
  }
129
158
  }
130
- export class TypedRpcConnection extends RpcConnection {
131
- constructor(webSocket, _Commands, _Events) {
132
- super(webSocket);
159
+ export class TypedRpcConnection {
160
+ get closed() {
161
+ return this.rpcConnection.closed;
162
+ }
163
+ get closeSignal() {
164
+ return this.rpcConnection.closeSignal;
165
+ }
166
+ constructor(rpcConnection, _Commands, _Events) {
167
+ this.rpcConnection = rpcConnection;
133
168
  this._Commands = _Commands;
134
169
  this._Events = _Events;
135
170
  }
136
171
  request(name, properties, timeout) {
137
- const _super = Object.create(null, {
138
- request: { get: () => super.request }
139
- });
140
172
  return __awaiter(this, void 0, void 0, function* () {
141
173
  properties = this._Commands[name].request.parse(properties);
142
174
  const response = yield (timeout !== undefined
143
- ? _super.request.call(this, name, properties, timeout)
144
- : _super.request.call(this, name, properties));
175
+ ? this.rpcConnection.request(name, properties, timeout)
176
+ : this.rpcConnection.request(name, properties));
145
177
  if (response === 'closed' ||
146
178
  response === 'timeout' ||
147
179
  response.type === 'Failure') {
@@ -159,14 +191,11 @@ export class TypedRpcConnection extends RpcConnection {
159
191
  }
160
192
  notify(name, properties) {
161
193
  properties = this._Commands[name].request.parse(properties);
162
- super.notify(name, properties);
194
+ this.rpcConnection.notify(name, properties);
163
195
  }
164
196
  recv() {
165
- const _super = Object.create(null, {
166
- recv: { get: () => super.recv }
167
- });
168
197
  return __awaiter(this, void 0, void 0, function* () {
169
- const event = yield _super.recv.call(this);
198
+ const event = yield this.rpcConnection.recv();
170
199
  if (event === 'closed')
171
200
  return event;
172
201
  const parsed = parseRpcMessage(this._Events, event);
@@ -178,11 +207,14 @@ export class TypedRpcConnection extends RpcConnection {
178
207
  }
179
208
  else {
180
209
  console.error(`schema validation for ${event.name} failed; closing connection`);
181
- this.close();
210
+ this.rpcConnection.close();
182
211
  }
183
212
  return 'closed';
184
213
  });
185
214
  }
215
+ close() {
216
+ this.rpcConnection.close();
217
+ }
186
218
  }
187
219
  export function parseRpc(buffer) {
188
220
  try {
@@ -207,3 +239,41 @@ export function parseRpcMessage(events, rpc) {
207
239
  export function isKnownRpcMessage(events, name) {
208
240
  return name in events;
209
241
  }
242
+ function readRpcFromStream(reader, onRpc) {
243
+ return __awaiter(this, void 0, void 0, function* () {
244
+ let buffer = new Uint8Array();
245
+ for (;;) {
246
+ let read;
247
+ try {
248
+ read = yield reader.read();
249
+ }
250
+ catch (error) {
251
+ break;
252
+ }
253
+ if (read.done)
254
+ break;
255
+ buffer = mergeBuffer(buffer, read.value);
256
+ let offset = 0;
257
+ for (let size;; offset += size) {
258
+ size = getByteSize(buffer, offset);
259
+ if (!size)
260
+ break;
261
+ onRpc(buffer.subarray(offset, offset + size));
262
+ }
263
+ buffer = buffer.subarray(offset);
264
+ }
265
+ if (buffer.length > 0) {
266
+ console.warn('unprocessed bytes left in rpc stream', buffer);
267
+ }
268
+ });
269
+ }
270
+ function mergeBuffer(a, b) {
271
+ if (a.length === 0)
272
+ return b;
273
+ if (b.length === 0)
274
+ return a;
275
+ const merged = new Uint8Array(a.length + b.length);
276
+ merged.set(a, 0);
277
+ merged.set(b, a.length);
278
+ return merged;
279
+ }
@@ -3,11 +3,9 @@ export declare class Token {
3
3
  readonly text: string;
4
4
  readonly claims: TokenClaims;
5
5
  static parse(token: string): Result<Token>;
6
- private readonly _Address;
7
6
  private readonly _Upstream;
8
7
  protected constructor(text: string, claims: TokenClaims);
9
8
  get timeLeft(): number;
10
- get address(): URL | undefined;
11
9
  get upstream(): Token | undefined;
12
10
  get defaultRoomId(): string | undefined;
13
11
  hasAudience(audience: Infer<typeof TokenAudienceSchema>): boolean;
@@ -24,14 +24,6 @@ export class Token {
24
24
  constructor(text, claims) {
25
25
  this.text = text;
26
26
  this.claims = claims;
27
- if (claims.adr !== undefined) {
28
- try {
29
- this._Address = new URL(`wss://${claims.adr}`);
30
- }
31
- catch (error) {
32
- console.error(`address in token(${claims.adr}) can not be parsed; ${error}`);
33
- }
34
- }
35
27
  if (claims.ups !== undefined) {
36
28
  const upstream = Token.parse(claims.ups);
37
29
  if (upstream.type === 'Success') {
@@ -47,9 +39,6 @@ export class Token {
47
39
  return Infinity;
48
40
  return this.claims.exp * 1000 - Date.now();
49
41
  }
50
- get address() {
51
- return this._Address;
52
- }
53
42
  get upstream() {
54
43
  return this._Upstream;
55
44
  }
@@ -0,0 +1,52 @@
1
+ import { MainCommands, MainNotifications, Result, Backend } from '@4players/odin-common';
2
+ import { type MainConnection, type DatagramHandler, ConnectionStatus } from '../mainConnection';
3
+ import { Token } from '../token';
4
+ import { RpcConnection, TypedRpcConnection } from '../rpcConnection';
5
+ export declare class WebRtcMainConnection implements MainConnection {
6
+ readonly id: number;
7
+ readonly address: string;
8
+ readonly url: URL;
9
+ readonly onDatagram: DatagramHandler;
10
+ readonly connection: TypedRpcConnection<MainCommands, MainNotifications>;
11
+ readonly webRtc: RTCPeerConnection;
12
+ static create(id: number, address: string, onDatagram: DatagramHandler, signal: AbortSignal): Promise<Result<MainConnection>>;
13
+ private readonly _AudioChannel;
14
+ private readonly _PeerIdMap;
15
+ private _PeriodicHealthCheck;
16
+ private _WebRtcIceTimeout;
17
+ private _WebRtcTimeout;
18
+ private _ClosePromise;
19
+ private _LocalVideos;
20
+ private _RemoteVideos;
21
+ private _Negotiation;
22
+ private _ResolveOffer?;
23
+ private _awaitingRemoteTracks;
24
+ get transport(): Backend.Transport;
25
+ get closePromise(): Promise<"closed">;
26
+ get closeSignal(): AbortSignal;
27
+ get closed(): boolean;
28
+ get status(): ConnectionStatus;
29
+ waitForConnected(): Promise<void>;
30
+ private constructor();
31
+ close(): void;
32
+ joinRoom(token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]): Promise<Result<RpcConnection> | 'closed'>;
33
+ getReconnectToken(peerId: number): Promise<Result<Token>>;
34
+ sendDatagram(datagram: Uint8Array): void;
35
+ healthCheck(): Promise<boolean>;
36
+ private _Request;
37
+ private _readWebRtcUpdate;
38
+ private _SendOffer;
39
+ private _OnWebRtcStatusChanged;
40
+ private _OnWebRtcUpdate;
41
+ private _UpdateRemoteSdp;
42
+ private _AddIceCandidate;
43
+ private _OnIceCandidate;
44
+ private ensureTransceivers;
45
+ startLocalVideo(mediaId: number, track: MediaStreamTrack): Promise<void>;
46
+ stopLocalVideo(mediaId: number): void;
47
+ startRemoteVideo(mediaId: number): Promise<MediaStreamTrack>;
48
+ stopRemoteVideo(mediaId: number): void;
49
+ private onTrack;
50
+ requestNegotiation(waitForAnswer: boolean): void;
51
+ private _CleanUp;
52
+ }