@4players/odin-plugin-web 1.0.0-alpha.14 → 1.0.0-alpha.16

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 -293
  15. package/lib/plugin/{audioController.d.ts → mediaController.d.ts} +6 -47
  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 +206 -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 -530
  39. /package/lib/plugin/{websocket.d.ts → webrtc/websocket.d.ts} +0 -0
@@ -1,23 +1,33 @@
1
1
  import { Backend } from '@4players/odin-common';
2
- import { AudioCapture, AudioContextFactory, AudioPlayback, CreateAudioCaptureParameters, CreateAudioPlaybackParameters, Device } from './audioController';
2
+ import { AudioContextFactory, CreateAudioCaptureParameters, CreateAudioPlaybackParameters, Device } from './mediaController';
3
3
  import { ConnectionPool } from './connectionPool';
4
4
  import { MainConnection } from './mainConnection';
5
+ import { AudioCapture } from './medias/audio-capture';
6
+ import { VideoCapture } from './medias/video-capture';
7
+ import { VideoPlayback } from './medias/video-playback';
8
+ import { AudioPlayback } from './medias/audio-playback';
9
+ import JoinRoomParameters = Backend.JoinRoomParameters;
10
+ export type { LogFilters } from '@4players/odin-common';
11
+ export { logger } from './config';
12
+ type Plugin = Backend.Plugin;
5
13
  export interface DeviceParameters {
6
14
  readonly device?: Device;
7
15
  onStatusChanged?: Backend.OnStatusChanged;
8
16
  }
9
- export declare class WebPlugin implements Backend.Plugin {
17
+ export declare class WebPlugin implements Plugin {
10
18
  readonly version = "1";
11
19
  private _NextRoomId;
12
20
  private _PlaybackVolume;
13
21
  private readonly _ConnectionPool;
14
- private readonly _AudioController;
22
+ private readonly _MediaController;
15
23
  get playbackVolume(): Backend.Volume;
16
24
  static create(audioContextFactory: AudioContextFactory): Promise<WebPlugin>;
17
25
  private constructor();
18
- joinRoom(parameters: Backend.JoinRoomParameters): Room;
26
+ joinRoom(parameters: JoinRoomParameters): Room;
19
27
  createAudioPlayback(parameters: CreateAudioPlaybackParameters): Promise<AudioPlayback>;
20
28
  createAudioCapture(parameters: CreateAudioCaptureParameters): Promise<AudioCapture>;
29
+ createVideoCapture(ms: MediaStream, customType?: string): Promise<VideoCapture>;
30
+ createVideoPlayback(parameters: Backend.CreateVideoPlaybackParameters): Promise<VideoPlayback>;
21
31
  enumerateDevices(): Promise<Device[]>;
22
32
  setOutputVolume(volume: Backend.Volume): void;
23
33
  setOutputDevice(device: DeviceParameters): Promise<void>;
@@ -32,9 +42,10 @@ export declare class Room implements Backend.Room {
32
42
  private readonly _Peers;
33
43
  private readonly _AvailableMediaIds;
34
44
  get mainConnection(): MainConnection | undefined;
35
- constructor(id: number, connectionPool: ConnectionPool, parameters: Backend.JoinRoomParameters);
45
+ constructor(id: number, connectionPool: ConnectionPool, parameters: JoinRoomParameters);
46
+ get token(): string;
36
47
  request(method: string, properties: unknown): Promise<unknown>;
37
- link(media: Backend.Media): void;
48
+ link(media: Backend.Media): Promise<void>;
38
49
  unlink(media: Backend.Media): void;
39
50
  close(): void;
40
51
  private _HandleEvent;
@@ -7,23 +7,32 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { assert, unwrap, RoomNotificationsRpc, generateUUID, } from '@4players/odin-common';
11
- import { AudioCapture, AudioContextCollection, AudioController, AudioPlayback, } from './audioController';
10
+ import { Backend, assert, unwrap, RoomNotificationsRpc, generateUUID, OdinError, } from '@4players/odin-common';
11
+ import { AudioContextCollection, MediaController, } from './mediaController';
12
12
  import { ConnectionPool } from './connectionPool';
13
13
  import { RoomConnection } from './roomConnection';
14
14
  import { parseRpcMessage } from './rpcConnection';
15
15
  import { Token } from './token';
16
+ import { AudioCapture } from './medias/audio-capture';
17
+ import { enumerateDevices } from './deviceManager';
18
+ import { VideoCapture } from './medias/video-capture';
19
+ import { VideoPlayback } from './medias/video-playback';
20
+ import { AudioPlayback } from './medias/audio-playback';
21
+ import { logger } from './config';
22
+ var Transport = Backend.Transport;
23
+ export { logger } from './config';
16
24
  export class WebPlugin {
17
25
  get playbackVolume() {
18
26
  return this._PlaybackVolume;
19
27
  }
20
28
  static create(audioContextFactory) {
21
29
  return __awaiter(this, void 0, void 0, function* () {
30
+ logger.info(['Other'], 'Using ODIN Plugin Web!');
22
31
  // eslint-disable-next-line prefer-const
23
32
  let audioController;
24
33
  const pool = new ConnectionPool((connectionId, packet) => audioController.receivePacket(connectionId, packet));
25
34
  const audioContexts = new AudioContextCollection(audioContextFactory);
26
- audioController = new AudioController(audioContexts, pool.sendDatagram.bind(pool));
35
+ audioController = new MediaController(audioContexts, pool.sendDatagram.bind(pool));
27
36
  return new WebPlugin(pool, audioController);
28
37
  });
29
38
  }
@@ -32,34 +41,45 @@ export class WebPlugin {
32
41
  this._NextRoomId = 0;
33
42
  this._PlaybackVolume = 1;
34
43
  this._ConnectionPool = connectionPool;
35
- this._AudioController = audioController;
44
+ this._MediaController = audioController;
36
45
  }
37
46
  joinRoom(parameters) {
38
47
  return new Room(this._NextRoomId++, this._ConnectionPool, parameters);
39
48
  }
40
49
  createAudioPlayback(parameters) {
41
50
  return __awaiter(this, void 0, void 0, function* () {
42
- return this._AudioController.createAudioPlayback(parameters);
51
+ return this._MediaController.createAudioPlayback(parameters);
43
52
  });
44
53
  }
45
54
  createAudioCapture(parameters) {
46
55
  return __awaiter(this, void 0, void 0, function* () {
47
- return this._AudioController.createAudioCapture(parameters);
56
+ return this._MediaController.createAudioCapture(parameters);
57
+ });
58
+ }
59
+ createVideoCapture(ms, customType) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ return this._MediaController.createVideoCapture(ms, customType);
62
+ });
63
+ }
64
+ createVideoPlayback(parameters) {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ return new VideoPlayback(parameters.uid, parameters.customType);
48
67
  });
