livekit-client 2.4.2 → 2.5.1
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/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