@4players/odin-plugin-web 1.0.0-alpha.21 → 1.0.0-alpha.22
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 +12 -0
- package/lib/plugin/config.js +22 -0
- package/lib/plugin/connectionPool.d.ts +17 -0
- package/lib/plugin/connectionPool.js +130 -0
- package/lib/plugin/deviceManager.d.ts +12 -0
- package/lib/plugin/deviceManager.js +120 -0
- package/lib/plugin/h3/mainConnection.d.ts +30 -0
- package/lib/plugin/h3/mainConnection.js +196 -0
- package/lib/plugin/index.d.ts +61 -18
- package/lib/plugin/index.js +405 -1
- package/lib/plugin/mainConnection.d.ts +30 -0
- package/lib/plugin/mainConnection.js +23 -0
- package/lib/plugin/mediaController.d.ts +66 -0
- package/lib/plugin/mediaController.js +215 -0
- package/lib/plugin/medias/audio-capture.d.ts +30 -0
- package/lib/plugin/medias/audio-capture.js +224 -0
- package/lib/plugin/medias/audio-playback.d.ts +13 -0
- package/lib/plugin/medias/audio-playback.js +40 -0
- package/lib/plugin/medias/video-capture.d.ts +15 -0
- package/lib/plugin/medias/video-capture.js +69 -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 +40 -0
- package/lib/plugin/roomConnection.js +320 -0
- package/lib/plugin/rpcConnection.d.ts +57 -0
- package/lib/plugin/rpcConnection.js +281 -0
- package/lib/plugin/token.d.ts +13 -0
- package/lib/plugin/token.js +66 -0
- package/lib/plugin/webrtc/mainConnection.d.ts +52 -0
- package/lib/plugin/webrtc/mainConnection.js +455 -0
- package/lib/plugin/webrtc/websocket.d.ts +3 -0
- package/lib/plugin/webrtc/websocket.js +56 -0
- package/lib/wasm/index.d.ts +97 -0
- package/lib/worker/buffers.d.ts +42 -0
- package/lib/worker/buffers.js +79 -0
- package/lib/worker/common.d.ts +45 -0
- package/lib/worker/common.js +7 -0
- package/lib/worker/config.d.ts +8 -0
- package/lib/worker/config.js +11 -0
- package/lib/worker/index.d.ts +3 -0
- package/lib/worker/index.js +1 -0
- package/lib/worker/memory.d.ts +9 -0
- package/lib/worker/memory.js +29 -0
- package/lib/worker/messages.d.ts +14 -0
- package/lib/worker/messages.js +15 -0
- package/lib/worker/packet.d.ts +11 -0
- package/lib/worker/packet.js +59 -0
- package/lib/worker/shared/capture.d.ts +27 -0
- package/lib/worker/shared/capture.js +63 -0
- package/lib/worker/shared/index.d.ts +3 -0
- package/lib/worker/shared/index.js +147 -0
- package/lib/worker/shared/messages.d.ts +67 -0
- package/lib/worker/shared/messages.js +64 -0
- package/lib/worker/shared/playback.d.ts +20 -0
- package/lib/worker/shared/playback.js +78 -0
- package/lib/worker/unshared/capture.d.ts +27 -0
- package/lib/worker/unshared/capture.js +66 -0
- package/lib/worker/unshared/index.d.ts +3 -0
- package/lib/worker/unshared/index.js +170 -0
- package/lib/worker/unshared/messages.d.ts +81 -0
- package/lib/worker/unshared/messages.js +80 -0
- package/lib/worker/unshared/playback.d.ts +22 -0
- package/lib/worker/unshared/playback.js +83 -0
- package/package.json +9 -12
- package/lib/worker/shared.js +0 -1
- package/lib/worker/unshared.js +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Backend, Strand } from '@4players/odin-common';
|
|
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';
|
|
6
|
+
export type CreateAudioPlaybackParameters = Backend.CreateAudioPlaybackParameters;
|
|
7
|
+
export type CreateAudioCaptureParameters = Backend.CreateAudioCaptureParameters & {
|
|
8
|
+
readonly device?: Device;
|
|
9
|
+
};
|
|
10
|
+
export type AudioContextFactory = (sampleRate: number | undefined) => Promise<AudioContext>;
|
|
11
|
+
export interface GlobalMediaId extends PlaybackId {
|
|
12
|
+
}
|
|
13
|
+
declare global {
|
|
14
|
+
interface AudioSinkOptions {
|
|
15
|
+
type: 'none';
|
|
16
|
+
}
|
|
17
|
+
interface AudioContext {
|
|
18
|
+
setSinkId?: (sinkId: string | undefined | AudioSinkOptions) => Promise<undefined>;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export declare class AudioContextCollection {
|
|
22
|
+
readonly audioContextFactory: AudioContextFactory;
|
|
23
|
+
readonly entries: Array<{
|
|
24
|
+
sampleRate: undefined | number;
|
|
25
|
+
audioContext: AudioContext;
|
|
26
|
+
references: number;
|
|
27
|
+
}>;
|
|
28
|
+
constructor(audioContextFactory: AudioContextFactory);
|
|
29
|
+
acquire(sampleRate: number | undefined): Promise<AudioContext>;
|
|
30
|
+
release(audioContext: BaseAudioContext): Promise<void>;
|
|
31
|
+
close(): void;
|
|
32
|
+
}
|
|
33
|
+
export declare class MediaController {
|
|
34
|
+
readonly audioContexts: AudioContextCollection;
|
|
35
|
+
readonly sink: CaptureSink;
|
|
36
|
+
playbackWorker: PlaybackWorker | undefined;
|
|
37
|
+
globalVolume: number;
|
|
38
|
+
playbacks: Map<string, {
|
|
39
|
+
id: GlobalMediaId | undefined;
|
|
40
|
+
volume: Backend.Volume;
|
|
41
|
+
playback: AudioPlayback;
|
|
42
|
+
}>;
|
|
43
|
+
setPlaybackStrand: Strand;
|
|
44
|
+
create(audioContexts: AudioContextCollection, sink: CaptureSink): Promise<MediaController>;
|
|
45
|
+
constructor(audioContexts: AudioContextCollection, sink: CaptureSink);
|
|
46
|
+
receivePacket(connectionId: number, packet: Uint8Array): void;
|
|
47
|
+
createAudioPlayback(parameters: CreateAudioPlaybackParameters): Promise<AudioPlayback>;
|
|
48
|
+
createAudioCapture(parameters: CreateAudioCaptureParameters): Promise<AudioCapture>;
|
|
49
|
+
createVideoCapture(ms: MediaStream, customType?: string): Promise<VideoCapture>;
|
|
50
|
+
playbackSetDevice(device?: Device): Promise<void>;
|
|
51
|
+
playbackSetGlobalVolume(value: Backend.Volume): void;
|
|
52
|
+
playbackStart(uid: string, id: GlobalMediaId): void;
|
|
53
|
+
playbackStop(uid: string, id: GlobalMediaId): void;
|
|
54
|
+
playbackGetActivity(uid: string): Backend.Activity;
|
|
55
|
+
playbackSetVolume(uid: string, volume: Backend.Volume): void;
|
|
56
|
+
playbackGetVolume(uid: string): Backend.Volume;
|
|
57
|
+
closePlayback(uid: string): void;
|
|
58
|
+
close(): void;
|
|
59
|
+
}
|
|
60
|
+
export declare class Device implements Backend.Device {
|
|
61
|
+
readonly device: MediaDeviceInfo;
|
|
62
|
+
readonly isDefault: boolean;
|
|
63
|
+
constructor(device: MediaDeviceInfo, isDefault?: boolean);
|
|
64
|
+
get type(): "AudioPlayback" | "AudioCapture";
|
|
65
|
+
get name(): string;
|
|
66
|
+
}
|
|
@@ -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,30 @@
|
|
|
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 _ProcessingSetDevice;
|
|
14
|
+
private _DeviceCb;
|
|
15
|
+
get vad(): Backend.VadConfig;
|
|
16
|
+
get apm(): Backend.ApmConfig;
|
|
17
|
+
get volume(): Backend.Volume;
|
|
18
|
+
get activity(): import("../../worker/common").Activity;
|
|
19
|
+
get speechProbability(): number;
|
|
20
|
+
get deviceId(): string | undefined;
|
|
21
|
+
constructor(controller: MediaController, parameters: CreateAudioCaptureParameters);
|
|
22
|
+
start(room: RoomConnection, media: GlobalMediaId, uid: Backend.UID): void;
|
|
23
|
+
stop(room: RoomConnection, media: GlobalMediaId): void;
|
|
24
|
+
setVad(value: Backend.VadConfig): void;
|
|
25
|
+
setApm(config: Backend.ApmConfig): Promise<void>;
|
|
26
|
+
setDevice(parameters: DeviceParameters): Promise<void>;
|
|
27
|
+
setVolume(value: Backend.Volume): Promise<void>;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
_UpdateMedias(): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
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 { Silence } from '../../worker/common';
|
|
11
|
+
import { logger } from '../index';
|
|
12
|
+
import { createCaptureWorker } from '../../worker';
|
|
13
|
+
import { openCaptureDevice, stopMediaStream, enumerateDevices, } from '../deviceManager';
|
|
14
|
+
const defaultVad = {
|
|
15
|
+
voiceActivity: {
|
|
16
|
+
attackThreshold: 0.9,
|
|
17
|
+
releaseThreshold: 0.8,
|
|
18
|
+
},
|
|
19
|
+
volumeGate: {
|
|
20
|
+
attackThreshold: -30,
|
|
21
|
+
releaseThreshold: -40,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const defaultApm = {
|
|
25
|
+
echoCanceller: false,
|
|
26
|
+
highPassFilter: false,
|
|
27
|
+
preAmplifier: false,
|
|
28
|
+
captureLevelAdjustment: false,
|
|
29
|
+
noiseSuppression: 'None',
|
|
30
|
+
transientSuppressor: false,
|
|
31
|
+
gainController: false,
|
|
32
|
+
};
|
|
33
|
+
export class AudioCapture {
|
|
34
|
+
get vad() {
|
|
35
|
+
return this._Vad;
|
|
36
|
+
}
|
|
37
|
+
get apm() {
|
|
38
|
+
return this._Apm;
|
|
39
|
+
}
|
|
40
|
+
get volume() {
|
|
41
|
+
return this._Volume;
|
|
42
|
+
}
|
|
43
|
+
get activity() {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
return (_b = (_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.activity) !== null && _b !== void 0 ? _b : Silence;
|
|
46
|
+
}
|
|
47
|
+
get speechProbability() {
|
|
48
|
+
var _a, _b;
|
|
49
|
+
return (_b = (_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.speechProbability) !== null && _b !== void 0 ? _b : 0;
|
|
50
|
+
}
|
|
51
|
+
get deviceId() {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
return (_b = (_a = this._DeviceParameters) === null || _a === void 0 ? void 0 : _a.device) === null || _b === void 0 ? void 0 : _b.device.deviceId;
|
|
54
|
+
}
|
|
55
|
+
constructor(controller, parameters) {
|
|
56
|
+
this.controller = controller;
|
|
57
|
+
this._Medias = new Map();
|
|
58
|
+
this._Vad = defaultVad;
|
|
59
|
+
this._Apm = defaultApm;
|
|
60
|
+
this._Volume = 1;
|
|
61
|
+
this._ProcessingSetDevice = false;
|
|
62
|
+
this._DeviceCb = () => __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
logger.debug(['Audio'], 'AudioCapture._DeviceCb()');
|
|
65
|
+
const devices = yield enumerateDevices();
|
|
66
|
+
const isListed = devices.some((device) => {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
return (device.device.deviceId ===
|
|
69
|
+
((_b = (_a = this._DeviceParameters) === null || _a === void 0 ? void 0 : _a.device) === null || _b === void 0 ? void 0 : _b.device.deviceId));
|
|
70
|
+
});
|
|
71
|
+
if (!isListed) {
|
|
72
|
+
(_b = (_a = this._DeviceParameters) === null || _a === void 0 ? void 0 : _a.onStatusChanged) === null || _b === void 0 ? void 0 : _b.call(_a, 'stopped');
|
|
73
|
+
navigator.mediaDevices.removeEventListener('devicechange', this._DeviceCb);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (parameters.apm) {
|
|
77
|
+
this.setApm(parameters.apm);
|
|
78
|
+
}
|
|
79
|
+
if (parameters.vad) {
|
|
80
|
+
this.setVad(parameters.vad);
|
|
81
|
+
}
|
|
82
|
+
if (parameters.volume) {
|
|
83
|
+
this.setVolume(parameters.volume);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
start(room, media, uid) {
|
|
87
|
+
var _a;
|
|
88
|
+
logger.debug(['Audio'], 'AudioCapture.start()', media);
|
|
89
|
+
let entry = this._Medias.get(media.connectionId);
|
|
90
|
+
if (entry === undefined) {
|
|
91
|
+
this._Medias.set(media.connectionId, (entry = new Set()));
|
|
92
|
+
}
|
|
93
|
+
if (entry.has(media.mediaId))
|
|
94
|
+
return;
|
|
95
|
+
entry.add(media.mediaId);
|
|
96
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
97
|
+
room
|
|
98
|
+
.typedRequest('StartMedia', {
|
|
99
|
+
media_id: media.mediaId,
|
|
100
|
+
properties: { kind: 'audio', uid },
|
|
101
|
+
})
|
|
102
|
+
.catch((reason) => console.error(`StartMedia got rejected; ${reason}`));
|
|
103
|
+
}
|
|
104
|
+
this._UpdateMedias();
|
|
105
|
+
}
|
|
106
|
+
stop(room, media) {
|
|
107
|
+
var _a;
|
|
108
|
+
logger.debug(['Audio'], 'AudioCapture.stop()', media);
|
|
109
|
+
const entry = this._Medias.get(media.connectionId);
|
|
110
|
+
if (!(entry === null || entry === void 0 ? void 0 : entry.delete(media.mediaId)))
|
|
111
|
+
return;
|
|
112
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
113
|
+
room
|
|
114
|
+
.typedRequest('StopMedia', { media_id: media.mediaId })
|
|
115
|
+
.catch((reason) => console.error(`StopMedia got rejected; ${reason}`));
|
|
116
|
+
}
|
|
117
|
+
this._UpdateMedias();
|
|
118
|
+
}
|
|
119
|
+
setVad(value) {
|
|
120
|
+
logger.debug(['Audio'], 'AudioCapture.setVad() config', value);
|
|
121
|
+
this._Vad = value;
|
|
122
|
+
if (this._Capture) {
|
|
123
|
+
this._Capture.worker.setVad(value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
setApm(config) {
|
|
127
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
logger.debug(['Audio'], 'AudioCapture.setApm() config', config);
|
|
129
|
+
this._Apm = config;
|
|
130
|
+
if (this._DeviceParameters) {
|
|
131
|
+
yield this.setDevice(this._DeviceParameters);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
setDevice(parameters) {
|
|
136
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
+
var _a;
|
|
138
|
+
logger.debug(['Device'], 'AudioCapture.setDevice() parameters: ', parameters);
|
|
139
|
+
const rollbackParams = this._DeviceParameters;
|
|
140
|
+
this._DeviceParameters = parameters;
|
|
141
|
+
if (this._Volume === 'muted') {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const device = yield openCaptureDevice(this.controller, parameters, this._Apm);
|
|
145
|
+
if (device.type !== 'Success') {
|
|
146
|
+
logger.error([
|
|
147
|
+
'Device',
|
|
148
|
+
`AudioCapture.setDevice() Unable to open capture device; ${device.reason}`,
|
|
149
|
+
]);
|
|
150
|
+
this._DeviceParameters = rollbackParams;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (this._DeviceParameters.onStatusChanged) {
|
|
154
|
+
navigator.mediaDevices.addEventListener('devicechange', this._DeviceCb);
|
|
155
|
+
}
|
|
156
|
+
if (this._Capture !== undefined) {
|
|
157
|
+
stopMediaStream(this._Capture.source.mediaStream);
|
|
158
|
+
this._Capture.source.disconnect(this._Capture.worker.worklet);
|
|
159
|
+
}
|
|
160
|
+
if (this._Capture === undefined ||
|
|
161
|
+
this._Capture.worker.worklet.context !== device.value.context) {
|
|
162
|
+
(_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.close();
|
|
163
|
+
this._Capture = {
|
|
164
|
+
worker: yield createCaptureWorker(device.value.context, this.controller.sink),
|
|
165
|
+
source: device.value,
|
|
166
|
+
};
|
|
167
|
+
yield this._UpdateMedias();
|
|
168
|
+
yield this.setVolume(this._Volume);
|
|
169
|
+
this.setVad(this.vad);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
this._Capture.source = device.value;
|
|
173
|
+
}
|
|
174
|
+
this._Capture.source.connect(this._Capture.worker.worklet);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
setVolume(value) {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
var _a;
|
|
180
|
+
logger.debug(['Audio'], 'AudioCapture.setVolume()', value);
|
|
181
|
+
this._Volume = value;
|
|
182
|
+
if (this._ProcessingSetDevice)
|
|
183
|
+
return;
|
|
184
|
+
this._ProcessingSetDevice = true;
|
|
185
|
+
if (value === 'muted') {
|
|
186
|
+
yield this.close();
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
if (!this._Capture && this._DeviceParameters) {
|
|
190
|
+
yield this.setDevice(this._DeviceParameters);
|
|
191
|
+
}
|
|
192
|
+
(_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.setVolume(value);
|
|
193
|
+
}
|
|
194
|
+
this._ProcessingSetDevice = false;
|
|
195
|
+
if (value !== this._Volume) {
|
|
196
|
+
yield this.setVolume(this._Volume);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
close() {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
logger.debug(['Audio'], 'AudioCapture.close()');
|
|
203
|
+
if (this._Capture !== undefined) {
|
|
204
|
+
this._Capture.worker.close();
|
|
205
|
+
stopMediaStream(this._Capture.source.mediaStream);
|
|
206
|
+
this._Capture.source.disconnect(this._Capture.worker.worklet);
|
|
207
|
+
yield this.controller.audioContexts.release(this._Capture.source.context);
|
|
208
|
+
}
|
|
209
|
+
navigator.mediaDevices.removeEventListener('devicechange', this._DeviceCb);
|
|
210
|
+
this._Capture = undefined;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
_UpdateMedias() {
|
|
214
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
215
|
+
logger.debug(['Audio'], 'AudioCapture._UpdateMedias()');
|
|
216
|
+
if (this._Capture !== undefined) {
|
|
217
|
+
const connections = Array.from(this._Medias).map(([key, value]) => {
|
|
218
|
+
return { id: key, mediaIds: Array.from(value) };
|
|
219
|
+
});
|
|
220
|
+
this._Capture.worker.setMedias(connections);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Backend } from '@4players/odin-common';
|
|
2
|
+
import { GlobalMediaId, MediaController } from '../mediaController';
|
|
3
|
+
export declare class AudioPlayback implements Backend.AudioPlayback {
|
|
4
|
+
readonly controller: MediaController;
|
|
5
|
+
readonly uid: Backend.UID;
|
|
6
|
+
get volume(): Backend.Volume;
|
|
7
|
+
get activity(): Backend.Activity;
|
|
8
|
+
constructor(controller: MediaController, uid: Backend.UID);
|
|
9
|
+
start(id: GlobalMediaId): void;
|
|
10
|
+
stop(id: GlobalMediaId): void;
|
|
11
|
+
setVolume(value: Backend.Volume): Promise<void>;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { logger } from '../config';
|
|
11
|
+
export class AudioPlayback {
|
|
12
|
+
get volume() {
|
|
13
|
+
return this.controller.playbackGetVolume(this.uid);
|
|
14
|
+
}
|
|
15
|
+
get activity() {
|
|
16
|
+
return this.controller.playbackGetActivity(this.uid);
|
|
17
|
+
}
|
|
18
|
+
constructor(controller, uid) {
|
|
19
|
+
this.controller = controller;
|
|
20
|
+
this.uid = uid;
|
|
21
|
+
}
|
|
22
|
+
start(id) {
|
|
23
|
+
logger.debug(['Audio'], 'AudioPlayback.start()', id);
|
|
24
|
+
this.controller.playbackStart(this.uid, id);
|
|
25
|
+
}
|
|
26
|
+
stop(id) {
|
|
27
|
+
logger.debug(['Audio'], 'AudioPlayback.stop()', id);
|
|
28
|
+
this.controller.playbackStop(this.uid, id);
|
|
29
|
+
}
|
|
30
|
+
setVolume(value) {
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
logger.debug(['Audio'], 'AudioPlayback.setVolume()', value);
|
|
33
|
+
this.controller.playbackSetVolume(this.uid, value);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
close() {
|
|
37
|
+
logger.debug(['Audio'], 'AudioPlayback.close()');
|
|
38
|
+
this.controller.closePlayback(this.uid);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GlobalMediaId, MediaController } from '../mediaController';
|
|
2
|
+
import { Backend } from '@4players/odin-common';
|
|
3
|
+
import { RoomConnection } from '../roomConnection';
|
|
4
|
+
export declare class VideoCapture implements Backend.VideoCapture {
|
|
5
|
+
readonly controller: MediaController;
|
|
6
|
+
readonly customType?: string | undefined;
|
|
7
|
+
private _mediaStream?;
|
|
8
|
+
private readonly _Medias;
|
|
9
|
+
constructor(controller: MediaController, customType?: string | undefined);
|
|
10
|
+
get mediaStream(): MediaStream | undefined;
|
|
11
|
+
setDevice(ms: MediaStream): Promise<void>;
|
|
12
|
+
start(room: RoomConnection, media: GlobalMediaId, uid: Backend.UID): void;
|
|
13
|
+
stop(room: RoomConnection, media: GlobalMediaId): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { stopMediaStream } from '../deviceManager';
|
|
11
|
+
export class VideoCapture {
|
|
12
|
+
constructor(controller, customType) {
|
|
13
|
+
this.controller = controller;
|
|
14
|
+
this.customType = customType;
|
|
15
|
+
this._Medias = new Map();
|
|
16
|
+
}
|
|
17
|
+
get mediaStream() {
|
|
18
|
+
return this._mediaStream;
|
|
19
|
+
}
|
|
20
|
+
setDevice(ms) {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
// @TODO: Stop old ms before
|
|
23
|
+
this._mediaStream = ms;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
start(room, media, uid) {
|
|
27
|
+
var _a;
|
|
28
|
+
let entry = this._Medias.get(media.connectionId);
|
|
29
|
+
if (entry === undefined) {
|
|
30
|
+
this._Medias.set(media.connectionId, (entry = new Set()));
|
|
31
|
+
}
|
|
32
|
+
if (entry.has(media.mediaId))
|
|
33
|
+
return;
|
|
34
|
+
entry.add(media.mediaId);
|
|
35
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
36
|
+
const properties = {
|
|
37
|
+
kind: 'video',
|
|
38
|
+
codec: 'VP8',
|
|
39
|
+
uid,
|
|
40
|
+
};
|
|
41
|
+
if (this.customType) {
|
|
42
|
+
properties.customType = this.customType;
|
|
43
|
+
}
|
|
44
|
+
room
|
|
45
|
+
.typedRequest('StartMedia', {
|
|
46
|
+
media_id: media.mediaId,
|
|
47
|
+
properties,
|
|
48
|
+
})
|
|
49
|
+
.catch((reason) => console.error(`StartMedia got rejected; ${reason}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
stop(room, media) {
|
|
53
|
+
var _a;
|
|
54
|
+
if (this._mediaStream) {
|
|
55
|
+
stopMediaStream(this._mediaStream);
|
|
56
|
+
}
|
|
57
|
+
const entry = this._Medias.get(media.connectionId);
|
|
58
|
+
if (!(entry === null || entry === void 0 ? void 0 : entry.delete(media.mediaId)))
|
|
59
|
+
return;
|
|
60
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
61
|
+
room
|
|
62
|
+
.typedRequest('StopMedia', { media_id: media.mediaId })
|
|
63
|
+
.catch((reason) => console.error(`StopMedia got rejected; ${reason}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
close() {
|
|
67
|
+
throw new Error('Method not implemented.');
|
|
68
|
+
}
|
|
69
|
+
}
|