@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
|
@@ -1,294 +1,23 @@
|
|
|
1
1
|
/* eslint-disable no-dupe-class-members */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export class MainConnection {
|
|
17
|
-
static create(id, url, onDatagram, signal) {
|
|
18
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
if (signal.aborted)
|
|
20
|
-
return failure('aborted');
|
|
21
|
-
const webSocketUrl = extendUrl(url, 'main');
|
|
22
|
-
if (webSocketUrl.type !== 'Success')
|
|
23
|
-
return webSocketUrl;
|
|
24
|
-
const webSocket = yield connect(webSocketUrl.value, signal);
|
|
25
|
-
if (webSocket.type !== 'Success')
|
|
26
|
-
return webSocket;
|
|
27
|
-
const connection = new TypedRpcConnection(webSocket.value, MainCommandsRpc, MainNotificationsRpc);
|
|
28
|
-
let webRtc;
|
|
29
|
-
try {
|
|
30
|
-
webRtc = new RTCPeerConnection();
|
|
31
|
-
}
|
|
32
|
-
catch (reason) {
|
|
33
|
-
connection.close();
|
|
34
|
-
return failure(`unable to create webrtc connection; ${reason}`);
|
|
35
|
-
}
|
|
36
|
-
return success(new MainConnection(id, url, onDatagram, connection, webRtc));
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
get closePromise() {
|
|
40
|
-
return this._ClosePromise;
|
|
41
|
-
}
|
|
42
|
-
get closeSignal() {
|
|
43
|
-
return this.connection.closeSignal;
|
|
44
|
-
}
|
|
45
|
-
get closed() {
|
|
46
|
-
return this.connection.closed;
|
|
47
|
-
}
|
|
48
|
-
webRtcReady() {
|
|
49
|
-
const isReady = () => {
|
|
50
|
-
switch (this.webRtc.connectionState) {
|
|
51
|
-
case 'connected':
|
|
52
|
-
return true;
|
|
53
|
-
case 'failed':
|
|
54
|
-
case 'closed':
|
|
55
|
-
return false;
|
|
56
|
-
default:
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const result = isReady();
|
|
61
|
-
if (result !== undefined)
|
|
62
|
-
return Promise.resolve(result);
|
|
63
|
-
return new Promise((accept) => {
|
|
64
|
-
const onConnectionStateChanged = () => {
|
|
65
|
-
const result = isReady();
|
|
66
|
-
if (result !== undefined) {
|
|
67
|
-
this.webRtc.removeEventListener('connectionstatechange', onConnectionStateChanged);
|
|
68
|
-
accept(result);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
this.webRtc.addEventListener('connectionstatechange', onConnectionStateChanged);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
constructor(id, url, onDatagram, connection, webRtc) {
|
|
75
|
-
this.id = id;
|
|
76
|
-
this.url = url;
|
|
77
|
-
this.onDatagram = onDatagram;
|
|
78
|
-
this.connection = connection;
|
|
79
|
-
this.webRtc = webRtc;
|
|
80
|
-
this._PeerIdMap = new WeakMap();
|
|
81
|
-
this.closeSignal.addEventListener('abort', () => this._CleanUp(), {
|
|
82
|
-
once: true,
|
|
83
|
-
});
|
|
84
|
-
this._ClosePromise = new Promise((accept) => {
|
|
85
|
-
this.closeSignal.addEventListener('abort', () => accept('closed'), {
|
|
86
|
-
once: true,
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
this._AudioChannel = this.webRtc.createDataChannel('audio', {
|
|
90
|
-
id: 2,
|
|
91
|
-
negotiated: true,
|
|
92
|
-
ordered: false,
|
|
93
|
-
maxRetransmits: 0,
|
|
94
|
-
});
|
|
95
|
-
this._AudioChannel.binaryType = 'arraybuffer';
|
|
96
|
-
this._AudioChannel.onmessage = (event) => onDatagram(this.id, new Uint8Array(event.data));
|
|
97
|
-
this.webRtc.onicecandidate = this._OnIceCandidate.bind(this);
|
|
98
|
-
this.webRtc.onconnectionstatechange = () => this._OnWebRtcStatusChanged();
|
|
99
|
-
this._SendOffer();
|
|
100
|
-
if (MainConnectionPingInterval !== undefined) {
|
|
101
|
-
this._PeriodicHealthCheck = setInterval(() => this.healthCheck(), MainConnectionPingInterval);
|
|
102
|
-
}
|
|
103
|
-
this._readEvents();
|
|
104
|
-
}
|
|
105
|
-
close() {
|
|
106
|
-
this.connection.close();
|
|
107
|
-
}
|
|
108
|
-
joinRoom(token, roomId, userData, position) {
|
|
109
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
-
const response = yield this.connection.request('JoinRoom', {
|
|
111
|
-
token: String(token),
|
|
112
|
-
room_id: roomId,
|
|
113
|
-
user_data: userData,
|
|
114
|
-
position,
|
|
115
|
-
});
|
|
116
|
-
if (response === 'closed' || response.type === 'Failure')
|
|
117
|
-
return response;
|
|
118
|
-
const responseValue = response.value;
|
|
119
|
-
if (!('token' in responseValue)) {
|
|
120
|
-
return failure('JoinRoom responded without token');
|
|
121
|
-
}
|
|
122
|
-
const url = extendUrl(this.url, `room?token=${responseValue.token}`);
|
|
123
|
-
if (url.type === 'Failure')
|
|
124
|
-
return url;
|
|
125
|
-
const webSocket = yield connect(url.value, this.closeSignal);
|
|
126
|
-
if (webSocket.type === 'Success') {
|
|
127
|
-
this._PeerIdMap.set(webSocket.value, responseValue.peer_id);
|
|
128
|
-
}
|
|
129
|
-
return webSocket;
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
getReconnectToken(webSocket) {
|
|
133
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
134
|
-
const peerId = this._PeerIdMap.get(webSocket);
|
|
135
|
-
if (peerId === undefined) {
|
|
136
|
-
return failure('the webSocket is not part of this connection');
|
|
137
|
-
}
|
|
138
|
-
const response = yield this._Request('RequestReconnectToken', {
|
|
139
|
-
peer_id: peerId,
|
|
140
|
-
});
|
|
141
|
-
if (response.type === 'Failure')
|
|
142
|
-
return response;
|
|
143
|
-
return Token.parse(response.value);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
sendDatagram(datagram) {
|
|
147
|
-
if (this._AudioChannel.readyState === 'open') {
|
|
148
|
-
this._AudioChannel.send(datagram);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
healthCheck() {
|
|
152
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
-
const response = yield this._Request('Ping', {}, HealthCheckTimeout);
|
|
154
|
-
const success = response !== 'timeout' && response.type === 'Success';
|
|
155
|
-
if (!success)
|
|
156
|
-
this.close();
|
|
157
|
-
return success;
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
_Request(name, properties, timeout) {
|
|
161
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
162
|
-
const result = yield (timeout === undefined
|
|
163
|
-
? this.connection.request(name, properties)
|
|
164
|
-
: this.connection.request(name, properties, timeout));
|
|
165
|
-
if (result === 'closed')
|
|
166
|
-
return failure('connection lost');
|
|
167
|
-
return result;
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
_readEvents() {
|
|
171
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
172
|
-
while (true) {
|
|
173
|
-
const event = yield this.connection.recv();
|
|
174
|
-
if (event === 'closed')
|
|
175
|
-
break;
|
|
176
|
-
console.debug(`${this.url} received ${event.name} on main`, event.properties);
|
|
177
|
-
if (event.name === 'WebRtcUpdate') {
|
|
178
|
-
this._OnWebRtcUpdate(event.properties);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
this.close();
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
_SendOffer() {
|
|
185
|
-
var _a;
|
|
186
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
-
let sdp;
|
|
188
|
-
try {
|
|
189
|
-
const offer = yield this.webRtc.createOffer();
|
|
190
|
-
assert(offer.type === 'offer' && offer.sdp, 'generated broken webrtc offer');
|
|
191
|
-
yield this.webRtc.setLocalDescription(offer);
|
|
192
|
-
sdp = (_a = offer.sdp) !== null && _a !== void 0 ? _a : '';
|
|
193
|
-
}
|
|
194
|
-
catch (_b) {
|
|
195
|
-
console.error('RTCPeerConnection failed creating an offer');
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
yield this._Request('WebRtcUpdate', {
|
|
199
|
-
kind: 'Sdp',
|
|
200
|
-
type: 'Offer',
|
|
201
|
-
sdp,
|
|
202
|
-
media_map: [],
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
_OnWebRtcStatusChanged() {
|
|
207
|
-
console.log(`webrtc ${this.webRtc.connectionState}`);
|
|
208
|
-
switch (this.webRtc.connectionState) {
|
|
209
|
-
case 'closed':
|
|
210
|
-
case 'failed':
|
|
211
|
-
this.close();
|
|
212
|
-
break;
|
|
213
|
-
case 'connected':
|
|
214
|
-
clearTimeout(this._WebRtcIceTimeout);
|
|
215
|
-
this._WebRtcIceTimeout = undefined;
|
|
216
|
-
clearTimeout(this._WebRtcTimeout);
|
|
217
|
-
this._WebRtcTimeout = undefined;
|
|
218
|
-
break;
|
|
219
|
-
case 'disconnected':
|
|
220
|
-
this.healthCheck();
|
|
221
|
-
// falls through
|
|
222
|
-
default:
|
|
223
|
-
if (this._WebRtcIceTimeout === undefined) {
|
|
224
|
-
this._WebRtcIceTimeout = setTimeout(() => {
|
|
225
|
-
this.webRtc.restartIce();
|
|
226
|
-
this._WebRtcIceTimeout = undefined;
|
|
227
|
-
}, WebRtcIceTimeout);
|
|
228
|
-
}
|
|
229
|
-
if (this._WebRtcTimeout === undefined) {
|
|
230
|
-
this._WebRtcTimeout = setTimeout(() => this.close(), WebRtcTimeout);
|
|
231
|
-
}
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
_OnWebRtcUpdate(update) {
|
|
236
|
-
switch (update.kind) {
|
|
237
|
-
case 'Sdp':
|
|
238
|
-
this._UpdateRemoteSdp(update);
|
|
239
|
-
break;
|
|
240
|
-
case 'Trickle':
|
|
241
|
-
this._AddIceCandidate(update);
|
|
242
|
-
break;
|
|
243
|
-
case 'TrickleFinished':
|
|
244
|
-
this.webRtc.addIceCandidate(undefined);
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
_UpdateRemoteSdp(update) {
|
|
249
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
250
|
-
const type = update.type === 'Answer' ? 'answer' : 'offer';
|
|
251
|
-
yield this.webRtc.setRemoteDescription({ type, sdp: update.sdp });
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
_AddIceCandidate(candidate) {
|
|
255
|
-
var _a, _b, _c;
|
|
256
|
-
const candidateInit = {
|
|
257
|
-
candidate: candidate.candidate,
|
|
258
|
-
sdpMid: (_a = candidate.spd_mid) !== null && _a !== void 0 ? _a : undefined,
|
|
259
|
-
sdpMLineIndex: (_b = candidate.spd_mline_index) !== null && _b !== void 0 ? _b : undefined,
|
|
260
|
-
usernameFragment: (_c = candidate.username_fragment) !== null && _c !== void 0 ? _c : undefined,
|
|
261
|
-
};
|
|
262
|
-
// HACK/TODO(max): find out why the server is not sending sdpMid or sdpMLineIndex
|
|
263
|
-
if (candidateInit.sdpMid === undefined &&
|
|
264
|
-
candidateInit.sdpMLineIndex === undefined) {
|
|
265
|
-
candidateInit.sdpMid = '0';
|
|
266
|
-
}
|
|
267
|
-
this.webRtc.addIceCandidate(candidateInit);
|
|
268
|
-
}
|
|
269
|
-
_OnIceCandidate(event) {
|
|
270
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
271
|
-
if (event.candidate) {
|
|
272
|
-
const { candidate, sdpMid, sdpMLineIndex, usernameFragment } = event.candidate;
|
|
273
|
-
this._Request('WebRtcUpdate', {
|
|
274
|
-
kind: 'Trickle',
|
|
275
|
-
candidate,
|
|
276
|
-
spd_mid: sdpMid !== null && sdpMid !== void 0 ? sdpMid : undefined,
|
|
277
|
-
spd_mline_index: sdpMLineIndex !== null && sdpMLineIndex !== void 0 ? sdpMLineIndex : undefined,
|
|
278
|
-
username_fragment: usernameFragment !== null && usernameFragment !== void 0 ? usernameFragment : undefined,
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
this._Request('WebRtcUpdate', { kind: 'TrickleFinished' });
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
_CleanUp() {
|
|
287
|
-
console.debug(`${this.url} closed`);
|
|
288
|
-
this.connection.close();
|
|
289
|
-
this.webRtc.close();
|
|
290
|
-
clearInterval(this._PeriodicHealthCheck);
|
|
291
|
-
clearTimeout(this._WebRtcIceTimeout);
|
|
292
|
-
clearTimeout(this._WebRtcTimeout);
|
|
2
|
+
import { Backend } from '@4players/odin-common';
|
|
3
|
+
import { logger } from './index';
|
|
4
|
+
import { WebRtcMainConnection } from './webrtc/mainConnection';
|
|
5
|
+
import { H3MainConnection } from './h3/mainConnection';
|
|
6
|
+
var Transport = Backend.Transport;
|
|
7
|
+
export function createMainConnection(id, transport, address, onDatagram, signal) {
|
|
8
|
+
logger.log(['Connection'], 'createMainConnection() ', transport, address);
|
|
9
|
+
switch (transport) {
|
|
10
|
+
case Transport.H3:
|
|
11
|
+
return H3MainConnection.create(id, address, onDatagram, signal);
|
|
12
|
+
case Transport.WebRTC:
|
|
13
|
+
return WebRtcMainConnection.create(id, address, onDatagram, signal);
|
|
14
|
+
default:
|
|
15
|
+
throw new Error(`transport ${transport} is not supported`);
|
|
293
16
|
}
|
|
294
17
|
}
|
|
18
|
+
export var ConnectionStatus;
|
|
19
|
+
(function (ConnectionStatus) {
|
|
20
|
+
ConnectionStatus[ConnectionStatus["Connecting"] = 0] = "Connecting";
|
|
21
|
+
ConnectionStatus[ConnectionStatus["Connected"] = 1] = "Connected";
|
|
22
|
+
ConnectionStatus[ConnectionStatus["Closed"] = 2] = "Closed";
|
|
23
|
+
})(ConnectionStatus || (ConnectionStatus = {}));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Backend, Strand } from '@4players/odin-common';
|
|
2
|
-
import { DeviceParameters } from './index';
|
|
3
|
-
import { RoomConnection } from './roomConnection';
|
|
4
2
|
import { CaptureSink, PlaybackId, PlaybackWorker } from '../worker/common';
|
|
3
|
+
import { AudioCapture } from './medias/audio-capture';
|
|
4
|
+
import { VideoCapture } from './medias/video-capture';
|
|
5
|
+
import { AudioPlayback } from './medias/audio-playback';
|
|
5
6
|
export type CreateAudioPlaybackParameters = Backend.CreateAudioPlaybackParameters;
|
|
6
7
|
export type CreateAudioCaptureParameters = Backend.CreateAudioCaptureParameters & {
|
|
7
8
|
readonly device?: Device;
|
|
@@ -29,7 +30,7 @@ export declare class AudioContextCollection {
|
|
|
29
30
|
release(audioContext: BaseAudioContext): Promise<void>;
|
|
30
31
|
close(): void;
|
|
31
32
|
}
|
|
32
|
-
export declare class
|
|
33
|
+
export declare class MediaController {
|
|
33
34
|
readonly audioContexts: AudioContextCollection;
|
|
34
35
|
readonly sink: CaptureSink;
|
|
35
36
|
playbackWorker: PlaybackWorker | undefined;
|
|
@@ -40,18 +41,12 @@ export declare class AudioController {
|
|
|
40
41
|
playback: AudioPlayback;
|
|
41
42
|
}>;
|
|
42
43
|
setPlaybackStrand: Strand;
|
|
43
|
-
create(audioContexts: AudioContextCollection, sink: CaptureSink): Promise<
|
|
44
|
+
create(audioContexts: AudioContextCollection, sink: CaptureSink): Promise<MediaController>;
|
|
44
45
|
constructor(audioContexts: AudioContextCollection, sink: CaptureSink);
|
|
45
46
|
receivePacket(connectionId: number, packet: Uint8Array): void;
|
|
46
47
|
createAudioPlayback(parameters: CreateAudioPlaybackParameters): Promise<AudioPlayback>;
|
|
47
48
|
createAudioCapture(parameters: CreateAudioCaptureParameters): Promise<AudioCapture>;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Requests audio and video browser permissions. Optionally, requests either audio or video permissions.
|
|
51
|
-
* @private
|
|
52
|
-
* @throws
|
|
53
|
-
*/
|
|
54
|
-
private _requestMediaPermissions;
|
|
49
|
+
createVideoCapture(ms: MediaStream, customType?: string): Promise<VideoCapture>;
|
|
55
50
|
playbackSetDevice(device?: Device): Promise<void>;
|
|
56
51
|
playbackSetGlobalVolume(value: Backend.Volume): void;
|
|
57
52
|
playbackStart(uid: string, id: GlobalMediaId): void;
|
|
@@ -69,38 +64,3 @@ export declare class Device implements Backend.Device {
|
|
|
69
64
|
get type(): "AudioPlayback" | "AudioCapture";
|
|
70
65
|
get name(): string;
|
|
71
66
|
}
|
|
72
|
-
export declare class AudioPlayback implements Backend.AudioPlayback {
|
|
73
|
-
readonly controller: AudioController;
|
|
74
|
-
readonly uid: Backend.UID;
|
|
75
|
-
get volume(): Backend.Volume;
|
|
76
|
-
get activity(): Backend.Activity;
|
|
77
|
-
constructor(controller: AudioController, uid: Backend.UID);
|
|
78
|
-
start(id: GlobalMediaId): void;
|
|
79
|
-
stop(id: GlobalMediaId): void;
|
|
80
|
-
setVolume(value: Backend.Volume): void;
|
|
81
|
-
close(): void;
|
|
82
|
-
}
|
|
83
|
-
export declare class AudioCapture implements Backend.AudioCapture {
|
|
84
|
-
readonly controller: AudioController;
|
|
85
|
-
private _Capture;
|
|
86
|
-
private readonly _Medias;
|
|
87
|
-
private _Vad;
|
|
88
|
-
private _Apm;
|
|
89
|
-
private _Volume;
|
|
90
|
-
private _deviceParameters?;
|
|
91
|
-
get vad(): Backend.VadConfig;
|
|
92
|
-
get apm(): Backend.ApmConfig;
|
|
93
|
-
get volume(): Backend.Volume;
|
|
94
|
-
get activity(): import("../worker/common").Activity;
|
|
95
|
-
get speechProbability(): number;
|
|
96
|
-
get deviceId(): string | undefined;
|
|
97
|
-
constructor(controller: AudioController, parameters: CreateAudioCaptureParameters);
|
|
98
|
-
start(room: RoomConnection, media: GlobalMediaId, uid: Backend.UID): void;
|
|
99
|
-
stop(room: RoomConnection, media: GlobalMediaId): void;
|
|
100
|
-
setVad(value: Backend.VadConfig): void;
|
|
101
|
-
setApm(config: Backend.ApmConfig): Promise<void>;
|
|
102
|
-
setDevice(parameters: DeviceParameters): Promise<import("@4players/odin-common").Success<void> | undefined>;
|
|
103
|
-
setVolume(value: Backend.Volume): void;
|
|
104
|
-
close(): void;
|
|
105
|
-
_UpdateMedias(): Promise<void>;
|
|
106
|
-
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { Strand, assert } from '@4players/odin-common';
|
|
11
|
+
import { createPlaybackWorker } from '../worker';
|
|
12
|
+
import { Silence, } from '../worker/common';
|
|
13
|
+
import { AudioCapture } from './medias/audio-capture';
|
|
14
|
+
import { VideoCapture } from './medias/video-capture';
|
|
15
|
+
import { AudioPlayback } from './medias/audio-playback';
|
|
16
|
+
export class AudioContextCollection {
|
|
17
|
+
constructor(audioContextFactory) {
|
|
18
|
+
this.audioContextFactory = audioContextFactory;
|
|
19
|
+
this.entries = [];
|
|
20
|
+
}
|
|
21
|
+
acquire(sampleRate) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
for (const entry of this.entries) {
|
|
24
|
+
if (entry.sampleRate === sampleRate) {
|
|
25
|
+
entry.references += 1;
|
|
26
|
+
return entry.audioContext;
|
|
27
|
+
}
|
|
28
|
+
else if (entry.sampleRate === undefined) {
|
|
29
|
+
const audioContext = entry.audioContext;
|
|
30
|
+
if (audioContext.sampleRate === sampleRate) {
|
|
31
|
+
entry.references += 1;
|
|
32
|
+
return entry.audioContext;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const entry = {
|
|
37
|
+
sampleRate,
|
|
38
|
+
audioContext: yield this.audioContextFactory(sampleRate),
|
|
39
|
+
references: 1,
|
|
40
|
+
};
|
|
41
|
+
this.entries.push(entry);
|
|
42
|
+
return entry.audioContext;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
release(audioContext) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const index = this.entries.findIndex((entry) => entry.audioContext === audioContext);
|
|
48
|
+
if (index < 0)
|
|
49
|
+
return;
|
|
50
|
+
this.entries[index].references -= 1;
|
|
51
|
+
if (this.entries[index].references <= 0) {
|
|
52
|
+
const context = this.entries[index].audioContext;
|
|
53
|
+
this.entries.splice(index, 1);
|
|
54
|
+
yield context.close();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
close() {
|
|
59
|
+
for (const entry of this.entries) {
|
|
60
|
+
entry.audioContext.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class MediaController {
|
|
65
|
+
create(audioContexts, sink) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
return new MediaController(audioContexts, sink);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
constructor(audioContexts, sink) {
|
|
71
|
+
this.audioContexts = audioContexts;
|
|
72
|
+
this.sink = sink;
|
|
73
|
+
this.globalVolume = 1;
|
|
74
|
+
this.playbacks = new Map();
|
|
75
|
+
this.setPlaybackStrand = new Strand();
|
|
76
|
+
}
|
|
77
|
+
receivePacket(connectionId, packet) {
|
|
78
|
+
var _a;
|
|
79
|
+
(_a = this.playbackWorker) === null || _a === void 0 ? void 0 : _a.receivePacket(connectionId, packet);
|
|
80
|
+
}
|
|
81
|
+
createAudioPlayback(parameters) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
let entry = this.playbacks.get(parameters.uid);
|
|
84
|
+
let volume = parameters.volume;
|
|
85
|
+
if (entry === undefined) {
|
|
86
|
+
volume = volume !== null && volume !== void 0 ? volume : 1;
|
|
87
|
+
const playback = new AudioPlayback(this, parameters.uid);
|
|
88
|
+
entry = { id: undefined, volume, playback };
|
|
89
|
+
this.playbacks.set(parameters.uid, entry);
|
|
90
|
+
}
|
|
91
|
+
if (volume !== undefined) {
|
|
92
|
+
entry.playback.setVolume(volume);
|
|
93
|
+
}
|
|
94
|
+
return entry.playback;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
createAudioCapture(parameters) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const capture = new AudioCapture(this, parameters);
|
|
100
|
+
yield capture.setDevice(parameters);
|
|
101
|
+
return capture;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
createVideoCapture(ms, customType) {
|
|
105
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
106
|
+
const videoCapture = new VideoCapture(this, customType);
|
|
107
|
+
yield videoCapture.setDevice(ms);
|
|
108
|
+
return videoCapture;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
playbackSetDevice(device) {
|
|
112
|
+
return this.setPlaybackStrand.enqueue(() => __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
var _a;
|
|
114
|
+
let deviceId = (_a = device === null || device === void 0 ? void 0 : device.device.deviceId) !== null && _a !== void 0 ? _a : '';
|
|
115
|
+
if (deviceId === 'default') {
|
|
116
|
+
deviceId = '';
|
|
117
|
+
}
|
|
118
|
+
let audioContext;
|
|
119
|
+
if (this.playbackWorker === undefined) {
|
|
120
|
+
audioContext = yield this.audioContexts.acquire(48000);
|
|
121
|
+
this.playbackWorker = yield createPlaybackWorker(audioContext);
|
|
122
|
+
this.playbackWorker.setGlobalVolume(this.globalVolume);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
audioContext = this.playbackWorker.audioContext;
|
|
126
|
+
}
|
|
127
|
+
if ((audioContext === null || audioContext === void 0 ? void 0 : audioContext.setSinkId) !== undefined &&
|
|
128
|
+
device &&
|
|
129
|
+
(audioContext === null || audioContext === void 0 ? void 0 : audioContext.state) === 'running') {
|
|
130
|
+
yield audioContext.setSinkId(deviceId);
|
|
131
|
+
}
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
playbackSetGlobalVolume(value) {
|
|
135
|
+
var _a;
|
|
136
|
+
this.globalVolume = value === 'muted' ? 0 : value;
|
|
137
|
+
(_a = this.playbackWorker) === null || _a === void 0 ? void 0 : _a.setGlobalVolume(this.globalVolume);
|
|
138
|
+
}
|
|
139
|
+
playbackStart(uid, id) {
|
|
140
|
+
var _a;
|
|
141
|
+
const entry = this.playbacks.get(uid);
|
|
142
|
+
if (entry === undefined)
|
|
143
|
+
return;
|
|
144
|
+
if (entry.id !== undefined) {
|
|
145
|
+
this.playbackStop(uid, entry.id);
|
|
146
|
+
}
|
|
147
|
+
entry.id = id;
|
|
148
|
+
(_a = this.playbackWorker) === null || _a === void 0 ? void 0 : _a.addMedias(id.connectionId, [id.mediaId]);
|
|
149
|
+
this.playbackSetVolume(uid, entry.volume);
|
|
150
|
+
}
|
|
151
|
+
playbackStop(uid, id) {
|
|
152
|
+
var _a;
|
|
153
|
+
const entry = this.playbacks.get(uid);
|
|
154
|
+
if (entry === undefined)
|
|
155
|
+
return;
|
|
156
|
+
if (entry.id !== id)
|
|
157
|
+
console.error('playback mediaId out of sync!');
|
|
158
|
+
entry.id = undefined;
|
|
159
|
+
(_a = this.playbackWorker) === null || _a === void 0 ? void 0 : _a.removeMedias(id.connectionId, [id.mediaId]);
|
|
160
|
+
}
|
|
161
|
+
playbackGetActivity(uid) {
|
|
162
|
+
var _a, _b;
|
|
163
|
+
const entry = this.playbacks.get(uid);
|
|
164
|
+
if ((entry === null || entry === void 0 ? void 0 : entry.id) === undefined)
|
|
165
|
+
return Silence;
|
|
166
|
+
return (_b = (_a = this.playbackWorker) === null || _a === void 0 ? void 0 : _a.getActivity(entry.id)) !== null && _b !== void 0 ? _b : Silence;
|
|
167
|
+
}
|
|
168
|
+
playbackSetVolume(uid, volume) {
|
|
169
|
+
var _a;
|
|
170
|
+
const entry = this.playbacks.get(uid);
|
|
171
|
+
if (entry === undefined)
|
|
172
|
+
return;
|
|
173
|
+
entry.volume = volume === 'muted' ? 0 : volume;
|
|
174
|
+
if (entry.id !== undefined) {
|
|
175
|
+
(_a = this.playbackWorker) === null || _a === void 0 ? void 0 : _a.setVolume(entry.id, entry.volume);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
playbackGetVolume(uid) {
|
|
179
|
+
var _a;
|
|
180
|
+
const entry = this.playbacks.get(uid);
|
|
181
|
+
return (_a = entry === null || entry === void 0 ? void 0 : entry.volume) !== null && _a !== void 0 ? _a : 1;
|
|
182
|
+
}
|
|
183
|
+
closePlayback(uid) {
|
|
184
|
+
const entry = this.playbacks.get(uid);
|
|
185
|
+
if (entry === undefined)
|
|
186
|
+
return;
|
|
187
|
+
if (entry.id !== undefined)
|
|
188
|
+
this.playbackStop(uid, entry.id);
|
|
189
|
+
this.playbacks.delete(uid);
|
|
190
|
+
}
|
|
191
|
+
close() {
|
|
192
|
+
var _a;
|
|
193
|
+
(_a = this.playbackWorker) === null || _a === void 0 ? void 0 : _a.close();
|
|
194
|
+
this.audioContexts.close();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export class Device {
|
|
198
|
+
constructor(device, isDefault = false) {
|
|
199
|
+
this.device = device;
|
|
200
|
+
this.isDefault = isDefault;
|
|
201
|
+
}
|
|
202
|
+
get type() {
|
|
203
|
+
switch (this.device.kind) {
|
|
204
|
+
case 'audioinput':
|
|
205
|
+
return 'AudioCapture';
|
|
206
|
+
case 'audiooutput':
|
|
207
|
+
return 'AudioPlayback';
|
|
208
|
+
default:
|
|
209
|
+
assert(false, 'not supported device');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
get name() {
|
|
213
|
+
return this.device.label;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Backend } from '@4players/odin-common';
|
|
2
|
+
import { DeviceParameters } from '../index';
|
|
3
|
+
import { RoomConnection } from '../roomConnection';
|
|
4
|
+
import { MediaController, CreateAudioCaptureParameters, GlobalMediaId } from '../mediaController';
|
|
5
|
+
export declare class AudioCapture implements Backend.AudioCapture {
|
|
6
|
+
readonly controller: MediaController;
|
|
7
|
+
private _Capture;
|
|
8
|
+
private readonly _Medias;
|
|
9
|
+
private _Vad;
|
|
10
|
+
private _Apm;
|
|
11
|
+
private _Volume;
|
|
12
|
+
private _DeviceParameters?;
|
|
13
|
+
private _DeviceCb;
|
|
14
|
+
get vad(): Backend.VadConfig;
|
|
15
|
+
get apm(): Backend.ApmConfig;
|
|
16
|
+
get volume(): Backend.Volume;
|
|
17
|
+
get activity(): import("../../worker/common").Activity;
|
|
18
|
+
get speechProbability(): number;
|
|
19
|
+
get deviceId(): string | undefined;
|
|
20
|
+
constructor(controller: MediaController, parameters: CreateAudioCaptureParameters);
|
|
21
|
+
start(room: RoomConnection, media: GlobalMediaId, uid: Backend.UID): void;
|
|
22
|
+
stop(room: RoomConnection, media: GlobalMediaId): void;
|
|
23
|
+
setVad(value: Backend.VadConfig): void;
|
|
24
|
+
setApm(config: Backend.ApmConfig): Promise<void>;
|
|
25
|
+
setDevice(parameters: DeviceParameters): Promise<import("@4players/odin-common").Success<void> | undefined>;
|
|
26
|
+
setVolume(value: Backend.Volume): void;
|
|
27
|
+
close(): void;
|
|
28
|
+
_UpdateMedias(): Promise<void>;
|
|
29
|
+
}
|