livekit-client 1.15.12 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +27 -15
- package/dist/livekit-client.esm.mjs +1750 -1581
- 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 +0 -2
- 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 +56 -1
- package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc_pb.d.ts +38 -0
- package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -2
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.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 +12 -15
- 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 +8 -5
- 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 +0 -2
- 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 +56 -1
- package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +38 -0
- package/dist/ts4.2/src/room/PCTransport.d.ts +1 -2
- package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
- package/dist/ts4.2/src/room/Room.d.ts +12 -15
- package/dist/ts4.2/src/room/events.d.ts +8 -5
- 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 +10 -9
- package/src/api/SignalClient.ts +2 -6
- 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 +78 -0
- package/src/proto/livekit_rtc_pb.ts +53 -0
- package/src/room/PCTransport.ts +3 -13
- package/src/room/PCTransportManager.ts +2 -3
- package/src/room/RTCEngine.ts +11 -1
- package/src/room/Room.ts +145 -114
- package/src/room/defaults.ts +1 -5
- package/src/room/events.ts +8 -6
- 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
@@ -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
|
|
@@ -30,12 +30,6 @@ export interface TrackPublishDefaults {
|
|
30
30
|
*/
|
31
31
|
videoCodec?: VideoCodec;
|
32
32
|
|
33
|
-
/**
|
34
|
-
* max audio bitrate, defaults to [[AudioPresets.music]]
|
35
|
-
* @deprecated use `audioPreset` instead
|
36
|
-
*/
|
37
|
-
audioBitrate?: number;
|
38
|
-
|
39
33
|
/**
|
40
34
|
* which audio preset should be used for publishing (audio) tracks
|
41
35
|
* defaults to [[AudioPresets.music]]
|