livekit-client 1.15.10 → 2.0.0
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 +21 -17
- package/dist/livekit-client.esm.mjs +1603 -1493
- 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 +1 -3
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +3 -9
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/proto/livekit_models_pb.d.ts +47 -0
- package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +14 -16
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +0 -4
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +8 -25
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +6 -10
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +9 -6
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +4 -5
- package/dist/src/room/timers.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +3 -3
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +2 -2
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +5 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +0 -5
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +11 -3
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +1 -3
- package/dist/ts4.2/src/index.d.ts +3 -3
- package/dist/ts4.2/src/options.d.ts +3 -9
- package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +47 -0
- package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
- package/dist/ts4.2/src/room/Room.d.ts +14 -16
- package/dist/ts4.2/src/room/events.d.ts +0 -4
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +8 -25
- package/dist/ts4.2/src/room/participant/Participant.d.ts +6 -10
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +9 -6
- package/dist/ts4.2/src/room/timers.d.ts +4 -5
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -3
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +2 -2
- package/dist/ts4.2/src/room/track/Track.d.ts +5 -0
- package/dist/ts4.2/src/room/track/options.d.ts +0 -5
- package/dist/ts4.2/src/room/types.d.ts +11 -3
- package/dist/ts4.2/src/version.d.ts +1 -1
- package/package.json +8 -7
- package/src/api/SignalClient.ts +10 -10
- package/src/e2ee/E2eeManager.ts +2 -2
- package/src/index.ts +2 -4
- package/src/options.ts +3 -10
- package/src/proto/livekit_models_pb.ts +66 -0
- package/src/room/RTCEngine.ts +6 -1
- package/src/room/Room.ts +169 -129
- package/src/room/defaults.ts +1 -5
- package/src/room/events.ts +0 -5
- package/src/room/participant/LocalParticipant.ts +36 -77
- package/src/room/participant/Participant.ts +23 -24
- package/src/room/participant/RemoteParticipant.ts +27 -24
- package/src/room/track/LocalVideoTrack.test.ts +1 -1
- package/src/room/track/LocalVideoTrack.ts +11 -7
- package/src/room/track/RemoteTrackPublication.ts +2 -7
- package/src/room/track/Track.ts +10 -1
- package/src/room/track/options.ts +0 -6
- package/src/room/types.ts +11 -4
- package/src/version.ts +1 -1
package/src/room/defaults.ts
CHANGED
@@ -10,10 +10,6 @@ import { AudioPresets, ScreenSharePresets, VideoPresets } from './track/options'
|
|
10
10
|
export const defaultVideoCodec = 'vp8';
|
11
11
|
|
12
12
|
export const publishDefaults: TrackPublishDefaults = {
|
13
|
-
/**
|
14
|
-
* @deprecated
|
15
|
-
*/
|
16
|
-
audioBitrate: AudioPresets.music.maxBitrate,
|
17
13
|
audioPreset: AudioPresets.music,
|
18
14
|
dtx: true,
|
19
15
|
red: true,
|
@@ -41,7 +37,7 @@ export const roomOptionDefaults: InternalRoomOptions = {
|
|
41
37
|
stopLocalTrackOnUnpublish: true,
|
42
38
|
reconnectPolicy: new DefaultReconnectPolicy(),
|
43
39
|
disconnectOnPageLeave: true,
|
44
|
-
|
40
|
+
webAudioMix: true,
|
45
41
|
} as const;
|
46
42
|
|
47
43
|
export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
|
package/src/room/events.ts
CHANGED
@@ -38,11 +38,6 @@ export enum RoomEvent {
|
|
38
38
|
*/
|
39
39
|
ConnectionStateChanged = 'connectionStateChanged',
|
40
40
|
|
41
|
-
/**
|
42
|
-
* @deprecated StateChanged has been renamed to ConnectionStateChanged
|
43
|
-
*/
|
44
|
-
StateChanged = 'connectionStateChanged',
|
45
|
-
|
46
41
|
/**
|
47
42
|
* When input or output devices on the machine have changed.
|
48
43
|
*/
|
@@ -53,7 +53,6 @@ import {
|
|
53
53
|
import Participant from './Participant';
|
54
54
|
import type { ParticipantTrackPermission } from './ParticipantTrackPermission';
|
55
55
|
import { trackPermissionToProto } from './ParticipantTrackPermission';
|
56
|
-
import RemoteParticipant from './RemoteParticipant';
|
57
56
|
import {
|
58
57
|
computeTrackBackupEncodings,
|
59
58
|
computeVideoEncodings,
|
@@ -61,12 +60,12 @@ import {
|
|
61
60
|
} from './publishUtils';
|
62
61
|
|
63
62
|
export default class LocalParticipant extends Participant {
|
64
|
-
|
63
|
+
audioTrackPublications: Map<string, LocalTrackPublication>;
|
65
64
|
|
66
|
-
|
65
|
+
videoTrackPublications: Map<string, LocalTrackPublication>;
|
67
66
|
|
68
67
|
/** map of track sid => all published tracks */
|
69
|
-
|
68
|
+
trackPublications: Map<string, LocalTrackPublication>;
|
70
69
|
|
71
70
|
/** @internal */
|
72
71
|
engine: RTCEngine;
|
@@ -99,9 +98,9 @@ export default class LocalParticipant extends Participant {
|
|
99
98
|
loggerName: options.loggerName,
|
100
99
|
loggerContextCb: () => this.engine.logContext,
|
101
100
|
});
|
102
|
-
this.
|
103
|
-
this.
|
104
|
-
this.
|
101
|
+
this.audioTrackPublications = new Map();
|
102
|
+
this.videoTrackPublications = new Map();
|
103
|
+
this.trackPublications = new Map();
|
105
104
|
this.engine = engine;
|
106
105
|
this.roomOptions = options;
|
107
106
|
this.setupEngine(engine);
|
@@ -120,15 +119,15 @@ export default class LocalParticipant extends Participant {
|
|
120
119
|
return this.encryptionType !== Encryption_Type.NONE;
|
121
120
|
}
|
122
121
|
|
123
|
-
|
124
|
-
const track = super.
|
122
|
+
getTrackPublication(source: Track.Source): LocalTrackPublication | undefined {
|
123
|
+
const track = super.getTrackPublication(source);
|
125
124
|
if (track) {
|
126
125
|
return track as LocalTrackPublication;
|
127
126
|
}
|
128
127
|
}
|
129
128
|
|
130
|
-
|
131
|
-
const track = super.
|
129
|
+
getTrackPublicationByName(name: string): LocalTrackPublication | undefined {
|
130
|
+
const track = super.getTrackPublicationByName(name);
|
132
131
|
if (track) {
|
133
132
|
return track as LocalTrackPublication;
|
134
133
|
}
|
@@ -140,7 +139,7 @@ export default class LocalParticipant extends Participant {
|
|
140
139
|
setupEngine(engine: RTCEngine) {
|
141
140
|
this.engine = engine;
|
142
141
|
this.engine.on(EngineEvent.RemoteMute, (trackSid: string, muted: boolean) => {
|
143
|
-
const pub = this.
|
142
|
+
const pub = this.trackPublications.get(trackSid);
|
144
143
|
if (!pub || !pub.track) {
|
145
144
|
return;
|
146
145
|
}
|
@@ -290,7 +289,7 @@ export default class LocalParticipant extends Participant {
|
|
290
289
|
publishOptions?: TrackPublishOptions,
|
291
290
|
) {
|
292
291
|
this.log.debug('setTrackEnabled', { ...this.logContext, source, enabled });
|
293
|
-
let track = this.
|
292
|
+
let track = this.getTrackPublication(source);
|
294
293
|
if (enabled) {
|
295
294
|
if (track) {
|
296
295
|
await track.unmute();
|
@@ -351,7 +350,7 @@ export default class LocalParticipant extends Participant {
|
|
351
350
|
// screenshare cannot be muted, unpublish instead
|
352
351
|
if (source === Track.Source.ScreenShare) {
|
353
352
|
track = await this.unpublishTrack(track.track);
|
354
|
-
const screenAudioTrack = this.
|
353
|
+
const screenAudioTrack = this.getTrackPublication(Track.Source.ScreenShareAudio);
|
355
354
|
if (screenAudioTrack && screenAudioTrack.track) {
|
356
355
|
this.unpublishTrack(screenAudioTrack.track);
|
357
356
|
}
|
@@ -573,7 +572,7 @@ export default class LocalParticipant extends Participant {
|
|
573
572
|
|
574
573
|
// is it already published? if so skip
|
575
574
|
let existingPublication: LocalTrackPublication | undefined;
|
576
|
-
this.
|
575
|
+
this.trackPublications.forEach((publication) => {
|
577
576
|
if (!publication.track) {
|
578
577
|
return;
|
579
578
|
}
|
@@ -651,7 +650,7 @@ export default class LocalParticipant extends Participant {
|
|
651
650
|
}
|
652
651
|
|
653
652
|
private async publish(track: LocalTrack, opts: TrackPublishOptions, isStereo: boolean) {
|
654
|
-
const existingTrackOfSource = Array.from(this.
|
653
|
+
const existingTrackOfSource = Array.from(this.trackPublications.values()).find(
|
655
654
|
(publishedTrack) => track instanceof LocalTrack && publishedTrack.source === track.source,
|
656
655
|
);
|
657
656
|
if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
|
@@ -788,7 +787,7 @@ export default class LocalParticipant extends Participant {
|
|
788
787
|
} else if (track.kind === Track.Kind.Audio) {
|
789
788
|
encodings = [
|
790
789
|
{
|
791
|
-
maxBitrate: opts.audioPreset?.maxBitrate
|
790
|
+
maxBitrate: opts.audioPreset?.maxBitrate,
|
792
791
|
priority: opts.audioPreset?.priority ?? 'high',
|
793
792
|
networkPriority: opts.audioPreset?.priority ?? 'high',
|
794
793
|
},
|
@@ -914,7 +913,7 @@ export default class LocalParticipant extends Participant {
|
|
914
913
|
|
915
914
|
// is it not published? if so skip
|
916
915
|
let existingPublication: LocalTrackPublication | undefined;
|
917
|
-
this.
|
916
|
+
this.trackPublications.forEach((publication) => {
|
918
917
|
if (!publication.track) {
|
919
918
|
return;
|
920
919
|
}
|
@@ -947,6 +946,9 @@ export default class LocalParticipant extends Participant {
|
|
947
946
|
return;
|
948
947
|
}
|
949
948
|
const simulcastTrack = track.addSimulcastTrack(videoCodec, encodings);
|
949
|
+
if (!simulcastTrack) {
|
950
|
+
return;
|
951
|
+
}
|
950
952
|
const req = new AddTrackRequest({
|
951
953
|
cid: simulcastTrack.mediaStreamTrack.id,
|
952
954
|
type: Track.kindToProto(track.kind),
|
@@ -1061,13 +1063,13 @@ export default class LocalParticipant extends Participant {
|
|
1061
1063
|
}
|
1062
1064
|
|
1063
1065
|
// remove from our maps
|
1064
|
-
this.
|
1066
|
+
this.trackPublications.delete(publication.trackSid);
|
1065
1067
|
switch (publication.kind) {
|
1066
1068
|
case Track.Kind.Audio:
|
1067
|
-
this.
|
1069
|
+
this.audioTrackPublications.delete(publication.trackSid);
|
1068
1070
|
break;
|
1069
1071
|
case Track.Kind.Video:
|
1070
|
-
this.
|
1072
|
+
this.videoTrackPublications.delete(publication.trackSid);
|
1071
1073
|
break;
|
1072
1074
|
default:
|
1073
1075
|
break;
|
@@ -1093,7 +1095,7 @@ export default class LocalParticipant extends Participant {
|
|
1093
1095
|
|
1094
1096
|
async republishAllTracks(options?: TrackPublishOptions, restartTracks: boolean = true) {
|
1095
1097
|
const localPubs: LocalTrackPublication[] = [];
|
1096
|
-
this.
|
1098
|
+
this.trackPublications.forEach((pub) => {
|
1097
1099
|
if (pub.track) {
|
1098
1100
|
if (options) {
|
1099
1101
|
pub.options = { ...pub.options, ...options };
|
@@ -1132,64 +1134,21 @@ export default class LocalParticipant extends Participant {
|
|
1132
1134
|
* participant in the room if the destination field in publishOptions is empty
|
1133
1135
|
*
|
1134
1136
|
* @param data Uint8Array of the payload. To send string data, use TextEncoder.encode
|
1135
|
-
* @param
|
1136
|
-
* For data that you need delivery guarantee (such as chat messages), use Reliable.
|
1137
|
-
* For data that should arrive as quickly as possible, but you are ok with dropped
|
1138
|
-
* packets, use Lossy.
|
1139
|
-
* @param publishOptions optionally specify a `topic` and `destination`
|
1137
|
+
* @param options optionally specify a `reliable`, `topic` and `destination`
|
1140
1138
|
*/
|
1141
|
-
async publishData(
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
): Promise<void>;
|
1146
|
-
/**
|
1147
|
-
* Publish a new data payload to the room. Data will be forwarded to each
|
1148
|
-
* participant in the room if the destination argument is empty
|
1149
|
-
*
|
1150
|
-
* @param data Uint8Array of the payload. To send string data, use TextEncoder.encode
|
1151
|
-
* @param kind whether to send this as reliable or lossy.
|
1152
|
-
* For data that you need delivery guarantee (such as chat messages), use Reliable.
|
1153
|
-
* For data that should arrive as quickly as possible, but you are ok with dropped
|
1154
|
-
* packets, use Lossy.
|
1155
|
-
* @param destination the participants who will receive the message
|
1156
|
-
*/
|
1157
|
-
async publishData(
|
1158
|
-
data: Uint8Array,
|
1159
|
-
kind: DataPacket_Kind,
|
1160
|
-
destination?: RemoteParticipant[] | string[],
|
1161
|
-
): Promise<void>;
|
1162
|
-
|
1163
|
-
async publishData(
|
1164
|
-
data: Uint8Array,
|
1165
|
-
kind: DataPacket_Kind,
|
1166
|
-
publishOptions: DataPublishOptions | RemoteParticipant[] | string[] = {},
|
1167
|
-
) {
|
1168
|
-
const destination = Array.isArray(publishOptions)
|
1169
|
-
? publishOptions
|
1170
|
-
: publishOptions?.destination;
|
1171
|
-
const destinationSids: string[] = [];
|
1172
|
-
|
1173
|
-
const topic = !Array.isArray(publishOptions) ? publishOptions.topic : undefined;
|
1174
|
-
|
1175
|
-
if (destination !== undefined) {
|
1176
|
-
destination.forEach((val: any) => {
|
1177
|
-
if (val instanceof RemoteParticipant) {
|
1178
|
-
destinationSids.push(val.sid);
|
1179
|
-
} else {
|
1180
|
-
destinationSids.push(val);
|
1181
|
-
}
|
1182
|
-
});
|
1183
|
-
}
|
1139
|
+
async publishData(data: Uint8Array, options: DataPublishOptions = {}): Promise<void> {
|
1140
|
+
const kind = options.reliable ? DataPacket_Kind.RELIABLE : DataPacket_Kind.LOSSY;
|
1141
|
+
const destinationIdentities = options.destinationIdentities;
|
1142
|
+
const topic = options.topic;
|
1184
1143
|
|
1185
1144
|
const packet = new DataPacket({
|
1186
|
-
kind,
|
1145
|
+
kind: kind,
|
1187
1146
|
value: {
|
1188
1147
|
case: 'user',
|
1189
1148
|
value: new UserPacket({
|
1190
|
-
|
1149
|
+
participantIdentity: this.identity,
|
1191
1150
|
payload: data,
|
1192
|
-
|
1151
|
+
destinationIdentities,
|
1193
1152
|
topic,
|
1194
1153
|
}),
|
1195
1154
|
},
|
@@ -1241,7 +1200,7 @@ export default class LocalParticipant extends Participant {
|
|
1241
1200
|
// if server's track mute status doesn't match actual, we'll have to update
|
1242
1201
|
// the server's copy
|
1243
1202
|
info.tracks.forEach((ti) => {
|
1244
|
-
const pub = this.
|
1203
|
+
const pub = this.trackPublications.get(ti.sid);
|
1245
1204
|
|
1246
1205
|
if (pub) {
|
1247
1206
|
const mutedOnServer = pub.isMuted || (pub.track?.isUpstreamPaused ?? false);
|
@@ -1313,7 +1272,7 @@ export default class LocalParticipant extends Participant {
|
|
1313
1272
|
if (!this.roomOptions?.dynacast) {
|
1314
1273
|
return;
|
1315
1274
|
}
|
1316
|
-
const pub = this.
|
1275
|
+
const pub = this.videoTrackPublications.get(update.trackSid);
|
1317
1276
|
if (!pub) {
|
1318
1277
|
this.log.warn('received subscribed quality update for unknown track', {
|
1319
1278
|
...this.logContext,
|
@@ -1341,7 +1300,7 @@ export default class LocalParticipant extends Participant {
|
|
1341
1300
|
};
|
1342
1301
|
|
1343
1302
|
private handleLocalTrackUnpublished = (unpublished: TrackUnpublishedResponse) => {
|
1344
|
-
const track = this.
|
1303
|
+
const track = this.trackPublications.get(unpublished.trackSid);
|
1345
1304
|
if (!track) {
|
1346
1305
|
this.log.warn('received unpublished event for unknown track', {
|
1347
1306
|
...this.logContext,
|
@@ -1415,7 +1374,7 @@ export default class LocalParticipant extends Participant {
|
|
1415
1374
|
track: LocalTrack | MediaStreamTrack,
|
1416
1375
|
): LocalTrackPublication | undefined {
|
1417
1376
|
let publication: LocalTrackPublication | undefined;
|
1418
|
-
this.
|
1377
|
+
this.trackPublications.forEach((pub) => {
|
1419
1378
|
const localTrack = pub.track;
|
1420
1379
|
if (!localTrack) {
|
1421
1380
|
return;
|
@@ -48,12 +48,12 @@ function qualityFromProto(q: ProtoQuality): ConnectionQuality {
|
|
48
48
|
export default class Participant extends (EventEmitter as new () => TypedEmitter<ParticipantEventCallbacks>) {
|
49
49
|
protected participantInfo?: ParticipantInfo;
|
50
50
|
|
51
|
-
|
51
|
+
audioTrackPublications: Map<string, TrackPublication>;
|
52
52
|
|
53
|
-
|
53
|
+
videoTrackPublications: Map<string, TrackPublication>;
|
54
54
|
|
55
55
|
/** map of track sid => all published tracks */
|
56
|
-
|
56
|
+
trackPublications: Map<string, TrackPublication>;
|
57
57
|
|
58
58
|
/** audio level between 0-1.0, 1 being loudest, 0 being softest */
|
59
59
|
audioLevel: number = 0;
|
@@ -94,7 +94,10 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
94
94
|
}
|
95
95
|
|
96
96
|
get isEncrypted() {
|
97
|
-
return
|
97
|
+
return (
|
98
|
+
this.trackPublications.size > 0 &&
|
99
|
+
Array.from(this.trackPublications.values()).every((tr) => tr.isEncrypted)
|
100
|
+
);
|
98
101
|
}
|
99
102
|
|
100
103
|
get isAgent() {
|
@@ -119,23 +122,21 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
119
122
|
this.identity = identity;
|
120
123
|
this.name = name;
|
121
124
|
this.metadata = metadata;
|
122
|
-
this.
|
123
|
-
this.
|
124
|
-
this.
|
125
|
+
this.audioTrackPublications = new Map();
|
126
|
+
this.videoTrackPublications = new Map();
|
127
|
+
this.trackPublications = new Map();
|
125
128
|
}
|
126
129
|
|
127
|
-
|
128
|
-
return Array.from(this.
|
130
|
+
getTrackPublications(): TrackPublication[] {
|
131
|
+
return Array.from(this.trackPublications.values());
|
129
132
|
}
|
130
133
|
|
131
134
|
/**
|
132
135
|
* Finds the first track that matches the source filter, for example, getting
|
133
136
|
* the user's camera track with getTrackBySource(Track.Source.Camera).
|
134
|
-
* @param source
|
135
|
-
* @returns
|
136
137
|
*/
|
137
|
-
|
138
|
-
for (const [, pub] of this.
|
138
|
+
getTrackPublication(source: Track.Source): TrackPublication | undefined {
|
139
|
+
for (const [, pub] of this.trackPublications) {
|
139
140
|
if (pub.source === source) {
|
140
141
|
return pub;
|
141
142
|
}
|
@@ -144,11 +145,9 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
144
145
|
|
145
146
|
/**
|
146
147
|
* Finds the first track that matches the track's name.
|
147
|
-
* @param name
|
148
|
-
* @returns
|
149
148
|
*/
|
150
|
-
|
151
|
-
for (const [, pub] of this.
|
149
|
+
getTrackPublicationByName(name: string): TrackPublication | undefined {
|
150
|
+
for (const [, pub] of this.trackPublications) {
|
152
151
|
if (pub.trackName === name) {
|
153
152
|
return pub;
|
154
153
|
}
|
@@ -160,17 +159,17 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
160
159
|
}
|
161
160
|
|
162
161
|
get isCameraEnabled(): boolean {
|
163
|
-
const track = this.
|
162
|
+
const track = this.getTrackPublication(Track.Source.Camera);
|
164
163
|
return !(track?.isMuted ?? true);
|
165
164
|
}
|
166
165
|
|
167
166
|
get isMicrophoneEnabled(): boolean {
|
168
|
-
const track = this.
|
167
|
+
const track = this.getTrackPublication(Track.Source.Microphone);
|
169
168
|
return !(track?.isMuted ?? true);
|
170
169
|
}
|
171
170
|
|
172
171
|
get isScreenShareEnabled(): boolean {
|
173
|
-
const track = this.
|
172
|
+
const track = this.getTrackPublication(Track.Source.ScreenShare);
|
174
173
|
return !!track;
|
175
174
|
}
|
176
175
|
|
@@ -283,7 +282,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
283
282
|
*/
|
284
283
|
setAudioContext(ctx: AudioContext | undefined) {
|
285
284
|
this.audioContext = ctx;
|
286
|
-
this.
|
285
|
+
this.audioTrackPublications.forEach(
|
287
286
|
(track) =>
|
288
287
|
(track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) &&
|
289
288
|
track.track.setAudioContext(ctx),
|
@@ -305,13 +304,13 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
305
304
|
pub.track.sid = publication.trackSid;
|
306
305
|
}
|
307
306
|
|
308
|
-
this.
|
307
|
+
this.trackPublications.set(publication.trackSid, publication);
|
309
308
|
switch (publication.kind) {
|
310
309
|
case Track.Kind.Audio:
|
311
|
-
this.
|
310
|
+
this.audioTrackPublications.set(publication.trackSid, publication);
|
312
311
|
break;
|
313
312
|
case Track.Kind.Video:
|
314
|
-
this.
|
313
|
+
this.videoTrackPublications.set(publication.trackSid, publication);
|
315
314
|
break;
|
316
315
|
default:
|
317
316
|
break;
|
@@ -16,11 +16,11 @@ import Participant from './Participant';
|
|
16
16
|
import type { ParticipantEventCallbacks } from './Participant';
|
17
17
|
|
18
18
|
export default class RemoteParticipant extends Participant {
|
19
|
-
|
19
|
+
audioTrackPublications: Map<string, RemoteTrackPublication>;
|
20
20
|
|
21
|
-
|
21
|
+
videoTrackPublications: Map<string, RemoteTrackPublication>;
|
22
22
|
|
23
|
-
|
23
|
+
trackPublications: Map<string, RemoteTrackPublication>;
|
24
24
|
|
25
25
|
signalClient: SignalClient;
|
26
26
|
|
@@ -44,9 +44,9 @@ export default class RemoteParticipant extends Participant {
|
|
44
44
|
) {
|
45
45
|
super(sid, identity || '', name, metadata, loggerOptions);
|
46
46
|
this.signalClient = signalClient;
|
47
|
-
this.
|
48
|
-
this.
|
49
|
-
this.
|
47
|
+
this.trackPublications = new Map();
|
48
|
+
this.audioTrackPublications = new Map();
|
49
|
+
this.videoTrackPublications = new Map();
|
50
50
|
this.volumeMap = new Map();
|
51
51
|
}
|
52
52
|
|
@@ -90,15 +90,15 @@ export default class RemoteParticipant extends Participant {
|
|
90
90
|
});
|
91
91
|
}
|
92
92
|
|
93
|
-
|
94
|
-
const track = super.
|
93
|
+
getTrackPublication(source: Track.Source): RemoteTrackPublication | undefined {
|
94
|
+
const track = super.getTrackPublication(source);
|
95
95
|
if (track) {
|
96
96
|
return track as RemoteTrackPublication;
|
97
97
|
}
|
98
98
|
}
|
99
99
|
|
100
|
-
|
101
|
-
const track = super.
|
100
|
+
getTrackPublicationByName(name: string): RemoteTrackPublication | undefined {
|
101
|
+
const track = super.getTrackPublicationByName(name);
|
102
102
|
if (track) {
|
103
103
|
return track as RemoteTrackPublication;
|
104
104
|
}
|
@@ -115,7 +115,7 @@ export default class RemoteParticipant extends Participant {
|
|
115
115
|
source: Track.Source.Microphone | Track.Source.ScreenShareAudio = Track.Source.Microphone,
|
116
116
|
) {
|
117
117
|
this.volumeMap.set(source, volume);
|
118
|
-
const audioPublication = this.
|
118
|
+
const audioPublication = this.getTrackPublication(source);
|
119
119
|
if (audioPublication && audioPublication.track) {
|
120
120
|
(audioPublication.track as RemoteAudioTrack).setVolume(volume);
|
121
121
|
}
|
@@ -127,7 +127,7 @@ export default class RemoteParticipant extends Participant {
|
|
127
127
|
getVolume(
|
128
128
|
source: Track.Source.Microphone | Track.Source.ScreenShareAudio = Track.Source.Microphone,
|
129
129
|
) {
|
130
|
-
const audioPublication = this.
|
130
|
+
const audioPublication = this.getTrackPublication(source);
|
131
131
|
if (audioPublication && audioPublication.track) {
|
132
132
|
return (audioPublication.track as RemoteAudioTrack).getVolume();
|
133
133
|
}
|
@@ -145,14 +145,14 @@ export default class RemoteParticipant extends Participant {
|
|
145
145
|
) {
|
146
146
|
// find the track publication
|
147
147
|
// it's possible for the media track to arrive before participant info
|
148
|
-
let publication = this.
|
148
|
+
let publication = this.getTrackPublicationBySid(sid);
|
149
149
|
|
150
150
|
// it's also possible that the browser didn't honor our original track id
|
151
151
|
// FireFox would use its own local uuid instead of server track id
|
152
152
|
if (!publication) {
|
153
153
|
if (!sid.startsWith('TR')) {
|
154
154
|
// find the first track that matches type
|
155
|
-
this.
|
155
|
+
this.trackPublications.forEach((p) => {
|
156
156
|
if (!publication && mediaTrack.kind === p.kind.toString()) {
|
157
157
|
publication = p;
|
158
158
|
}
|
@@ -224,8 +224,11 @@ export default class RemoteParticipant extends Participant {
|
|
224
224
|
return !!this.participantInfo;
|
225
225
|
}
|
226
226
|
|
227
|
-
|
228
|
-
|
227
|
+
/**
|
228
|
+
* @internal
|
229
|
+
*/
|
230
|
+
getTrackPublicationBySid(sid: Track.SID): RemoteTrackPublication | undefined {
|
231
|
+
return this.trackPublications.get(sid);
|
229
232
|
}
|
230
233
|
|
231
234
|
/** @internal */
|
@@ -243,7 +246,7 @@ export default class RemoteParticipant extends Participant {
|
|
243
246
|
const newTracks = new Map<string, RemoteTrackPublication>();
|
244
247
|
|
245
248
|
info.tracks.forEach((ti) => {
|
246
|
-
let publication = this.
|
249
|
+
let publication = this.getTrackPublicationBySid(ti.sid);
|
247
250
|
if (!publication) {
|
248
251
|
// new publication
|
249
252
|
const kind = Track.kindFromProto(ti.type);
|
@@ -258,7 +261,7 @@ export default class RemoteParticipant extends Participant {
|
|
258
261
|
);
|
259
262
|
publication.updateInfo(ti);
|
260
263
|
newTracks.set(ti.sid, publication);
|
261
|
-
const existingTrackOfSource = Array.from(this.
|
264
|
+
const existingTrackOfSource = Array.from(this.trackPublications.values()).find(
|
262
265
|
(publishedTrack) => publishedTrack.source === publication?.source,
|
263
266
|
);
|
264
267
|
if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
|
@@ -279,7 +282,7 @@ export default class RemoteParticipant extends Participant {
|
|
279
282
|
});
|
280
283
|
|
281
284
|
// detect removed tracks
|
282
|
-
this.
|
285
|
+
this.trackPublications.forEach((publication) => {
|
283
286
|
if (!validTracks.has(publication.trackSid)) {
|
284
287
|
this.log.trace('detected removed track on remote participant, unpublishing', {
|
285
288
|
...this.logContext,
|
@@ -298,7 +301,7 @@ export default class RemoteParticipant extends Participant {
|
|
298
301
|
|
299
302
|
/** @internal */
|
300
303
|
unpublishTrack(sid: Track.SID, sendUnpublish?: boolean) {
|
301
|
-
const publication = <RemoteTrackPublication>this.
|
304
|
+
const publication = <RemoteTrackPublication>this.trackPublications.get(sid);
|
302
305
|
if (!publication) {
|
303
306
|
return;
|
304
307
|
}
|
@@ -311,15 +314,15 @@ export default class RemoteParticipant extends Participant {
|
|
311
314
|
}
|
312
315
|
|
313
316
|
// remove track from maps only after unsubscribed has been fired
|
314
|
-
this.
|
317
|
+
this.trackPublications.delete(sid);
|
315
318
|
|
316
319
|
// remove from the right type map
|
317
320
|
switch (publication.kind) {
|
318
321
|
case Track.Kind.Audio:
|
319
|
-
this.
|
322
|
+
this.audioTrackPublications.delete(sid);
|
320
323
|
break;
|
321
324
|
case Track.Kind.Video:
|
322
|
-
this.
|
325
|
+
this.videoTrackPublications.delete(sid);
|
323
326
|
break;
|
324
327
|
default:
|
325
328
|
break;
|
@@ -336,7 +339,7 @@ export default class RemoteParticipant extends Participant {
|
|
336
339
|
async setAudioOutput(output: AudioOutputOptions) {
|
337
340
|
this.audioOutput = output;
|
338
341
|
const promises: Promise<void>[] = [];
|
339
|
-
this.
|
342
|
+
this.audioTrackPublications.forEach((pub) => {
|
340
343
|
if (pub.track instanceof RemoteAudioTrack) {
|
341
344
|
promises.push(pub.track.setSinkId(output.deviceId ?? 'default'));
|
342
345
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
2
|
-
import { VideoQuality } from '../../proto/livekit_models_pb';
|
3
2
|
import { videoLayersFromEncodings } from './LocalVideoTrack';
|
3
|
+
import { VideoQuality } from './Track';
|
4
4
|
|
5
5
|
describe('videoLayersFromEncodings', () => {
|
6
6
|
it('returns single layer for no encoding', () => {
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import type { SignalClient } from '../../api/SignalClient';
|
2
2
|
import type { StructuredLogger } from '../../logger';
|
3
|
-
import {
|
3
|
+
import { VideoQuality as ProtoVideoQuality, VideoLayer } from '../../proto/livekit_models_pb';
|
4
4
|
import { SubscribedCodec, SubscribedQuality } from '../../proto/livekit_rtc_pb';
|
5
5
|
import { ScalabilityMode } from '../participant/publishUtils';
|
6
6
|
import type { VideoSenderStats } from '../stats';
|
@@ -8,7 +8,7 @@ import { computeBitrate, monitorFrequency } from '../stats';
|
|
8
8
|
import type { LoggerOptions } from '../types';
|
9
9
|
import { Mutex, isFireFox, isMobile, isWeb, unwrapConstraint } from '../utils';
|
10
10
|
import LocalTrack from './LocalTrack';
|
11
|
-
import { Track } from './Track';
|
11
|
+
import { Track, VideoQuality } from './Track';
|
12
12
|
import type { VideoCaptureOptions, VideoCodec } from './options';
|
13
13
|
import type { TrackProcessor } from './processor/types';
|
14
14
|
import { constraintsForOptions } from './utils';
|
@@ -254,9 +254,13 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
254
254
|
}
|
255
255
|
}
|
256
256
|
|
257
|
-
addSimulcastTrack(
|
257
|
+
addSimulcastTrack(
|
258
|
+
codec: VideoCodec,
|
259
|
+
encodings?: RTCRtpEncodingParameters[],
|
260
|
+
): SimulcastTrackInfo | undefined {
|
258
261
|
if (this.simulcastCodecs.has(codec)) {
|
259
|
-
|
262
|
+
this.log.error(`${codec} already added, skipping adding simulcast codec`, this.logContext);
|
263
|
+
return;
|
260
264
|
}
|
261
265
|
const simulcastCodecInfo: SimulcastTrackInfo = {
|
262
266
|
codec,
|
@@ -427,14 +431,14 @@ async function setPublishingLayersForSender(
|
|
427
431
|
const encoding = encodings[0];
|
428
432
|
/* @ts-ignore */
|
429
433
|
// const mode = new ScalabilityMode(encoding.scalabilityMode);
|
430
|
-
let maxQuality =
|
434
|
+
let maxQuality = ProtoVideoQuality.OFF;
|
431
435
|
qualities.forEach((q) => {
|
432
|
-
if (q.enabled && (maxQuality ===
|
436
|
+
if (q.enabled && (maxQuality === ProtoVideoQuality.OFF || q.quality > maxQuality)) {
|
433
437
|
maxQuality = q.quality;
|
434
438
|
}
|
435
439
|
});
|
436
440
|
|
437
|
-
if (maxQuality ===
|
441
|
+
if (maxQuality === ProtoVideoQuality.OFF) {
|
438
442
|
if (encoding.active) {
|
439
443
|
encoding.active = false;
|
440
444
|
hasChanged = true;
|
@@ -1,15 +1,10 @@
|
|
1
|
-
import {
|
2
|
-
ParticipantTracks,
|
3
|
-
SubscriptionError,
|
4
|
-
TrackInfo,
|
5
|
-
VideoQuality,
|
6
|
-
} from '../../proto/livekit_models_pb';
|
1
|
+
import { ParticipantTracks, SubscriptionError, TrackInfo } from '../../proto/livekit_models_pb';
|
7
2
|
import { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc_pb';
|
8
3
|
import { TrackEvent } from '../events';
|
9
4
|
import type { LoggerOptions } from '../types';
|
10
5
|
import type RemoteTrack from './RemoteTrack';
|
11
6
|
import RemoteVideoTrack from './RemoteVideoTrack';
|
12
|
-
import { Track } from './Track';
|
7
|
+
import { Track, VideoQuality } from './Track';
|
13
8
|
import { TrackPublication } from './TrackPublication';
|
14
9
|
|
15
10
|
export default class RemoteTrackPublication extends TrackPublication {
|
package/src/room/track/Track.ts
CHANGED
@@ -2,7 +2,11 @@ import { EventEmitter } from 'events';
|
|
2
2
|
import type TypedEventEmitter from 'typed-emitter';
|
3
3
|
import type { SignalClient } from '../../api/SignalClient';
|
4
4
|
import log, { LoggerNames, StructuredLogger, getLogger } from '../../logger';
|
5
|
-
import {
|
5
|
+
import {
|
6
|
+
VideoQuality as ProtoQuality,
|
7
|
+
TrackSource,
|
8
|
+
TrackType,
|
9
|
+
} from '../../proto/livekit_models_pb';
|
6
10
|
import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc_pb';
|
7
11
|
import { TrackEvent } from '../events';
|
8
12
|
import type { LoggerOptions } from '../types';
|
@@ -15,6 +19,11 @@ const BACKGROUND_REACTION_DELAY = 5000;
|
|
15
19
|
// Safari tracks which audio elements have been "blessed" by the user.
|
16
20
|
const recycledElements: Array<HTMLAudioElement> = [];
|
17
21
|
|
22
|
+
export enum VideoQuality {
|
23
|
+
LOW = ProtoQuality.LOW,
|
24
|
+
MEDIUM = ProtoQuality.MEDIUM,
|
25
|
+
HIGH = ProtoQuality.HIGH,
|
26
|
+
}
|
18
27
|
export abstract class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEventCallbacks>) {
|
19
28
|
kind: Track.Kind;
|
20
29
|
|