@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.
- package/lib/common/msgpack.d.ts +2 -0
- package/lib/common/msgpack.js +117 -0
- package/lib/plugin/config.d.ts +5 -3
- package/lib/plugin/config.js +15 -3
- package/lib/plugin/connectionPool.d.ts +3 -2
- package/lib/plugin/connectionPool.js +31 -13
- package/lib/plugin/deviceManager.d.ts +12 -0
- package/lib/plugin/deviceManager.js +120 -0
- package/lib/plugin/h3/mainConnection.d.ts +40 -0
- package/lib/plugin/h3/mainConnection.js +197 -0
- package/lib/plugin/index.d.ts +17 -6
- package/lib/plugin/index.js +158 -56
- package/lib/plugin/mainConnection.d.ts +23 -30
- package/lib/plugin/mainConnection.js +20 -291
- package/lib/plugin/{audioController.d.ts → mediaController.d.ts} +6 -46
- package/lib/plugin/mediaController.js +215 -0
- package/lib/plugin/medias/audio-capture.d.ts +29 -0
- package/lib/plugin/medias/audio-capture.js +203 -0
- package/lib/plugin/medias/audio-playback.d.ts +13 -0
- package/lib/plugin/medias/audio-playback.js +24 -0
- package/lib/plugin/medias/video-capture.d.ts +15 -0
- package/lib/plugin/medias/video-capture.js +70 -0
- package/lib/plugin/medias/video-playback.d.ts +13 -0
- package/lib/plugin/medias/video-playback.js +20 -0
- package/lib/plugin/roomConnection.d.ts +5 -2
- package/lib/plugin/roomConnection.js +54 -31
- package/lib/plugin/rpcConnection.d.ts +13 -7
- package/lib/plugin/rpcConnection.js +103 -33
- package/lib/plugin/token.d.ts +0 -2
- package/lib/plugin/token.js +0 -11
- package/lib/plugin/webrtc/mainConnection.d.ts +52 -0
- package/lib/plugin/webrtc/mainConnection.js +455 -0
- package/lib/plugin/{websocket.js → webrtc/websocket.js} +2 -2
- package/lib/version.d.ts +1 -0
- package/lib/version.js +2 -0
- package/lib/worker/index.js +1 -1
- package/package.json +18 -11
- package/lib/plugin/audioController.js +0 -513
- /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(
|
|
20
|
-
this.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
59
|
+
if (!this.closed) {
|
|
52
60
|
this._Send([2, name, properties]);
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
recv() {
|
|
56
|
-
if (this.closed
|
|
57
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
|
79
|
-
this.
|
|
98
|
+
for (const data of this._SendQueue) {
|
|
99
|
+
this._Send(data);
|
|
80
100
|
}
|
|
81
101
|
this._SendQueue.length = 0;
|
|
82
102
|
}
|
|
83
|
-
|
|
84
|
-
const rpc = parseRpc(
|
|
103
|
+
_OnRpcMessage(bytes) {
|
|
104
|
+
const rpc = parseRpc(bytes);
|
|
85
105
|
if (rpc.type !== 'Success') {
|
|
86
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
?
|
|
144
|
-
:
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/lib/plugin/token.d.ts
CHANGED
|
@@ -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;
|
package/lib/plugin/token.js
CHANGED
|
@@ -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
|
+
}
|