livekit-client 0.16.6 → 0.17.2
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/dist/api/RequestQueue.js +6 -6
- package/dist/api/RequestQueue.js.map +1 -1
- package/dist/api/SignalClient.d.ts +3 -1
- package/dist/api/SignalClient.js +47 -5
- package/dist/api/SignalClient.js.map +1 -1
- package/dist/connect.js +1 -1
- package/dist/connect.js.map +1 -1
- package/dist/options.d.ts +7 -2
- package/dist/proto/livekit_models.d.ts +33 -0
- package/dist/proto/livekit_models.js +213 -3
- package/dist/proto/livekit_models.js.map +1 -1
- package/dist/proto/livekit_rtc.d.ts +15 -1
- package/dist/proto/livekit_rtc.js +128 -2
- package/dist/proto/livekit_rtc.js.map +1 -1
- package/dist/room/RTCEngine.d.ts +4 -3
- package/dist/room/RTCEngine.js +34 -13
- package/dist/room/RTCEngine.js.map +1 -1
- package/dist/room/Room.js +27 -12
- package/dist/room/Room.js.map +1 -1
- package/dist/room/events.d.ts +6 -1
- package/dist/room/events.js +6 -1
- package/dist/room/events.js.map +1 -1
- package/dist/room/participant/LocalParticipant.d.ts +3 -1
- package/dist/room/participant/LocalParticipant.js +24 -1
- package/dist/room/participant/LocalParticipant.js.map +1 -1
- package/dist/room/participant/RemoteParticipant.d.ts +2 -1
- package/dist/room/participant/RemoteParticipant.js +3 -3
- package/dist/room/participant/RemoteParticipant.js.map +1 -1
- package/dist/room/participant/publishUtils.d.ts +6 -0
- package/dist/room/participant/publishUtils.js +65 -24
- package/dist/room/participant/publishUtils.js.map +1 -1
- package/dist/room/participant/publishUtils.test.js +35 -5
- package/dist/room/participant/publishUtils.test.js.map +1 -1
- package/dist/room/track/LocalAudioTrack.d.ts +2 -0
- package/dist/room/track/LocalAudioTrack.js +23 -0
- package/dist/room/track/LocalAudioTrack.js.map +1 -1
- package/dist/room/track/LocalTrack.d.ts +4 -0
- package/dist/room/track/LocalTrack.js +35 -0
- package/dist/room/track/LocalTrack.js.map +1 -1
- package/dist/room/track/LocalVideoTrack.d.ts +1 -0
- package/dist/room/track/LocalVideoTrack.js +13 -0
- package/dist/room/track/LocalVideoTrack.js.map +1 -1
- package/dist/room/track/RemoteTrack.d.ts +1 -0
- package/dist/room/track/RemoteTrack.js +2 -0
- package/dist/room/track/RemoteTrack.js.map +1 -1
- package/dist/room/track/RemoteVideoTrack.d.ts +4 -2
- package/dist/room/track/RemoteVideoTrack.js +28 -11
- package/dist/room/track/RemoteVideoTrack.js.map +1 -1
- package/dist/room/track/Track.d.ts +5 -1
- package/dist/room/track/Track.js +20 -1
- package/dist/room/track/Track.js.map +1 -1
- package/dist/room/track/create.js +1 -0
- package/dist/room/track/create.js.map +1 -1
- package/dist/room/track/defaults.js +2 -2
- package/dist/room/track/defaults.js.map +1 -1
- package/dist/room/track/options.d.ts +65 -15
- package/dist/room/track/options.js +38 -0
- package/dist/room/track/options.js.map +1 -1
- package/dist/room/track/types.d.ts +11 -0
- package/dist/room/track/utils.d.ts +10 -0
- package/dist/room/track/utils.js +46 -1
- package/dist/room/track/utils.js.map +1 -1
- package/dist/room/utils.d.ts +2 -0
- package/dist/room/utils.js +9 -1
- package/dist/room/utils.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/api/RequestQueue.ts +7 -7
- package/src/api/SignalClient.ts +36 -6
- package/src/connect.ts +1 -1
- package/src/options.ts +12 -3
- package/src/proto/livekit_models.ts +249 -0
- package/src/proto/livekit_rtc.ts +155 -0
- package/src/room/RTCEngine.ts +39 -16
- package/src/room/Room.ts +27 -13
- package/src/room/events.ts +6 -1
- package/src/room/participant/LocalParticipant.ts +31 -4
- package/src/room/participant/RemoteParticipant.ts +4 -4
- package/src/room/participant/publishUtils.test.ts +46 -6
- package/src/room/participant/publishUtils.ts +72 -27
- package/src/room/track/LocalAudioTrack.ts +19 -1
- package/src/room/track/LocalTrack.ts +37 -0
- package/src/room/track/LocalVideoTrack.ts +9 -1
- package/src/room/track/RemoteTrack.ts +3 -0
- package/src/room/track/RemoteVideoTrack.ts +25 -10
- package/src/room/track/Track.ts +16 -2
- package/src/room/track/create.ts +1 -0
- package/src/room/track/defaults.ts +2 -2
- package/src/room/track/options.ts +55 -3
- package/src/room/track/types.ts +12 -0
- package/src/room/track/utils.ts +39 -0
- package/src/room/utils.ts +8 -0
- package/src/version.ts +1 -1
package/src/room/RTCEngine.ts
CHANGED
@@ -3,6 +3,8 @@ import type TypedEventEmitter from 'typed-emitter';
|
|
3
3
|
import { SignalClient, SignalOptions } from '../api/SignalClient';
|
4
4
|
import log from '../logger';
|
5
5
|
import {
|
6
|
+
ClientConfigSetting,
|
7
|
+
ClientConfiguration,
|
6
8
|
DataPacket, DataPacket_Kind, SpeakerInfo, TrackInfo, UserPacket,
|
7
9
|
} from '../proto/livekit_models';
|
8
10
|
import {
|
@@ -14,7 +16,7 @@ import {
|
|
14
16
|
import { ConnectionError, TrackInvalidError, UnexpectedConnectionState } from './errors';
|
15
17
|
import { EngineEvent } from './events';
|
16
18
|
import PCTransport from './PCTransport';
|
17
|
-
import { isFireFox, sleep } from './utils';
|
19
|
+
import { isFireFox, isWeb, sleep } from './utils';
|
18
20
|
|
19
21
|
const lossyDataChannel = '_lossy';
|
20
22
|
const reliableDataChannel = '_reliable';
|
@@ -70,7 +72,9 @@ export default class RTCEngine extends (
|
|
70
72
|
|
71
73
|
private reconnectStart: number = 0;
|
72
74
|
|
73
|
-
private
|
75
|
+
private fullReconnectOnNext: boolean = false;
|
76
|
+
|
77
|
+
private clientConfiguration?: ClientConfiguration;
|
74
78
|
|
75
79
|
private connectedServerAddr?: string;
|
76
80
|
|
@@ -96,6 +100,7 @@ export default class RTCEngine extends (
|
|
96
100
|
if (!this.subscriberPrimary) {
|
97
101
|
this.negotiate();
|
98
102
|
}
|
103
|
+
this.clientConfiguration = joinResponse.clientConfiguration;
|
99
104
|
|
100
105
|
return joinResponse;
|
101
106
|
}
|
@@ -107,7 +112,10 @@ export default class RTCEngine extends (
|
|
107
112
|
if (this.publisher && this.publisher.pc.signalingState !== 'closed') {
|
108
113
|
this.publisher.pc.getSenders().forEach((sender) => {
|
109
114
|
try {
|
110
|
-
|
115
|
+
// TODO: react-native-webrtc doesn't have removeTrack yet.
|
116
|
+
if (this.publisher?.pc.removeTrack) {
|
117
|
+
this.publisher?.pc.removeTrack(sender);
|
118
|
+
}
|
111
119
|
} catch (e) {
|
112
120
|
log.warn('could not removeTrack', e);
|
113
121
|
}
|
@@ -166,6 +174,11 @@ export default class RTCEngine extends (
|
|
166
174
|
this.rtcConfig.iceServers = rtcIceServers;
|
167
175
|
}
|
168
176
|
|
177
|
+
// @ts-ignore
|
178
|
+
this.rtcConfig.sdpSemantics = 'unified-plan';
|
179
|
+
// @ts-ignore
|
180
|
+
this.rtcConfig.continualGatheringPolicy = 'gather_continually';
|
181
|
+
|
169
182
|
this.publisher = new PCTransport(this.rtcConfig);
|
170
183
|
this.subscriber = new PCTransport(this.rtcConfig);
|
171
184
|
|
@@ -216,10 +229,18 @@ export default class RTCEngine extends (
|
|
216
229
|
}
|
217
230
|
};
|
218
231
|
|
219
|
-
|
220
|
-
this.
|
221
|
-
|
222
|
-
|
232
|
+
if (isWeb()) {
|
233
|
+
this.subscriber.pc.ontrack = (ev: RTCTrackEvent) => {
|
234
|
+
this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
|
235
|
+
};
|
236
|
+
} else {
|
237
|
+
// TODO: react-native-webrtc doesn't have ontrack yet, replace when ready.
|
238
|
+
// @ts-ignore
|
239
|
+
this.subscriber.pc.onaddstream = (ev: { stream: MediaStream }) => {
|
240
|
+
const track = ev.stream.getTracks()[0];
|
241
|
+
this.emit(EngineEvent.MediaTrackAdded, track, ev.stream);
|
242
|
+
};
|
243
|
+
}
|
223
244
|
// data channels
|
224
245
|
this.lossyDC = this.publisher.pc.createDataChannel(lossyDataChannel, {
|
225
246
|
// will drop older packets that arrive
|
@@ -303,7 +324,7 @@ export default class RTCEngine extends (
|
|
303
324
|
|
304
325
|
this.client.onLeave = (leave?: LeaveRequest) => {
|
305
326
|
if (leave?.canReconnect) {
|
306
|
-
this.
|
327
|
+
this.fullReconnectOnNext = true;
|
307
328
|
this.primaryPC = undefined;
|
308
329
|
} else {
|
309
330
|
this.emit(EngineEvent.Disconnected);
|
@@ -376,19 +397,19 @@ export default class RTCEngine extends (
|
|
376
397
|
if (this.isClosed) {
|
377
398
|
return;
|
378
399
|
}
|
379
|
-
if (isFireFox()
|
380
|
-
|
381
|
-
this.
|
400
|
+
if (isFireFox() // TODO remove once clientConfiguration handles firefox case server side
|
401
|
+
|| this.clientConfiguration?.resumeConnection === ClientConfigSetting.DISABLED) {
|
402
|
+
this.fullReconnectOnNext = true;
|
382
403
|
}
|
383
404
|
|
384
405
|
try {
|
385
|
-
if (this.
|
406
|
+
if (this.fullReconnectOnNext) {
|
386
407
|
await this.restartConnection();
|
387
408
|
} else {
|
388
409
|
await this.resumeConnection();
|
389
410
|
}
|
390
411
|
this.reconnectAttempts = 0;
|
391
|
-
this.
|
412
|
+
this.fullReconnectOnNext = false;
|
392
413
|
} catch (e) {
|
393
414
|
this.reconnectAttempts += 1;
|
394
415
|
let recoverable = true;
|
@@ -398,7 +419,7 @@ export default class RTCEngine extends (
|
|
398
419
|
recoverable = false;
|
399
420
|
} else if (!(e instanceof SignalReconnectError)) {
|
400
421
|
// cannot resume
|
401
|
-
this.
|
422
|
+
this.fullReconnectOnNext = true;
|
402
423
|
}
|
403
424
|
|
404
425
|
const duration = Date.now() - this.reconnectStart;
|
@@ -444,6 +465,7 @@ export default class RTCEngine extends (
|
|
444
465
|
}
|
445
466
|
|
446
467
|
await this.waitForPCConnected();
|
468
|
+
this.client.setReconnected();
|
447
469
|
|
448
470
|
// reconnect success
|
449
471
|
this.emit(EngineEvent.Restarted, joinResponse);
|
@@ -478,6 +500,7 @@ export default class RTCEngine extends (
|
|
478
500
|
}
|
479
501
|
|
480
502
|
await this.waitForPCConnected();
|
503
|
+
this.client.setReconnected();
|
481
504
|
|
482
505
|
// resume success
|
483
506
|
this.emit(EngineEvent.Resumed);
|
@@ -564,7 +587,7 @@ export default class RTCEngine extends (
|
|
564
587
|
this.publisher.negotiate();
|
565
588
|
}
|
566
589
|
|
567
|
-
|
590
|
+
dataChannelForKind(kind: DataPacket_Kind): RTCDataChannel | undefined {
|
568
591
|
if (kind === DataPacket_Kind.LOSSY) {
|
569
592
|
return this.lossyDC;
|
570
593
|
} if (kind === DataPacket_Kind.RELIABLE) {
|
@@ -621,7 +644,7 @@ export type EngineEventCallbacks = {
|
|
621
644
|
mediaTrackAdded: (
|
622
645
|
track: MediaStreamTrack,
|
623
646
|
streams: MediaStream,
|
624
|
-
receiver
|
647
|
+
receiver?: RTCRtpReceiver
|
625
648
|
) => void,
|
626
649
|
activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void,
|
627
650
|
dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void,
|
package/src/room/Room.ts
CHANGED
@@ -28,8 +28,9 @@ import LocalTrackPublication from './track/LocalTrackPublication';
|
|
28
28
|
import RemoteTrackPublication from './track/RemoteTrackPublication';
|
29
29
|
import { Track } from './track/Track';
|
30
30
|
import { TrackPublication } from './track/TrackPublication';
|
31
|
-
import { RemoteTrack } from './track/types';
|
32
|
-
import {
|
31
|
+
import { AdaptiveStreamSettings, RemoteTrack } from './track/types';
|
32
|
+
import { getNewAudioContext } from './track/utils';
|
33
|
+
import { isWeb, unpackStreamId } from './utils';
|
33
34
|
|
34
35
|
export enum RoomState {
|
35
36
|
Disconnected = 'disconnected',
|
@@ -195,7 +196,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
195
196
|
|
196
197
|
try {
|
197
198
|
const joinResponse = await this.engine.join(url, token, opts);
|
198
|
-
log.debug(
|
199
|
+
log.debug(`connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`);
|
199
200
|
|
200
201
|
if (!joinResponse.serverVersion) {
|
201
202
|
throw new UnsupportedServer('unknown server version');
|
@@ -270,8 +271,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
270
271
|
clearTimeout(connectTimeout);
|
271
272
|
|
272
273
|
// also hook unload event
|
273
|
-
|
274
|
-
|
274
|
+
if (isWeb()) {
|
275
|
+
window.addEventListener('beforeunload', this.onBeforeUnload);
|
276
|
+
navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange);
|
277
|
+
}
|
275
278
|
|
276
279
|
resolve(this);
|
277
280
|
});
|
@@ -437,12 +440,20 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
437
440
|
if (!trackId || trackId === '') trackId = mediaTrack.id;
|
438
441
|
|
439
442
|
const participant = this.getOrCreateParticipant(participantId);
|
443
|
+
let adaptiveStreamSettings: AdaptiveStreamSettings | undefined;
|
444
|
+
if (this.options.adaptiveStream) {
|
445
|
+
if (typeof this.options.adaptiveStream === 'object') {
|
446
|
+
adaptiveStreamSettings = this.options.adaptiveStream;
|
447
|
+
} else {
|
448
|
+
adaptiveStreamSettings = {};
|
449
|
+
}
|
450
|
+
}
|
440
451
|
participant.addSubscribedMediaTrack(
|
441
452
|
mediaTrack,
|
442
453
|
trackId,
|
443
454
|
stream,
|
444
455
|
receiver,
|
445
|
-
|
456
|
+
adaptiveStreamSettings,
|
446
457
|
);
|
447
458
|
}
|
448
459
|
|
@@ -458,6 +469,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
458
469
|
};
|
459
470
|
|
460
471
|
private handleRestarted = async (joinResponse: JoinResponse) => {
|
472
|
+
log.debug('reconnected to server region', joinResponse.serverRegion);
|
461
473
|
this.state = RoomState.Connected;
|
462
474
|
this.emit(RoomEvent.Reconnected);
|
463
475
|
this.emit(RoomEvent.StateChanged, this.state);
|
@@ -511,8 +523,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
511
523
|
this.audioContext.close();
|
512
524
|
this.audioContext = undefined;
|
513
525
|
}
|
514
|
-
|
515
|
-
|
526
|
+
if (isWeb()) {
|
527
|
+
window.removeEventListener('beforeunload', this.onBeforeUnload);
|
528
|
+
navigator.mediaDevices.removeEventListener('devicechange', this.handleDeviceChange);
|
529
|
+
}
|
516
530
|
this.state = RoomState.Disconnected;
|
517
531
|
this.emit(RoomEvent.Disconnected);
|
518
532
|
this.emit(RoomEvent.StateChanged, this.state);
|
@@ -522,7 +536,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
522
536
|
// handle changes to participant state, and send events
|
523
537
|
participantInfos.forEach((info) => {
|
524
538
|
if (info.sid === this.localParticipant.sid
|
525
|
-
|
539
|
+
|| info.identity === this.localParticipant.identity) {
|
526
540
|
this.localParticipant.updateInfo(info);
|
527
541
|
return;
|
528
542
|
}
|
@@ -717,10 +731,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
717
731
|
}
|
718
732
|
// by using an AudioContext, it reduces lag on audio elements
|
719
733
|
// https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
this.audioContext = new AudioContext();
|
734
|
+
const ctx = getNewAudioContext();
|
735
|
+
if (ctx) {
|
736
|
+
this.audioContext = ctx;
|
724
737
|
}
|
725
738
|
}
|
726
739
|
|
@@ -826,6 +839,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
826
839
|
participantTracks: [],
|
827
840
|
},
|
828
841
|
publishTracks: this.localParticipant.publishedTracksInfo(),
|
842
|
+
dataChannels: this.localParticipant.dataChannelsInfo(),
|
829
843
|
});
|
830
844
|
}
|
831
845
|
|
package/src/room/events.ts
CHANGED
@@ -185,7 +185,7 @@ export enum RoomEvent {
|
|
185
185
|
ConnectionQualityChanged = 'connectionQualityChanged',
|
186
186
|
|
187
187
|
/**
|
188
|
-
* StreamState indicates if a subscribed track has been paused by the SFU
|
188
|
+
* StreamState indicates if a subscribed (remote) track has been paused by the SFU
|
189
189
|
* (typically this happens because of subscriber's bandwidth constraints)
|
190
190
|
*
|
191
191
|
* When bandwidth conditions allow, the track will be resumed automatically.
|
@@ -400,6 +400,11 @@ export enum TrackEvent {
|
|
400
400
|
AudioPlaybackStarted = 'audioPlaybackStarted',
|
401
401
|
/** @internal */
|
402
402
|
AudioPlaybackFailed = 'audioPlaybackFailed',
|
403
|
+
/**
|
404
|
+
* @internal
|
405
|
+
* Only fires on LocalAudioTrack instances
|
406
|
+
*/
|
407
|
+
AudioSilenceDetected = 'audioSilenceDetected',
|
403
408
|
/** @internal */
|
404
409
|
VisibilityChanged = 'visibilityChanged',
|
405
410
|
/** @internal */
|
@@ -3,7 +3,9 @@ import { RoomOptions } from '../../options';
|
|
3
3
|
import {
|
4
4
|
DataPacket, DataPacket_Kind,
|
5
5
|
} from '../../proto/livekit_models';
|
6
|
-
import {
|
6
|
+
import {
|
7
|
+
AddTrackRequest, DataChannelInfo, SubscribedQualityUpdate, TrackPublishedResponse,
|
8
|
+
} from '../../proto/livekit_rtc';
|
7
9
|
import {
|
8
10
|
TrackInvalidError,
|
9
11
|
UnexpectedConnectionState,
|
@@ -17,10 +19,12 @@ import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTr
|
|
17
19
|
import {
|
18
20
|
CreateLocalTracksOptions,
|
19
21
|
ScreenShareCaptureOptions,
|
20
|
-
|
22
|
+
ScreenSharePresets,
|
23
|
+
TrackPublishOptions, VideoCodec,
|
21
24
|
} from '../track/options';
|
22
25
|
import { Track } from '../track/Track';
|
23
26
|
import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
|
27
|
+
import { isFireFox } from '../utils';
|
24
28
|
import Participant from './Participant';
|
25
29
|
import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
|
26
30
|
import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
|
@@ -256,6 +260,7 @@ export default class LocalParticipant extends Participant {
|
|
256
260
|
} else if (track.kind === Track.Kind.Audio) {
|
257
261
|
track.source = Track.Source.Microphone;
|
258
262
|
}
|
263
|
+
track.mediaStream = stream;
|
259
264
|
return track;
|
260
265
|
});
|
261
266
|
}
|
@@ -272,7 +277,7 @@ export default class LocalParticipant extends Participant {
|
|
272
277
|
options = {};
|
273
278
|
}
|
274
279
|
if (options.resolution === undefined) {
|
275
|
-
options.resolution =
|
280
|
+
options.resolution = ScreenSharePresets.h1080fps15.resolution;
|
276
281
|
}
|
277
282
|
|
278
283
|
let videoConstraints: MediaTrackConstraints | boolean = true;
|
@@ -355,6 +360,12 @@ export default class LocalParticipant extends Participant {
|
|
355
360
|
track.stopOnMute = true;
|
356
361
|
}
|
357
362
|
|
363
|
+
if (track.source === Track.Source.ScreenShare && isFireFox()) {
|
364
|
+
// Firefox does not work well with simulcasted screen share
|
365
|
+
// we frequently get no data on layer 0 when enabled
|
366
|
+
opts.simulcast = false;
|
367
|
+
}
|
368
|
+
|
358
369
|
// handle track actions
|
359
370
|
track.on(TrackEvent.Muted, this.onTrackMuted);
|
360
371
|
track.on(TrackEvent.Unmuted, this.onTrackUnmuted);
|
@@ -525,7 +536,7 @@ export default class LocalParticipant extends Participant {
|
|
525
536
|
destination?: RemoteParticipant[] | string[]) {
|
526
537
|
const dest: string[] = [];
|
527
538
|
if (destination !== undefined) {
|
528
|
-
destination.forEach((val
|
539
|
+
destination.forEach((val: any) => {
|
529
540
|
if (val instanceof RemoteParticipant) {
|
530
541
|
dest.push(val.sid);
|
531
542
|
} else {
|
@@ -681,4 +692,20 @@ export default class LocalParticipant extends Participant {
|
|
681
692
|
});
|
682
693
|
return infos;
|
683
694
|
}
|
695
|
+
|
696
|
+
/** @internal */
|
697
|
+
dataChannelsInfo(): DataChannelInfo[] {
|
698
|
+
const infos: DataChannelInfo[] = [];
|
699
|
+
const getInfo = (dc: RTCDataChannel | undefined) => {
|
700
|
+
if (dc?.id !== undefined && dc.id !== null) {
|
701
|
+
infos.push({
|
702
|
+
label: dc.label,
|
703
|
+
id: dc.id,
|
704
|
+
});
|
705
|
+
}
|
706
|
+
};
|
707
|
+
getInfo(this.engine.dataChannelForKind(DataPacket_Kind.LOSSY));
|
708
|
+
getInfo(this.engine.dataChannelForKind(DataPacket_Kind.RELIABLE));
|
709
|
+
return infos;
|
710
|
+
}
|
684
711
|
}
|
@@ -10,7 +10,7 @@ import RemoteAudioTrack from '../track/RemoteAudioTrack';
|
|
10
10
|
import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
11
11
|
import RemoteVideoTrack from '../track/RemoteVideoTrack';
|
12
12
|
import { Track } from '../track/Track';
|
13
|
-
import { RemoteTrack } from '../track/types';
|
13
|
+
import { AdaptiveStreamSettings, RemoteTrack } from '../track/types';
|
14
14
|
import Participant, { ParticipantEventCallbacks } from './Participant';
|
15
15
|
|
16
16
|
export default class RemoteParticipant extends Participant {
|
@@ -82,7 +82,7 @@ export default class RemoteParticipant extends Participant {
|
|
82
82
|
sid: Track.SID,
|
83
83
|
mediaStream: MediaStream,
|
84
84
|
receiver?: RTCRtpReceiver,
|
85
|
-
|
85
|
+
adaptiveStreamSettings?: AdaptiveStreamSettings,
|
86
86
|
triesLeft?: number,
|
87
87
|
) {
|
88
88
|
// find the track publication
|
@@ -114,7 +114,7 @@ export default class RemoteParticipant extends Participant {
|
|
114
114
|
if (triesLeft === undefined) triesLeft = 20;
|
115
115
|
setTimeout(() => {
|
116
116
|
this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream,
|
117
|
-
receiver,
|
117
|
+
receiver, adaptiveStreamSettings, triesLeft! - 1);
|
118
118
|
}, 150);
|
119
119
|
return;
|
120
120
|
}
|
@@ -122,7 +122,7 @@ export default class RemoteParticipant extends Participant {
|
|
122
122
|
const isVideo = mediaTrack.kind === 'video';
|
123
123
|
let track: RemoteTrack;
|
124
124
|
if (isVideo) {
|
125
|
-
track = new RemoteVideoTrack(mediaTrack, sid, receiver,
|
125
|
+
track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
|
126
126
|
} else {
|
127
127
|
track = new RemoteAudioTrack(mediaTrack, sid, receiver);
|
128
128
|
}
|
@@ -1,11 +1,15 @@
|
|
1
|
-
import { VideoPresets, VideoPresets43 } from '../track/options';
|
2
1
|
import {
|
2
|
+
ScreenSharePresets, VideoPreset, VideoPresets, VideoPresets43,
|
3
|
+
} from '../track/options';
|
4
|
+
import {
|
5
|
+
computeDefaultScreenShareSimulcastPresets,
|
3
6
|
computeVideoEncodings,
|
4
7
|
determineAppropriateEncoding,
|
5
8
|
presets169,
|
6
9
|
presets43,
|
7
10
|
presetsForResolution,
|
8
11
|
presetsScreenShare,
|
12
|
+
sortPresets,
|
9
13
|
} from './publishUtils';
|
10
14
|
|
11
15
|
describe('presetsForResolution', () => {
|
@@ -63,7 +67,7 @@ describe('computeVideoEncodings', () => {
|
|
63
67
|
|
64
68
|
// ensure they are what we expect
|
65
69
|
expect(encodings![0].rid).toBe('q');
|
66
|
-
expect(encodings![0].maxBitrate).toBe(VideoPresets.
|
70
|
+
expect(encodings![0].maxBitrate).toBe(VideoPresets.h180.encoding.maxBitrate);
|
67
71
|
expect(encodings![0].scaleResolutionDownBy).toBe(3);
|
68
72
|
expect(encodings![1].rid).toBe('h');
|
69
73
|
expect(encodings![1].scaleResolutionDownBy).toBe(1.5);
|
@@ -77,7 +81,7 @@ describe('computeVideoEncodings', () => {
|
|
77
81
|
expect(encodings).toHaveLength(3);
|
78
82
|
expect(encodings![0].scaleResolutionDownBy).toBe(3);
|
79
83
|
expect(encodings![1].scaleResolutionDownBy).toBe(1.5);
|
80
|
-
expect(encodings![2].maxBitrate).toBe(VideoPresets.
|
84
|
+
expect(encodings![2].maxBitrate).toBe(VideoPresets.h540.encoding.maxBitrate);
|
81
85
|
});
|
82
86
|
|
83
87
|
it('returns two encodings for lower-res simulcast', () => {
|
@@ -88,9 +92,9 @@ describe('computeVideoEncodings', () => {
|
|
88
92
|
|
89
93
|
// ensure they are what we expect
|
90
94
|
expect(encodings![0].rid).toBe('q');
|
91
|
-
expect(encodings![0].maxBitrate).toBe(VideoPresets.
|
95
|
+
expect(encodings![0].maxBitrate).toBe(VideoPresets.h180.encoding.maxBitrate);
|
92
96
|
expect(encodings![1].rid).toBe('h');
|
93
|
-
expect(encodings![1].maxBitrate).toBe(VideoPresets.
|
97
|
+
expect(encodings![1].maxBitrate).toBe(VideoPresets.h360.encoding.maxBitrate);
|
94
98
|
});
|
95
99
|
|
96
100
|
it('respects provided min resolution', () => {
|
@@ -99,7 +103,43 @@ describe('computeVideoEncodings', () => {
|
|
99
103
|
});
|
100
104
|
expect(encodings).toHaveLength(1);
|
101
105
|
expect(encodings![0].rid).toBe('q');
|
102
|
-
expect(encodings![0].maxBitrate).toBe(VideoPresets43.
|
106
|
+
expect(encodings![0].maxBitrate).toBe(VideoPresets43.h120.encoding.maxBitrate);
|
103
107
|
expect(encodings![0].scaleResolutionDownBy).toBe(1);
|
104
108
|
});
|
105
109
|
});
|
110
|
+
|
111
|
+
describe('customSimulcastLayers', () => {
|
112
|
+
it('sorts presets from lowest to highest', () => {
|
113
|
+
const sortedPresets = sortPresets(
|
114
|
+
[VideoPresets.h1440, VideoPresets.h360, VideoPresets.h1080, VideoPresets.h90],
|
115
|
+
) as Array<VideoPreset>;
|
116
|
+
expect(sortPresets).not.toBeUndefined();
|
117
|
+
expect(sortedPresets[0]).toBe(VideoPresets.h90);
|
118
|
+
expect(sortedPresets[1]).toBe(VideoPresets.h360);
|
119
|
+
expect(sortedPresets[2]).toBe(VideoPresets.h1080);
|
120
|
+
expect(sortedPresets[3]).toBe(VideoPresets.h1440);
|
121
|
+
});
|
122
|
+
it('sorts presets from lowest to highest, even when dimensions are the same', () => {
|
123
|
+
const sortedPresets = sortPresets([
|
124
|
+
new VideoPreset(1920, 1080, 3_000_000, 20),
|
125
|
+
new VideoPreset(1920, 1080, 2_000_000, 15),
|
126
|
+
new VideoPreset(1920, 1080, 3_000_000, 15),
|
127
|
+
]) as Array<VideoPreset>;
|
128
|
+
expect(sortPresets).not.toBeUndefined();
|
129
|
+
expect(sortedPresets[0].encoding.maxBitrate).toBe(2_000_000);
|
130
|
+
expect(sortedPresets[1].encoding.maxFramerate).toBe(15);
|
131
|
+
expect(sortedPresets[2].encoding.maxFramerate).toBe(20);
|
132
|
+
});
|
133
|
+
});
|
134
|
+
|
135
|
+
describe('screenShareSimulcastDefaults', () => {
|
136
|
+
it('computes appropriate bitrate from original preset', () => {
|
137
|
+
const defaultSimulcastLayers = computeDefaultScreenShareSimulcastPresets(
|
138
|
+
ScreenSharePresets.h720fps15,
|
139
|
+
);
|
140
|
+
expect(defaultSimulcastLayers[0].width).toBe(640);
|
141
|
+
expect(defaultSimulcastLayers[0].height).toBe(360);
|
142
|
+
expect(defaultSimulcastLayers[0].encoding.maxFramerate).toBe(3);
|
143
|
+
expect(defaultSimulcastLayers[0].encoding.maxBitrate).toBe(150_000);
|
144
|
+
});
|
145
|
+
});
|
@@ -26,32 +26,38 @@ export function mediaTrackToLocalTrack(
|
|
26
26
|
}
|
27
27
|
|
28
28
|
/* @internal */
|
29
|
-
export const presets169 =
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
export const presets169 = Object.values(VideoPresets);
|
30
|
+
|
31
|
+
/* @internal */
|
32
|
+
export const presets43 = Object.values(VideoPresets43);
|
33
|
+
|
34
|
+
/* @internal */
|
35
|
+
export const presetsScreenShare = Object.values(ScreenSharePresets);
|
36
36
|
|
37
37
|
/* @internal */
|
38
|
-
export const
|
39
|
-
|
40
|
-
|
41
|
-
VideoPresets43.qhd,
|
42
|
-
VideoPresets43.hd,
|
43
|
-
VideoPresets43.fhd,
|
38
|
+
export const defaultSimulcastPresets169 = [
|
39
|
+
VideoPresets.h180,
|
40
|
+
VideoPresets.h360,
|
44
41
|
];
|
45
42
|
|
46
43
|
/* @internal */
|
47
|
-
export const
|
48
|
-
|
49
|
-
|
50
|
-
ScreenSharePresets.hd_15,
|
51
|
-
ScreenSharePresets.fhd_15,
|
52
|
-
ScreenSharePresets.fhd_30,
|
44
|
+
export const defaultSimulcastPresets43 = [
|
45
|
+
VideoPresets43.h180,
|
46
|
+
VideoPresets43.h360,
|
53
47
|
];
|
54
48
|
|
49
|
+
/* @internal */
|
50
|
+
export const computeDefaultScreenShareSimulcastPresets = (fromPreset: VideoPreset) => {
|
51
|
+
const layers = [{ scaleResolutionDownBy: 2, fps: 3 }];
|
52
|
+
return layers.map((t) => new VideoPreset(
|
53
|
+
Math.floor(fromPreset.width / t.scaleResolutionDownBy),
|
54
|
+
Math.floor(fromPreset.height / t.scaleResolutionDownBy),
|
55
|
+
Math.max(150_000, Math.floor(fromPreset.encoding.maxBitrate
|
56
|
+
/ (t.scaleResolutionDownBy ** 2 * ((fromPreset.encoding.maxFramerate ?? 30) / t.fps)))),
|
57
|
+
t.fps,
|
58
|
+
));
|
59
|
+
};
|
60
|
+
|
55
61
|
const videoRids = ['q', 'h', 'f'];
|
56
62
|
|
57
63
|
/* @internal */
|
@@ -65,7 +71,7 @@ export function computeVideoEncodings(
|
|
65
71
|
if (isScreenShare) {
|
66
72
|
videoEncoding = options?.screenShareEncoding;
|
67
73
|
}
|
68
|
-
const useSimulcast =
|
74
|
+
const useSimulcast = options?.simulcast;
|
69
75
|
|
70
76
|
if ((!videoEncoding && !useSimulcast) || !width || !height) {
|
71
77
|
// when we aren't simulcasting, will need to return a single encoding without
|
@@ -82,16 +88,22 @@ export function computeVideoEncodings(
|
|
82
88
|
if (!useSimulcast) {
|
83
89
|
return [videoEncoding];
|
84
90
|
}
|
85
|
-
|
86
|
-
|
91
|
+
const original = new VideoPreset(
|
92
|
+
width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate,
|
93
|
+
);
|
94
|
+
let presets: Array<VideoPreset> = [];
|
95
|
+
if (isScreenShare) {
|
96
|
+
presets = sortPresets(options?.screenShareSimulcastLayers)
|
97
|
+
?? defaultSimulcastLayers(isScreenShare, original);
|
98
|
+
} else {
|
99
|
+
presets = sortPresets(options?.videoSimulcastLayers)
|
100
|
+
?? defaultSimulcastLayers(isScreenShare, original);
|
101
|
+
}
|
87
102
|
let midPreset: VideoPreset | undefined;
|
88
103
|
const lowPreset = presets[0];
|
89
104
|
if (presets.length > 1) {
|
90
|
-
[,midPreset] = presets;
|
105
|
+
[, midPreset] = presets;
|
91
106
|
}
|
92
|
-
const original = new VideoPreset(
|
93
|
-
width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate,
|
94
|
-
);
|
95
107
|
|
96
108
|
// NOTE:
|
97
109
|
// 1. Ordering of these encodings is important. Chrome seems
|
@@ -108,7 +120,7 @@ export function computeVideoEncodings(
|
|
108
120
|
lowPreset, midPreset, original,
|
109
121
|
]);
|
110
122
|
}
|
111
|
-
if (size >=
|
123
|
+
if (size >= 480) {
|
112
124
|
return encodingsFromPresets(width, height, [
|
113
125
|
lowPreset, original,
|
114
126
|
]);
|
@@ -155,6 +167,21 @@ export function presetsForResolution(
|
|
155
167
|
return presets43;
|
156
168
|
}
|
157
169
|
|
170
|
+
/* @internal */
|
171
|
+
export function defaultSimulcastLayers(
|
172
|
+
isScreenShare: boolean, original: VideoPreset,
|
173
|
+
): VideoPreset[] {
|
174
|
+
if (isScreenShare) {
|
175
|
+
return computeDefaultScreenShareSimulcastPresets(original);
|
176
|
+
}
|
177
|
+
const { width, height } = original;
|
178
|
+
const aspect = width > height ? width / height : height / width;
|
179
|
+
if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
|
180
|
+
return defaultSimulcastPresets169;
|
181
|
+
}
|
182
|
+
return defaultSimulcastPresets43;
|
183
|
+
}
|
184
|
+
|
158
185
|
// presets should be ordered by low, medium, high
|
159
186
|
function encodingsFromPresets(
|
160
187
|
width: number,
|
@@ -178,3 +205,21 @@ function encodingsFromPresets(
|
|
178
205
|
});
|
179
206
|
return encodings;
|
180
207
|
}
|
208
|
+
|
209
|
+
/** @internal */
|
210
|
+
export function sortPresets(presets: Array<VideoPreset> | undefined) {
|
211
|
+
if (!presets) return;
|
212
|
+
return presets.sort((a, b) => {
|
213
|
+
const { encoding: aEnc } = a;
|
214
|
+
const { encoding: bEnc } = b;
|
215
|
+
|
216
|
+
if (aEnc.maxBitrate > bEnc.maxBitrate) {
|
217
|
+
return 1;
|
218
|
+
}
|
219
|
+
if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
|
220
|
+
if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
|
221
|
+
return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
|
222
|
+
}
|
223
|
+
return 0;
|
224
|
+
});
|
225
|
+
}
|