livekit-client 2.4.2 → 2.5.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +8 -4
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +374 -102
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +3 -2
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/publishAudio.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +4 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +5 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +4 -3
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +12 -3
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +5 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +4 -4
- package/dist/src/room/timers.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts +12 -2
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +1 -1
- package/dist/src/room/types.d.ts +2 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +3 -2
- package/dist/ts4.2/src/room/PCTransport.d.ts +1 -1
- package/dist/ts4.2/src/room/RTCEngine.d.ts +4 -3
- package/dist/ts4.2/src/room/Room.d.ts +5 -0
- package/dist/ts4.2/src/room/errors.d.ts +4 -3
- package/dist/ts4.2/src/room/events.d.ts +12 -3
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +5 -2
- package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -0
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +1 -1
- package/dist/ts4.2/src/room/timers.d.ts +4 -4
- package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +12 -2
- package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/options.d.ts +1 -1
- package/dist/ts4.2/src/room/types.d.ts +2 -0
- package/dist/ts4.2/src/room/utils.d.ts +1 -1
- package/dist/ts4.2/src/version.d.ts +1 -1
- package/package.json +10 -10
- package/src/api/SignalClient.ts +12 -6
- package/src/connectionHelper/checks/publishAudio.ts +4 -1
- package/src/connectionHelper/checks/publishVideo.ts +6 -3
- package/src/room/PCTransport.ts +4 -1
- package/src/room/RTCEngine.ts +11 -5
- package/src/room/Room.ts +42 -5
- package/src/room/errors.ts +7 -3
- package/src/room/events.ts +12 -1
- package/src/room/participant/LocalParticipant.ts +125 -84
- package/src/room/participant/Participant.ts +1 -0
- package/src/room/participant/RemoteParticipant.ts +1 -1
- package/src/room/timers.ts +15 -6
- package/src/room/track/LocalVideoTrack.test.ts +60 -0
- package/src/room/track/LocalVideoTrack.ts +1 -1
- package/src/room/track/RemoteAudioTrack.ts +1 -1
- package/src/room/track/RemoteTrack.ts +38 -2
- package/src/room/track/RemoteVideoTrack.ts +2 -2
- package/src/room/track/options.ts +1 -1
- package/src/room/types.ts +2 -0
- package/src/room/utils.ts +10 -0
- package/src/version.ts +1 -1
package/src/room/events.ts
CHANGED
@@ -323,6 +323,11 @@ export enum RoomEvent {
|
|
323
323
|
* args: (kind: MediaDeviceKind, deviceId: string)
|
324
324
|
*/
|
325
325
|
ActiveDeviceChanged = 'activeDeviceChanged',
|
326
|
+
|
327
|
+
/**
|
328
|
+
* fired when the first remote participant has subscribed to the localParticipant's track
|
329
|
+
*/
|
330
|
+
LocalTrackSubscribed = 'localTrackSubscribed',
|
326
331
|
}
|
327
332
|
|
328
333
|
export enum ParticipantEvent {
|
@@ -509,6 +514,11 @@ export enum ParticipantEvent {
|
|
509
514
|
* When a participant's attributes changed, this event will be emitted with the changed attributes
|
510
515
|
*/
|
511
516
|
AttributesChanged = 'attributesChanged',
|
517
|
+
|
518
|
+
/**
|
519
|
+
* fired on local participant only, when the first remote participant has subscribed to the track specified in the payload
|
520
|
+
*/
|
521
|
+
LocalTrackSubscribed = 'localTrackSubscribed',
|
512
522
|
}
|
513
523
|
|
514
524
|
/** @internal */
|
@@ -538,8 +548,9 @@ export enum EngineEvent {
|
|
538
548
|
RemoteMute = 'remoteMute',
|
539
549
|
SubscribedQualityUpdate = 'subscribedQualityUpdate',
|
540
550
|
LocalTrackUnpublished = 'localTrackUnpublished',
|
551
|
+
LocalTrackSubscribed = 'localTrackSubscribed',
|
541
552
|
Offline = 'offline',
|
542
|
-
|
553
|
+
SignalRequestResponse = 'signalRequestResponse',
|
543
554
|
}
|
544
555
|
|
545
556
|
export enum TrackEvent {
|
@@ -1,13 +1,16 @@
|
|
1
1
|
import {
|
2
2
|
AddTrackRequest,
|
3
|
+
Codec,
|
3
4
|
DataPacket,
|
4
5
|
DataPacket_Kind,
|
5
6
|
Encryption_Type,
|
6
|
-
ErrorResponse,
|
7
7
|
ParticipantInfo,
|
8
8
|
ParticipantPermission,
|
9
|
+
RequestResponse,
|
10
|
+
RequestResponse_Reason,
|
9
11
|
SimulcastCodec,
|
10
12
|
SubscribedQualityUpdate,
|
13
|
+
TrackInfo,
|
11
14
|
TrackUnpublishedResponse,
|
12
15
|
UserPacket,
|
13
16
|
} from '@livekit/protocol';
|
@@ -109,6 +112,8 @@ export default class LocalParticipant extends Participant {
|
|
109
112
|
}
|
110
113
|
>;
|
111
114
|
|
115
|
+
private enabledPublishVideoCodecs: Codec[] = [];
|
116
|
+
|
112
117
|
/** @internal */
|
113
118
|
constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions) {
|
114
119
|
super(sid, identity, undefined, undefined, {
|
@@ -177,7 +182,7 @@ export default class LocalParticipant extends Participant {
|
|
177
182
|
.on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
|
178
183
|
.on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate)
|
179
184
|
.on(EngineEvent.Disconnected, this.handleDisconnected)
|
180
|
-
.on(EngineEvent.
|
185
|
+
.on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse);
|
181
186
|
}
|
182
187
|
|
183
188
|
private handleReconnecting = () => {
|
@@ -200,11 +205,13 @@ export default class LocalParticipant extends Participant {
|
|
200
205
|
}
|
201
206
|
};
|
202
207
|
|
203
|
-
private
|
204
|
-
const { requestId, reason, message } =
|
205
|
-
const
|
206
|
-
if (
|
207
|
-
|
208
|
+
private handleSignalRequestResponse = (response: RequestResponse) => {
|
209
|
+
const { requestId, reason, message } = response;
|
210
|
+
const targetRequest = this.pendingSignalRequests.get(requestId);
|
211
|
+
if (targetRequest) {
|
212
|
+
if (reason !== RequestResponse_Reason.OK) {
|
213
|
+
targetRequest.reject(new SignalRequestError(message, reason));
|
214
|
+
}
|
208
215
|
this.pendingSignalRequests.delete(requestId);
|
209
216
|
}
|
210
217
|
};
|
@@ -278,7 +285,9 @@ export default class LocalParticipant extends Participant {
|
|
278
285
|
}
|
279
286
|
await sleep(50);
|
280
287
|
}
|
281
|
-
reject(
|
288
|
+
reject(
|
289
|
+
new SignalRequestError('Request to update local metadata timed out', 'TimeoutError'),
|
290
|
+
);
|
282
291
|
} catch (e: any) {
|
283
292
|
if (e instanceof Error) reject(e);
|
284
293
|
}
|
@@ -770,6 +779,17 @@ export default class LocalParticipant extends Participant {
|
|
770
779
|
if (opts.videoCodec === undefined) {
|
771
780
|
opts.videoCodec = defaultVideoCodec;
|
772
781
|
}
|
782
|
+
if (this.enabledPublishVideoCodecs.length > 0) {
|
783
|
+
// fallback to a supported codec if it is not supported
|
784
|
+
if (
|
785
|
+
!this.enabledPublishVideoCodecs.some(
|
786
|
+
(c) => opts.videoCodec === mimeTypeToVideoCodecString(c.mime),
|
787
|
+
)
|
788
|
+
) {
|
789
|
+
opts.videoCodec = mimeTypeToVideoCodecString(this.enabledPublishVideoCodecs[0].mime);
|
790
|
+
}
|
791
|
+
}
|
792
|
+
|
773
793
|
const videoCodec = opts.videoCodec;
|
774
794
|
|
775
795
|
// handle track actions
|
@@ -903,33 +923,87 @@ export default class LocalParticipant extends Participant {
|
|
903
923
|
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
904
924
|
}
|
905
925
|
|
906
|
-
const
|
907
|
-
|
908
|
-
|
909
|
-
let primaryCodecMime: string | undefined;
|
910
|
-
ti.codecs.forEach((codec) => {
|
911
|
-
if (primaryCodecMime === undefined) {
|
912
|
-
primaryCodecMime = codec.mimeType;
|
926
|
+
const negotiate = async () => {
|
927
|
+
if (!this.engine.pcManager) {
|
928
|
+
throw new UnexpectedConnectionState('pcManager is not ready');
|
913
929
|
}
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
if (
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
930
|
+
|
931
|
+
track.sender = await this.engine.createSender(track, opts, encodings);
|
932
|
+
|
933
|
+
if (track instanceof LocalVideoTrack) {
|
934
|
+
opts.degradationPreference ??= getDefaultDegradationPreference(track);
|
935
|
+
track.setDegradationPreference(opts.degradationPreference);
|
936
|
+
}
|
937
|
+
|
938
|
+
if (encodings) {
|
939
|
+
if (isFireFox() && track.kind === Track.Kind.Audio) {
|
940
|
+
/* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
|
941
|
+
livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
|
942
|
+
publish high quality audio track. But firefox always uses this value as the actual
|
943
|
+
bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
|
944
|
+
So the client need to modify maxaverragebitrates in answer sdp to user provided value to
|
945
|
+
fix the issue.
|
946
|
+
*/
|
947
|
+
let trackTransceiver: RTCRtpTransceiver | undefined = undefined;
|
948
|
+
for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
|
949
|
+
if (transceiver.sender === track.sender) {
|
950
|
+
trackTransceiver = transceiver;
|
951
|
+
break;
|
952
|
+
}
|
953
|
+
}
|
954
|
+
if (trackTransceiver) {
|
955
|
+
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
956
|
+
transceiver: trackTransceiver,
|
957
|
+
codec: 'opus',
|
958
|
+
maxbr: encodings[0]?.maxBitrate ? encodings[0].maxBitrate / 1000 : 0,
|
959
|
+
});
|
960
|
+
}
|
961
|
+
} else if (track.codec && isSVCCodec(track.codec) && encodings[0]?.maxBitrate) {
|
962
|
+
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
963
|
+
cid: req.cid,
|
964
|
+
codec: track.codec,
|
965
|
+
maxbr: encodings[0].maxBitrate / 1000,
|
966
|
+
});
|
967
|
+
}
|
968
|
+
}
|
969
|
+
|
970
|
+
await this.engine.negotiate();
|
971
|
+
};
|
972
|
+
|
973
|
+
let ti: TrackInfo;
|
974
|
+
if (this.enabledPublishVideoCodecs.length > 0) {
|
975
|
+
const rets = await Promise.all([this.engine.addTrack(req), negotiate()]);
|
976
|
+
ti = rets[0];
|
977
|
+
} else {
|
978
|
+
ti = await this.engine.addTrack(req);
|
979
|
+
// server might not support the codec the client has requested, in that case, fallback
|
980
|
+
// to a supported codec
|
981
|
+
let primaryCodecMime: string | undefined;
|
982
|
+
ti.codecs.forEach((codec) => {
|
983
|
+
if (primaryCodecMime === undefined) {
|
984
|
+
primaryCodecMime = codec.mimeType;
|
985
|
+
}
|
986
|
+
});
|
987
|
+
if (primaryCodecMime && track.kind === Track.Kind.Video) {
|
988
|
+
const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
|
989
|
+
if (updatedCodec !== videoCodec) {
|
990
|
+
this.log.debug('falling back to server selected codec', {
|
991
|
+
...this.logContext,
|
992
|
+
...getLogContextFromTrack(track),
|
993
|
+
codec: updatedCodec,
|
994
|
+
});
|
995
|
+
opts.videoCodec = updatedCodec;
|
996
|
+
|
997
|
+
// recompute encodings since bitrates/etc could have changed
|
998
|
+
encodings = computeVideoEncodings(
|
999
|
+
track.source === Track.Source.ScreenShare,
|
1000
|
+
req.width,
|
1001
|
+
req.height,
|
1002
|
+
opts,
|
1003
|
+
);
|
1004
|
+
}
|
932
1005
|
}
|
1006
|
+
await negotiate();
|
933
1007
|
}
|
934
1008
|
|
935
1009
|
const publication = new LocalTrackPublication(track.kind, ti, track, {
|
@@ -940,56 +1014,12 @@ export default class LocalParticipant extends Participant {
|
|
940
1014
|
publication.options = opts;
|
941
1015
|
track.sid = ti.sid;
|
942
1016
|
|
943
|
-
if (!this.engine.pcManager) {
|
944
|
-
throw new UnexpectedConnectionState('pcManager is not ready');
|
945
|
-
}
|
946
1017
|
this.log.debug(`publishing ${track.kind} with encodings`, {
|
947
1018
|
...this.logContext,
|
948
1019
|
encodings,
|
949
1020
|
trackInfo: ti,
|
950
1021
|
});
|
951
1022
|
|
952
|
-
track.sender = await this.engine.createSender(track, opts, encodings);
|
953
|
-
|
954
|
-
if (track instanceof LocalVideoTrack) {
|
955
|
-
opts.degradationPreference ??= getDefaultDegradationPreference(track);
|
956
|
-
track.setDegradationPreference(opts.degradationPreference);
|
957
|
-
}
|
958
|
-
|
959
|
-
if (encodings) {
|
960
|
-
if (isFireFox() && track.kind === Track.Kind.Audio) {
|
961
|
-
/* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
|
962
|
-
livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
|
963
|
-
publish high quality audio track. But firefox always uses this value as the actual
|
964
|
-
bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
|
965
|
-
So the client need to modify maxaverragebitrates in answer sdp to user provided value to
|
966
|
-
fix the issue.
|
967
|
-
*/
|
968
|
-
let trackTransceiver: RTCRtpTransceiver | undefined = undefined;
|
969
|
-
for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
|
970
|
-
if (transceiver.sender === track.sender) {
|
971
|
-
trackTransceiver = transceiver;
|
972
|
-
break;
|
973
|
-
}
|
974
|
-
}
|
975
|
-
if (trackTransceiver) {
|
976
|
-
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
977
|
-
transceiver: trackTransceiver,
|
978
|
-
codec: 'opus',
|
979
|
-
maxbr: encodings[0]?.maxBitrate ? encodings[0].maxBitrate / 1000 : 0,
|
980
|
-
});
|
981
|
-
}
|
982
|
-
} else if (track.codec && isSVCCodec(track.codec) && encodings[0]?.maxBitrate) {
|
983
|
-
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
984
|
-
cid: req.cid,
|
985
|
-
codec: track.codec,
|
986
|
-
maxbr: encodings[0].maxBitrate / 1000,
|
987
|
-
});
|
988
|
-
}
|
989
|
-
}
|
990
|
-
|
991
|
-
await this.engine.negotiate();
|
992
|
-
|
993
1023
|
if (track instanceof LocalVideoTrack) {
|
994
1024
|
track.startMonitor(this.engine.client);
|
995
1025
|
} else if (track instanceof LocalAudioTrack) {
|
@@ -1076,15 +1106,19 @@ export default class LocalParticipant extends Participant {
|
|
1076
1106
|
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
1077
1107
|
}
|
1078
1108
|
|
1079
|
-
const
|
1109
|
+
const negotiate = async () => {
|
1110
|
+
const transceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly' };
|
1111
|
+
if (encodings) {
|
1112
|
+
transceiverInit.sendEncodings = encodings;
|
1113
|
+
}
|
1114
|
+
await this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings);
|
1080
1115
|
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1116
|
+
await this.engine.negotiate();
|
1117
|
+
};
|
1118
|
+
|
1119
|
+
const rets = await Promise.all([this.engine.addTrack(req), negotiate()]);
|
1120
|
+
const ti = rets[0];
|
1086
1121
|
|
1087
|
-
await this.engine.negotiate();
|
1088
1122
|
this.log.debug(`published ${videoCodec} for track ${track.sid}`, {
|
1089
1123
|
...this.logContext,
|
1090
1124
|
encodings,
|
@@ -1304,6 +1338,13 @@ export default class LocalParticipant extends Participant {
|
|
1304
1338
|
}
|
1305
1339
|
}
|
1306
1340
|
|
1341
|
+
/** @internal */
|
1342
|
+
setEnabledPublishCodecs(codecs: Codec[]) {
|
1343
|
+
this.enabledPublishVideoCodecs = codecs.filter(
|
1344
|
+
(c) => c.mime.split('/')[0].toLowerCase() === 'video',
|
1345
|
+
);
|
1346
|
+
}
|
1347
|
+
|
1307
1348
|
/** @internal */
|
1308
1349
|
updateInfo(info: ParticipantInfo): boolean {
|
1309
1350
|
if (info.sid !== this.sid) {
|
@@ -386,4 +386,5 @@ export type ParticipantEventCallbacks = {
|
|
386
386
|
status: TrackPublication.SubscriptionStatus,
|
387
387
|
) => void;
|
388
388
|
attributesChanged: (changedAttributes: Record<string, string>) => void;
|
389
|
+
localTrackSubscribed: (trackPublication: LocalTrackPublication) => void;
|
389
390
|
};
|
@@ -164,7 +164,7 @@ export default class RemoteParticipant extends Participant {
|
|
164
164
|
mediaTrack: MediaStreamTrack,
|
165
165
|
sid: Track.SID,
|
166
166
|
mediaStream: MediaStream,
|
167
|
-
receiver
|
167
|
+
receiver: RTCRtpReceiver,
|
168
168
|
adaptiveStreamSettings?: AdaptiveStreamSettings,
|
169
169
|
triesLeft?: number,
|
170
170
|
) {
|
package/src/room/timers.ts
CHANGED
@@ -4,13 +4,22 @@
|
|
4
4
|
* that the timer fires on time.
|
5
5
|
*/
|
6
6
|
export default class CriticalTimers {
|
7
|
-
|
8
|
-
|
7
|
+
static setTimeout: (...args: Parameters<typeof setTimeout>) => ReturnType<typeof setTimeout> = (
|
8
|
+
...args: Parameters<typeof setTimeout>
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
10
|
+
) => setTimeout(...args);
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
static setInterval: (...args: Parameters<typeof setInterval>) => ReturnType<typeof setInterval> =
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
14
|
+
(...args: Parameters<typeof setInterval>) => setInterval(...args);
|
12
15
|
|
13
|
-
static clearTimeout
|
16
|
+
static clearTimeout: (
|
17
|
+
...args: Parameters<typeof clearTimeout>
|
18
|
+
) => ReturnType<typeof clearTimeout> = (...args: Parameters<typeof clearTimeout>) =>
|
19
|
+
clearTimeout(...args);
|
14
20
|
|
15
|
-
static clearInterval
|
21
|
+
static clearInterval: (
|
22
|
+
...args: Parameters<typeof clearInterval>
|
23
|
+
) => ReturnType<typeof clearInterval> = (...args: Parameters<typeof clearInterval>) =>
|
24
|
+
clearInterval(...args);
|
16
25
|
}
|
@@ -47,6 +47,66 @@ describe('videoLayersFromEncodings', () => {
|
|
47
47
|
expect(layers[2].height).toBe(720);
|
48
48
|
});
|
49
49
|
|
50
|
+
it('returns qualities starting from lowest for SVC', () => {
|
51
|
+
const layers = videoLayersFromEncodings(
|
52
|
+
1280,
|
53
|
+
720,
|
54
|
+
[
|
55
|
+
{
|
56
|
+
/** @ts-ignore */
|
57
|
+
scalabilityMode: 'L2T2',
|
58
|
+
},
|
59
|
+
],
|
60
|
+
true,
|
61
|
+
);
|
62
|
+
|
63
|
+
expect(layers).toHaveLength(2);
|
64
|
+
expect(layers[0].quality).toBe(VideoQuality.MEDIUM);
|
65
|
+
expect(layers[0].width).toBe(1280);
|
66
|
+
expect(layers[1].quality).toBe(VideoQuality.LOW);
|
67
|
+
expect(layers[1].width).toBe(640);
|
68
|
+
});
|
69
|
+
|
70
|
+
it('returns qualities starting from lowest for SVC (three layers)', () => {
|
71
|
+
const layers = videoLayersFromEncodings(
|
72
|
+
1280,
|
73
|
+
720,
|
74
|
+
[
|
75
|
+
{
|
76
|
+
/** @ts-ignore */
|
77
|
+
scalabilityMode: 'L3T3',
|
78
|
+
},
|
79
|
+
],
|
80
|
+
true,
|
81
|
+
);
|
82
|
+
|
83
|
+
expect(layers).toHaveLength(3);
|
84
|
+
expect(layers[0].quality).toBe(VideoQuality.HIGH);
|
85
|
+
expect(layers[0].width).toBe(1280);
|
86
|
+
expect(layers[1].quality).toBe(VideoQuality.MEDIUM);
|
87
|
+
expect(layers[1].width).toBe(640);
|
88
|
+
expect(layers[2].quality).toBe(VideoQuality.LOW);
|
89
|
+
expect(layers[2].width).toBe(320);
|
90
|
+
});
|
91
|
+
|
92
|
+
it('returns qualities starting from lowest for SVC (single layer)', () => {
|
93
|
+
const layers = videoLayersFromEncodings(
|
94
|
+
1280,
|
95
|
+
720,
|
96
|
+
[
|
97
|
+
{
|
98
|
+
/** @ts-ignore */
|
99
|
+
scalabilityMode: 'L1T2',
|
100
|
+
},
|
101
|
+
],
|
102
|
+
true,
|
103
|
+
);
|
104
|
+
|
105
|
+
expect(layers).toHaveLength(1);
|
106
|
+
expect(layers[0].quality).toBe(VideoQuality.LOW);
|
107
|
+
expect(layers[0].width).toBe(1280);
|
108
|
+
});
|
109
|
+
|
50
110
|
it('handles portrait', () => {
|
51
111
|
const layers = videoLayersFromEncodings(720, 1280, [
|
52
112
|
{
|
@@ -607,7 +607,7 @@ export function videoLayersFromEncodings(
|
|
607
607
|
for (let i = 0; i < sm.spatial; i += 1) {
|
608
608
|
layers.push(
|
609
609
|
new VideoLayer({
|
610
|
-
quality: VideoQuality.HIGH - i,
|
610
|
+
quality: Math.min(VideoQuality.HIGH, sm.spatial - 1) - i,
|
611
611
|
width: Math.ceil(width / resRatio ** i),
|
612
612
|
height: Math.ceil(height / resRatio ** i),
|
613
613
|
bitrate: encodings[0].maxBitrate
|
@@ -25,7 +25,7 @@ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
|
|
25
25
|
constructor(
|
26
26
|
mediaTrack: MediaStreamTrack,
|
27
27
|
sid: string,
|
28
|
-
receiver
|
28
|
+
receiver: RTCRtpReceiver,
|
29
29
|
audioContext?: AudioContext,
|
30
30
|
audioOutput?: AudioOutputOptions,
|
31
31
|
loggerOptions?: LoggerOptions,
|
@@ -8,13 +8,13 @@ export default abstract class RemoteTrack<
|
|
8
8
|
TrackKind extends Track.Kind = Track.Kind,
|
9
9
|
> extends Track<TrackKind> {
|
10
10
|
/** @internal */
|
11
|
-
receiver
|
11
|
+
receiver: RTCRtpReceiver | undefined;
|
12
12
|
|
13
13
|
constructor(
|
14
14
|
mediaTrack: MediaStreamTrack,
|
15
15
|
sid: string,
|
16
16
|
kind: TrackKind,
|
17
|
-
receiver
|
17
|
+
receiver: RTCRtpReceiver,
|
18
18
|
loggerOptions?: LoggerOptions,
|
19
19
|
) {
|
20
20
|
super(mediaTrack, kind, loggerOptions);
|
@@ -39,6 +39,9 @@ export default abstract class RemoteTrack<
|
|
39
39
|
const onRemoveTrack = (event: MediaStreamTrackEvent) => {
|
40
40
|
if (event.track === this._mediaStreamTrack) {
|
41
41
|
stream.removeEventListener('removetrack', onRemoveTrack);
|
42
|
+
if (this.receiver && 'playoutDelayHint' in this.receiver) {
|
43
|
+
this.receiver.playoutDelayHint = undefined;
|
44
|
+
}
|
42
45
|
this.receiver = undefined;
|
43
46
|
this._currentBitrate = 0;
|
44
47
|
this.emit(TrackEvent.Ended, this);
|
@@ -73,6 +76,39 @@ export default abstract class RemoteTrack<
|
|
73
76
|
return statsReport;
|
74
77
|
}
|
75
78
|
|
79
|
+
/**
|
80
|
+
* Allows to set a playout delay (in seconds) for this track.
|
81
|
+
* A higher value allows for more buffering of the track in the browser
|
82
|
+
* and will result in a delay of media being played back of `delayInSeconds`
|
83
|
+
*/
|
84
|
+
setPlayoutDelay(delayInSeconds: number): void {
|
85
|
+
if (this.receiver) {
|
86
|
+
if ('playoutDelayHint' in this.receiver) {
|
87
|
+
this.receiver.playoutDelayHint = delayInSeconds;
|
88
|
+
} else {
|
89
|
+
this.log.warn('Playout delay not supported in this browser');
|
90
|
+
}
|
91
|
+
} else {
|
92
|
+
this.log.warn('Cannot set playout delay, track already ended');
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Returns the current playout delay (in seconds) of this track.
|
98
|
+
*/
|
99
|
+
getPlayoutDelay(): number {
|
100
|
+
if (this.receiver) {
|
101
|
+
if ('playoutDelayHint' in this.receiver) {
|
102
|
+
return this.receiver.playoutDelayHint as number;
|
103
|
+
} else {
|
104
|
+
this.log.warn('Playout delay not supported in this browser');
|
105
|
+
}
|
106
|
+
} else {
|
107
|
+
this.log.warn('Cannot get playout delay, track already ended');
|
108
|
+
}
|
109
|
+
return 0;
|
110
|
+
}
|
111
|
+
|
76
112
|
/* @internal */
|
77
113
|
startMonitor() {
|
78
114
|
if (!this.monitorInterval) {
|
@@ -26,7 +26,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
26
26
|
constructor(
|
27
27
|
mediaTrack: MediaStreamTrack,
|
28
28
|
sid: string,
|
29
|
-
receiver
|
29
|
+
receiver: RTCRtpReceiver,
|
30
30
|
adaptiveStreamSettings?: AdaptiveStreamSettings,
|
31
31
|
loggerOptions?: LoggerOptions,
|
32
32
|
) {
|
@@ -226,7 +226,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
226
226
|
);
|
227
227
|
|
228
228
|
const backgroundPause =
|
229
|
-
this.adaptiveStreamSettings?.pauseVideoInBackground ?? true // default to true
|
229
|
+
(this.adaptiveStreamSettings?.pauseVideoInBackground ?? true) // default to true
|
230
230
|
? this.isInBackground
|
231
231
|
: false;
|
232
232
|
const isPiPMode = this.elementInfos.some((info) => info.pictureInPicture);
|
@@ -64,7 +64,7 @@ export interface TrackPublishDefaults {
|
|
64
64
|
simulcast?: boolean;
|
65
65
|
|
66
66
|
/**
|
67
|
-
* scalability mode for svc codecs, defaults to '
|
67
|
+
* scalability mode for svc codecs, defaults to 'L3T3_KEY'.
|
68
68
|
* for svc codecs, simulcast is disabled.
|
69
69
|
*/
|
70
70
|
scalabilityMode?: ScalabilityMode;
|
package/src/room/types.ts
CHANGED
package/src/room/utils.ts
CHANGED
@@ -532,8 +532,16 @@ export function toHttpUrl(url: string): string {
|
|
532
532
|
|
533
533
|
export function extractTranscriptionSegments(
|
534
534
|
transcription: TranscriptionModel,
|
535
|
+
firstReceivedTimesMap: Map<string, number>,
|
535
536
|
): TranscriptionSegment[] {
|
536
537
|
return transcription.segments.map(({ id, text, language, startTime, endTime, final }) => {
|
538
|
+
const firstReceivedTime = firstReceivedTimesMap.get(id) ?? Date.now();
|
539
|
+
const lastReceivedTime = Date.now();
|
540
|
+
if (final) {
|
541
|
+
firstReceivedTimesMap.delete(id);
|
542
|
+
} else {
|
543
|
+
firstReceivedTimesMap.set(id, firstReceivedTime);
|
544
|
+
}
|
537
545
|
return {
|
538
546
|
id,
|
539
547
|
text,
|
@@ -541,6 +549,8 @@ export function extractTranscriptionSegments(
|
|
541
549
|
endTime: Number.parseInt(endTime.toString()),
|
542
550
|
final,
|
543
551
|
language,
|
552
|
+
firstReceivedTime,
|
553
|
+
lastReceivedTime,
|
544
554
|
};
|
545
555
|
});
|
546
556
|
}
|
package/src/version.ts
CHANGED