49
68
  }
50
69
  enumerateDevices() {
51
- return this._AudioController.enumerateDevices();
70
+ return enumerateDevices();
52
71
  }
53
72
  setOutputVolume(volume) {
54
- this._AudioController.playbackSetGlobalVolume(volume);
73
+ this._MediaController.playbackSetGlobalVolume(volume);
55
74
  }
56
75
  setOutputDevice(device) {
57
76
  return __awaiter(this, void 0, void 0, function* () {
58
- yield this._AudioController.playbackSetDevice(device.device);
77
+ yield this._MediaController.playbackSetDevice(device.device);
59
78
  });
60
79
  }
61
80
  close() {
62
- this._AudioController.close();
81
+ logger.debug(['Other', 'Plugin.close()']);
82
+ this._MediaController.close();
63
83
  this._ConnectionPool.close();
64
84
  }
65
85
  }
@@ -68,7 +88,7 @@ export class Room {
68
88
  return this._Connection.mainConnection;
69
89
  }
70
90
  constructor(id, connectionPool, parameters) {
71
- var _a, _b, _c;
91
+ var _a, _b, _c, _d;
72
92
  this._Captures = new Map();
73
93
  this._Playbacks = new Map();
74
94
  this._Peers = new Map();
@@ -81,69 +101,126 @@ export class Room {
81
101
  assert(roomId !== undefined, 'room id required for multi-room-tokens');
82
102
  this._Id = id;
83
103
  this._OnEvent = onEvent;
84
- this._Connection = new RoomConnection(connectionPool, this._HandleEvent.bind(this), gateway ? new URL(gateway) : undefined, token, roomId, userData, position);
104
+ this._Connection = new RoomConnection(connectionPool, (_d = parameters.transport) !== null && _d !== void 0 ? _d : Transport.H3, this._HandleEvent.bind(this), gateway ? new URL(gateway) : undefined, token, roomId, userData, position);
105
+ }
106
+ get token() {
107
+ return this._Connection.token.text;
85
108
  }
86
109
  request(method, properties) {
87
110
  return this._Connection.request(method, properties);
88
111
  }
89
112
  link(media) {
90
- var _a;
91
- if (media instanceof AudioPlayback) {
92
- const entry = (_a = this._Playbacks.get(media.uid)) !== null && _a !== void 0 ? _a : {
93
- media: undefined,
94
- playback: undefined,
95
- };
96
- if (entry.playback !== undefined) {
97
- this.unlink(entry.playback);
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ var _a, _b, _c, _d;
115
+ if (media instanceof AudioPlayback) {
116
+ const entry = (_a = this._Playbacks.get(media.uid)) !== null && _a !== void 0 ? _a : {
117
+ media: undefined,
118
+ playback: undefined,
119
+ };
120
+ if (entry.playback !== undefined) {
121
+ this.unlink(entry.playback);
122
+ }
123
+ if (entry.media !== undefined) {
124
+ media.start(entry.media);
125
+ }
126
+ entry.playback = media;
127
+ this._Playbacks.set(media.uid, entry);
98
128
  }
99
- if (entry.media !== undefined) {
100
- media.start(entry.media);
129
+ else if (media instanceof AudioCapture) {
130
+ let entry = this._Captures.get(media);
131
+ if (entry) {
132
+ this.unlink(media);
133
+ }
134
+ const mediaId = this.mainConnection !== undefined && this._AvailableMediaIds.shift();
135
+ entry = {
136
+ media: mediaId
137
+ ? {
138
+ connectionId: this.mainConnection.id,
139
+ mediaId,
140
+ }
141
+ : undefined,
142
+ uid: generateUUID(),
143
+ };
144
+ this._Captures.set(media, entry);
145
+ if (entry.media !== undefined) {
146
+ media.start(this._Connection, entry.media, entry.uid);
147
+ }
101
148
  }
102
- entry.playback = media;
103
- this._Playbacks.set(media.uid, entry);
104
- }
105
- else if (media instanceof AudioCapture) {
106
- let entry = this._Captures.get(media);
107
- if (entry) {
108
- this.unlink(media);
149
+ else if (media instanceof VideoCapture) {
150
+ let mediaEntry = this._Captures.get(media);
151
+ if (mediaEntry) {
152
+ this.unlink(media);
153
+ }
154
+ if (media.mediaStream) {
155
+ const mediaId = this.mainConnection !== undefined && this._AvailableMediaIds.shift();
156
+ mediaEntry = {
157
+ media: mediaId
158
+ ? {
159
+ connectionId: this.mainConnection.id,
160
+ mediaId,
161
+ }
162
+ : undefined,
163
+ uid: generateUUID(),
164
+ };
165
+ this._Captures.set(media, mediaEntry);
166
+ if (mediaEntry.media !== undefined) {
167
+ // @TODO Check if we also need to send the StartMedia rpc
168
+ (_b = this.mainConnection) === null || _b === void 0 ? void 0 : _b.startLocalVideo(mediaEntry.media.mediaId, media.mediaStream.getTracks()[0]);
169
+ media.start(this._Connection, mediaEntry.media, mediaEntry.uid);
170
+ }
171
+ }
109
172
  }
110
- const mediaId = this.mainConnection !== undefined && this._AvailableMediaIds.shift();
111
- entry = {
112
- media: mediaId
113
- ? {
114
- connectionId: this.mainConnection.id,
115
- mediaId,
173
+ else if (media instanceof VideoPlayback) {
174
+ const entry = (_c = this._Playbacks.get(media.uid)) !== null && _c !== void 0 ? _c : {
175
+ media: undefined,
176
+ playback: undefined,
177
+ };
178
+ if (entry.playback !== undefined) {
179
+ this.unlink(entry.playback);
180
+ }
181
+ if (entry.media !== undefined) {
182
+ const track = yield ((_d = this.mainConnection) === null || _d === void 0 ? void 0 : _d.startRemoteVideo(entry.media.mediaId));
183
+ if (track) {
184
+ media.setMediaStream(new MediaStream([track]));
116
185
  }
117
- : undefined,
118
- uid: generateUUID(),
119
- };
120
- this._Captures.set(media, entry);
121
- if (entry.media !== undefined) {
122
- media.start(this._Connection, entry.media, entry.uid);
186
+ }
187
+ entry.playback = media;
188
+ this._Playbacks.set(media.uid, entry);
123
189
  }
124
- }
125
- else {
126
- throw new Error('invalid media');
127
- }
190
+ else {
191
+ throw new OdinError('invalid media');
192
+ }
193
+ });
128
194
  }
129
195
  unlink(media) {
130
- if (media instanceof AudioPlayback) {
196
+ var _a, _b;
197
+ if (media instanceof AudioPlayback || media instanceof VideoPlayback) {
131
198
  const entry = this._Playbacks.get(media.uid);
132
199
  if ((entry === null || entry === void 0 ? void 0 : entry.playback) !== media)
133
200
  return;
134
201
  entry.playback = undefined;
135
202
  if (entry.media !== undefined) {
136
- media.stop(entry.media);
203
+ if (media instanceof AudioPlayback) {
204
+ media.stop(entry.media);
205
+ }
206
+ else {
207
+ (_a = this.mainConnection) === null || _a === void 0 ? void 0 : _a.stopRemoteVideo(entry.media.mediaId);
208
+ // @TODO check if the MediaStream and its tracks have to get closed manually.
209
+ media.setMediaStream(undefined);
210
+ }
137
211
  }
138
212
  this._Playbacks.set(media.uid, entry);
139
213
  }
140
- else if (media instanceof AudioCapture) {
214
+ else if (media instanceof AudioCapture || media instanceof VideoCapture) {
141
215
  const entry = this._Captures.get(media);
142
216
  if (entry === undefined)
143
217
  return;
144
218
  if (entry.media !== undefined) {
145
219
  media.stop(this._Connection, entry.media);
146
220
  this._AvailableMediaIds.push(entry.media.mediaId);
221
+ if (media instanceof VideoCapture) {
222
+ (_b = this.mainConnection) === null || _b === void 0 ? void 0 : _b.stopLocalVideo(entry.media.mediaId);
223
+ }
147
224
  }
148
225
  this._Captures.delete(media);
149
226
  }
@@ -152,6 +229,7 @@ export class Room {
152
229
  }
153
230
  }
154
231
  close() {
232
+ logger.debug(['Other', 'Room.close()']);
155
233
  this._Connection.close();
156
234
  }
157
235
  _HandleEvent(method, properties) {
@@ -163,7 +241,6 @@ export class Room {
163
241
  this._HandleKnownEvent(rpc);
164
242
  properties = rpc.properties;
165
243
  }
166
- console.log(`handle event ${method} ${this._OnEvent !== undefined}`);
167
244
  this._OnEvent(method, properties);
168
245
  }
169
246
  _HandleKnownEvent(event) {
@@ -190,8 +267,14 @@ export class Room {
190
267
  for (const [capture, entry] of this._Captures) {
191
268
  if (entry.media === undefined)
192
269
  continue;
193
- capture.stop(this._Connection, entry.media);
194
- entry.media = undefined;
270
+ if (capture instanceof VideoCapture) {
271
+ // @TODO Check if it needs implementation
272
+ logger.debug(['Connection', 'Video'], '_HandleConnectionLost() VideoCapture');
273
+ }
274
+ if (capture instanceof AudioCapture) {
275
+ capture.stop(this._Connection, entry.media);
276
+ entry.media = undefined;
277
+ }
195
278
  }
196
279
  this._AvailableMediaIds.length = 0;
197
280
  }
@@ -221,7 +304,12 @@ export class Room {
221
304
  connectionId: this.mainConnection.id,
222
305
  mediaId: this._AvailableMediaIds.shift(),
223
306
  };
224
- capture.start(this._Connection, entry.media, entry.uid);
307
+ if (capture instanceof AudioCapture) {
308
+ capture.start(this._Connection, entry.media, entry.uid);
309
+ }
310
+ if (capture instanceof VideoCapture) {
311
+ // @TODO Check if it needs implementation
312
+ }
225
313
  }
226
314
  }
227
315
  _HandlePeerUpdate(update) {
@@ -254,10 +342,11 @@ export class Room {
254
342
  if (this.mainConnection === undefined)
255
343
  return;
256
344
  const peer = this._Peers.get(peerId);
257
- if (peer === undefined || media.properties.kind !== 'audio')
345
+ if (peer === undefined ||
346
+ (media.properties.kind !== 'audio' && media.properties.kind !== 'video')) {
258
347
  return;
348
+ }
259
349
  const uid = String((_a = media.properties.uid) !== null && _a !== void 0 ? _a : generateUUID());
260
- console.log(`uuid ${media.properties.uid} ${uid}`);
261
350
  media.properties.uid = uid; // feed the uid back, so it can be seen be seen down the chain.
262
351
  const entry = (_b = this._Playbacks.get(uid)) !== null && _b !== void 0 ? _b : {
263
352
  media: undefined,
@@ -270,10 +359,17 @@ export class Room {
270
359
  mediaId: media.id,
271
360
  };
272
361
  if (entry.playback !== undefined) {
273
- entry.playback.start(entry.media);
362
+ if (entry.playback instanceof AudioPlayback) {
363
+ entry.playback.start(entry.media);
364
+ }
365
+ if (entry.playback instanceof VideoPlayback) {
366
+ logger.debug(['Video'], '_HandleMediaStart() VideoPlayback');
367
+ this.mainConnection.startRemoteVideo(entry.media.mediaId).then();
368
+ }
274
369
  }
275
370
  }
276
371
  _HandleMediaStopped(peerId, mediaId) {
372
+ var _a;
277
373
  const peer = this._Peers.get(peerId);
278
374
  if (peer === undefined)
279
375
  return;
@@ -284,7 +380,13 @@ export class Room {
284
380
  if (entry === undefined)
285
381
  return;
286
382
  if (entry.media !== undefined && entry.playback !== undefined) {
287
- entry.playback.stop(entry.media);
383
+ if (entry.playback instanceof AudioPlayback) {
384
+ entry.playback.stop(entry.media);
385
+ }
386
+ if (entry.playback instanceof VideoPlayback) {
387
+ logger.debug(['Video', '_HandleMediaStopped() VideoPlayback']);
388
+ (_a = this.mainConnection) === null || _a === void 0 ? void 0 : _a.stopRemoteVideo(entry.media.mediaId);
389
+ }
288
390
  }
289
391
  entry.media = undefined;
290
392
  peer.mediaIds.delete(mediaId);
@@ -1,37 +1,30 @@
1
- import { MainCommands, MainNotifications, Result } from '@4players/odin-common';
1
+ import { Backend, Result } from '@4players/odin-common';
2
2
  import { Token } from './token';
3
- import { TypedRpcConnection } from './rpcConnection';
3
+ import { RpcConnection } from './rpcConnection';
4
+ import Transport = Backend.Transport;
4
5
  export type DatagramHandler = (connectionId: number, datagram: Uint8Array) => void;
5
- export declare class MainConnection {
6
- readonly id: number;
7
- readonly url: URL;
8
- readonly onDatagram: DatagramHandler;
9
- readonly connection: TypedRpcConnection<MainCommands, MainNotifications>;
10
- readonly webRtc: RTCPeerConnection;
11
- static create(id: number, url: URL, onDatagram: DatagramHandler, signal: AbortSignal): Promise<Result<MainConnection>>;
12
- private readonly _AudioChannel;
13
- private readonly _PeerIdMap;
14
- private _PeriodicHealthCheck;
15
- private _WebRtcIceTimeout;
16
- private _WebRtcTimeout;
17
- private _ClosePromise;
18
- get closePromise(): Promise<"closed">;
6
+ export declare function createMainConnection(id: number, transport: Transport, address: string, onDatagram: DatagramHandler, signal: AbortSignal): Promise<Result<MainConnection>>;
7
+ export declare enum ConnectionStatus {
8
+ Connecting = 0,
9
+ Connected = 1,
10
+ Closed = 2
11
+ }
12
+ export interface MainConnection {
13
+ get id(): number;
14
+ get transport(): Transport;
15
+ get address(): string;
16
+ get status(): ConnectionStatus;
17
+ get closePromise(): Promise<'closed'>;
19
18
  get closeSignal(): AbortSignal;
20
19
  get closed(): boolean;
21
- webRtcReady(): Promise<boolean>;
22
- private constructor();
20
+ waitForConnected(): Promise<void>;
23
21
  close(): void;
24
- joinRoom(token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]): Promise<Result<WebSocket> | 'closed'>;
25
- getReconnectToken(webSocket: WebSocket): Promise<Result<Token>>;
26
- sendDatagram(datagram: Uint8Array): void;
22
+ joinRoom(token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]): Promise<Result<RpcConnection> | 'closed'>;
23
+ getReconnectToken(peerId: number): Promise<Result<Token>>;
27
24
  healthCheck(): Promise<boolean>;
28
- private _Request;
29
- private _readEvents;
30
- private _SendOffer;
31
- private _OnWebRtcStatusChanged;
32
- private _OnWebRtcUpdate;
33
- private _UpdateRemoteSdp;
34
- private _AddIceCandidate;
35
- private _OnIceCandidate;
36
- private _CleanUp;
25
+ sendDatagram(datagram: Uint8Array): void;
26
+ startLocalVideo(mediaId: number, track: MediaStreamTrack): void;
27
+ stopLocalVideo(mediaId: number): void;
28
+ startRemoteVideo(mediaId: number): Promise<MediaStreamTrack>;
29
+ stopRemoteVideo(mediaId: number): void;
37
30
  }