livekit-client 1.1.9 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.esm.mjs +218 -70
- 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 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/options.d.ts +1 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +9 -0
- package/dist/src/room/PCTransport.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 +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +8 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +3 -3
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +4 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/SignalClient.ts +3 -1
- package/src/options.ts +1 -0
- package/src/room/PCTransport.ts +39 -0
- package/src/room/RTCEngine.ts +41 -16
- package/src/room/Room.ts +20 -16
- package/src/room/events.ts +7 -0
- package/src/room/participant/LocalParticipant.ts +70 -10
- package/src/room/participant/RemoteParticipant.ts +12 -10
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/track/LocalTrack.ts +4 -0
- package/src/room/track/RemoteTrackPublication.ts +37 -11
@@ -32,7 +32,7 @@ import {
|
|
32
32
|
} from '../track/options';
|
33
33
|
import { Track } from '../track/Track';
|
34
34
|
import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
|
35
|
-
import { isFireFox } from '../utils';
|
35
|
+
import { isFireFox, isWeb } from '../utils';
|
36
36
|
import Participant from './Participant';
|
37
37
|
import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
|
38
38
|
import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
|
@@ -125,8 +125,9 @@ export default class LocalParticipant extends Participant {
|
|
125
125
|
setCameraEnabled(
|
126
126
|
enabled: boolean,
|
127
127
|
options?: VideoCaptureOptions,
|
128
|
+
publishOptions?: TrackPublishOptions,
|
128
129
|
): Promise<LocalTrackPublication | undefined> {
|
129
|
-
return this.setTrackEnabled(Track.Source.Camera, enabled, options);
|
130
|
+
return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
|
130
131
|
}
|
131
132
|
|
132
133
|
/**
|
@@ -138,8 +139,9 @@ export default class LocalParticipant extends Participant {
|
|
138
139
|
setMicrophoneEnabled(
|
139
140
|
enabled: boolean,
|
140
141
|
options?: AudioCaptureOptions,
|
142
|
+
publishOptions?: TrackPublishOptions,
|
141
143
|
): Promise<LocalTrackPublication | undefined> {
|
142
|
-
return this.setTrackEnabled(Track.Source.Microphone, enabled, options);
|
144
|
+
return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
|
143
145
|
}
|
144
146
|
|
145
147
|
/**
|
@@ -149,8 +151,9 @@ export default class LocalParticipant extends Participant {
|
|
149
151
|
setScreenShareEnabled(
|
150
152
|
enabled: boolean,
|
151
153
|
options?: ScreenShareCaptureOptions,
|
154
|
+
publishOptions?: TrackPublishOptions,
|
152
155
|
): Promise<LocalTrackPublication | undefined> {
|
153
|
-
return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options);
|
156
|
+
return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
|
154
157
|
}
|
155
158
|
|
156
159
|
/** @internal */
|
@@ -172,21 +175,25 @@ export default class LocalParticipant extends Participant {
|
|
172
175
|
source: Extract<Track.Source, Track.Source.Camera>,
|
173
176
|
enabled: boolean,
|
174
177
|
options?: VideoCaptureOptions,
|
178
|
+
publishOptions?: TrackPublishOptions,
|
175
179
|
): Promise<LocalTrackPublication | undefined>;
|
176
180
|
private async setTrackEnabled(
|
177
181
|
source: Extract<Track.Source, Track.Source.Microphone>,
|
178
182
|
enabled: boolean,
|
179
183
|
options?: AudioCaptureOptions,
|
184
|
+
publishOptions?: TrackPublishOptions,
|
180
185
|
): Promise<LocalTrackPublication | undefined>;
|
181
186
|
private async setTrackEnabled(
|
182
187
|
source: Extract<Track.Source, Track.Source.ScreenShare>,
|
183
188
|
enabled: boolean,
|
184
189
|
options?: ScreenShareCaptureOptions,
|
190
|
+
publishOptions?: TrackPublishOptions,
|
185
191
|
): Promise<LocalTrackPublication | undefined>;
|
186
192
|
private async setTrackEnabled(
|
187
193
|
source: Track.Source,
|
188
194
|
enabled: true,
|
189
195
|
options?: VideoCaptureOptions | AudioCaptureOptions | ScreenShareCaptureOptions,
|
196
|
+
publishOptions?: TrackPublishOptions,
|
190
197
|
) {
|
191
198
|
log.debug('setTrackEnabled', { source, enabled });
|
192
199
|
let track = this.getTrack(source);
|
@@ -224,7 +231,7 @@ export default class LocalParticipant extends Participant {
|
|
224
231
|
}
|
225
232
|
const publishPromises: Array<Promise<LocalTrackPublication>> = [];
|
226
233
|
for (const localTrack of localTracks) {
|
227
|
-
publishPromises.push(this.publishTrack(localTrack));
|
234
|
+
publishPromises.push(this.publishTrack(localTrack, publishOptions));
|
228
235
|
}
|
229
236
|
const publishedTracks = await Promise.all(publishPromises);
|
230
237
|
// for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
|
@@ -544,6 +551,14 @@ export default class LocalParticipant extends Participant {
|
|
544
551
|
track.codec = opts.videoCodec;
|
545
552
|
}
|
546
553
|
|
554
|
+
if (track.codec === 'av1' && encodings && encodings[0]?.maxBitrate) {
|
555
|
+
this.engine.publisher.setTrackCodecBitrate(
|
556
|
+
req.cid,
|
557
|
+
track.codec,
|
558
|
+
encodings[0].maxBitrate / 1000,
|
559
|
+
);
|
560
|
+
}
|
561
|
+
|
547
562
|
this.engine.negotiate();
|
548
563
|
|
549
564
|
// store RTPSender
|
@@ -642,6 +657,13 @@ export default class LocalParticipant extends Participant {
|
|
642
657
|
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
643
658
|
track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
|
644
659
|
|
660
|
+
if (videoCodec === 'av1' && encodings[0]?.maxBitrate) {
|
661
|
+
this.engine.publisher.setTrackCodecBitrate(
|
662
|
+
req.cid,
|
663
|
+
videoCodec,
|
664
|
+
encodings[0].maxBitrate / 1000,
|
665
|
+
);
|
666
|
+
}
|
645
667
|
this.engine.negotiate();
|
646
668
|
log.debug(`published ${opts.videoCodec} for track ${track.sid}`, { encodings, trackInfo: ti });
|
647
669
|
}
|
@@ -894,11 +916,49 @@ export default class LocalParticipant extends Participant {
|
|
894
916
|
this.unpublishTrack(track.track!);
|
895
917
|
};
|
896
918
|
|
897
|
-
private handleTrackEnded = (track: LocalTrack) => {
|
898
|
-
|
899
|
-
track
|
900
|
-
|
901
|
-
|
919
|
+
private handleTrackEnded = async (track: LocalTrack) => {
|
920
|
+
if (
|
921
|
+
track.source === Track.Source.ScreenShare ||
|
922
|
+
track.source === Track.Source.ScreenShareAudio
|
923
|
+
) {
|
924
|
+
log.debug('unpublishing local track due to TrackEnded', {
|
925
|
+
track: track.sid,
|
926
|
+
});
|
927
|
+
this.unpublishTrack(track);
|
928
|
+
} else if (track.isUserProvided) {
|
929
|
+
await track.pauseUpstream();
|
930
|
+
} else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
|
931
|
+
try {
|
932
|
+
if (isWeb()) {
|
933
|
+
try {
|
934
|
+
const currentPermissions = await navigator?.permissions.query({
|
935
|
+
// the permission query for camera and microphone currently not supported in Safari and Firefox
|
936
|
+
// @ts-ignore
|
937
|
+
name: track.source === Track.Source.Camera ? 'camera' : 'microphone',
|
938
|
+
});
|
939
|
+
if (currentPermissions && currentPermissions.state === 'denied') {
|
940
|
+
log.warn(`user has revoked access to ${track.source}`);
|
941
|
+
|
942
|
+
// detect granted change after permissions were denied to try and resume then
|
943
|
+
currentPermissions.onchange = () => {
|
944
|
+
if (currentPermissions.state !== 'denied') {
|
945
|
+
track.restartTrack();
|
946
|
+
currentPermissions.onchange = null;
|
947
|
+
}
|
948
|
+
};
|
949
|
+
throw new Error('GetUserMedia Permission denied');
|
950
|
+
}
|
951
|
+
} catch (e: any) {
|
952
|
+
// permissions query fails for firefox, we continue and try to restart the track
|
953
|
+
}
|
954
|
+
}
|
955
|
+
log.debug('track ended, attempting to use a different device');
|
956
|
+
await track.restartTrack();
|
957
|
+
} catch (e) {
|
958
|
+
log.warn(`could not restart track, pausing upstream instead`);
|
959
|
+
await track.pauseUpstream();
|
960
|
+
}
|
961
|
+
}
|
902
962
|
};
|
903
963
|
|
904
964
|
private getPublicationForTrack(
|
@@ -7,6 +7,7 @@ import RemoteAudioTrack from '../track/RemoteAudioTrack';
|
|
7
7
|
import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
8
8
|
import RemoteVideoTrack from '../track/RemoteVideoTrack';
|
9
9
|
import { Track } from '../track/Track';
|
10
|
+
import { TrackPublication } from '../track/TrackPublication';
|
10
11
|
import { AdaptiveStreamSettings, RemoteTrack } from '../track/types';
|
11
12
|
import Participant, { ParticipantEventCallbacks } from './Participant';
|
12
13
|
|
@@ -49,8 +50,17 @@ export default class RemoteParticipant extends Participant {
|
|
49
50
|
});
|
50
51
|
this.signalClient.sendUpdateSubscription(sub);
|
51
52
|
});
|
52
|
-
publication.on(
|
53
|
-
|
53
|
+
publication.on(
|
54
|
+
TrackEvent.SubscriptionPermissionChanged,
|
55
|
+
(status: TrackPublication.SubscriptionStatus) => {
|
56
|
+
this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
|
57
|
+
},
|
58
|
+
);
|
59
|
+
publication.on(TrackEvent.Subscribed, (track: RemoteTrack) => {
|
60
|
+
this.emit(ParticipantEvent.TrackSubscribed, track, publication);
|
61
|
+
});
|
62
|
+
publication.on(TrackEvent.Unsubscribed, (previousTrack: RemoteTrack) => {
|
63
|
+
this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
|
54
64
|
});
|
55
65
|
}
|
56
66
|
|
@@ -156,8 +166,6 @@ export default class RemoteParticipant extends Participant {
|
|
156
166
|
track.start();
|
157
167
|
|
158
168
|
publication.setTrack(track);
|
159
|
-
// subscription means participant has permissions to subscribe
|
160
|
-
publication._allowed = true;
|
161
169
|
// set participant volume on new microphone tracks
|
162
170
|
if (
|
163
171
|
this.volume !== undefined &&
|
@@ -166,7 +174,6 @@ export default class RemoteParticipant extends Participant {
|
|
166
174
|
) {
|
167
175
|
track.setVolume(this.volume);
|
168
176
|
}
|
169
|
-
this.emit(ParticipantEvent.TrackSubscribed, track, publication);
|
170
177
|
|
171
178
|
return publication;
|
172
179
|
}
|
@@ -251,13 +258,8 @@ export default class RemoteParticipant extends Participant {
|
|
251
258
|
// also send unsubscribe, if track is actively subscribed
|
252
259
|
const { track } = publication;
|
253
260
|
if (track) {
|
254
|
-
const { isSubscribed } = publication;
|
255
261
|
track.stop();
|
256
262
|
publication.setTrack(undefined);
|
257
|
-
// always send unsubscribed, since apps may rely on this
|
258
|
-
if (isSubscribed) {
|
259
|
-
this.emit(ParticipantEvent.TrackUnsubscribed, track, publication);
|
260
|
-
}
|
261
263
|
}
|
262
264
|
if (sendUnpublish) {
|
263
265
|
this.emit(ParticipantEvent.TrackUnpublished, publication);
|
@@ -106,7 +106,7 @@ export function computeVideoEncodings(
|
|
106
106
|
encodings.push({
|
107
107
|
rid: videoRids[2 - i],
|
108
108
|
scaleResolutionDownBy: 2 ** i,
|
109
|
-
maxBitrate: videoEncoding ? videoEncoding.maxBitrate /
|
109
|
+
maxBitrate: videoEncoding ? videoEncoding.maxBitrate / 3 ** i : 0,
|
110
110
|
/* @ts-ignore */
|
111
111
|
maxFramerate: original.encoding.maxFramerate,
|
112
112
|
/* @ts-ignore */
|
@@ -117,6 +117,8 @@ export default class LocalTrack extends Track {
|
|
117
117
|
}
|
118
118
|
this._mediaStreamTrack = track;
|
119
119
|
|
120
|
+
await this.resumeUpstream();
|
121
|
+
|
120
122
|
this.attachedElements.forEach((el) => {
|
121
123
|
attachToElement(track, el);
|
122
124
|
});
|
@@ -166,6 +168,8 @@ export default class LocalTrack extends Track {
|
|
166
168
|
|
167
169
|
this._mediaStreamTrack = newTrack;
|
168
170
|
|
171
|
+
await this.resumeUpstream();
|
172
|
+
|
169
173
|
this.attachedElements.forEach((el) => {
|
170
174
|
attachToElement(newTrack, el);
|
171
175
|
});
|
@@ -11,7 +11,7 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
11
11
|
track?: RemoteTrack;
|
12
12
|
|
13
13
|
/** @internal */
|
14
|
-
|
14
|
+
protected allowed = true;
|
15
15
|
|
16
16
|
// keeps track of client's desire to subscribe to a track
|
17
17
|
protected subscribed?: boolean;
|
@@ -28,6 +28,9 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
28
28
|
*/
|
29
29
|
setSubscribed(subscribed: boolean) {
|
30
30
|
this.subscribed = subscribed;
|
31
|
+
// reset allowed status when desired subscription state changes
|
32
|
+
// server will notify client via signal message if it's not allowed
|
33
|
+
this.allowed = true;
|
31
34
|
|
32
35
|
const sub: UpdateSubscription = {
|
33
36
|
trackSids: [this.trackSid],
|
@@ -46,11 +49,11 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
46
49
|
|
47
50
|
get subscriptionStatus(): TrackPublication.SubscriptionStatus {
|
48
51
|
if (this.subscribed === false || !super.isSubscribed) {
|
52
|
+
if (!this.allowed) {
|
53
|
+
return TrackPublication.SubscriptionStatus.NotAllowed;
|
54
|
+
}
|
49
55
|
return TrackPublication.SubscriptionStatus.Unsubscribed;
|
50
56
|
}
|
51
|
-
if (!this._allowed) {
|
52
|
-
return TrackPublication.SubscriptionStatus.NotAllowed;
|
53
|
-
}
|
54
57
|
return TrackPublication.SubscriptionStatus.Subscribed;
|
55
58
|
}
|
56
59
|
|
@@ -61,9 +64,6 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
61
64
|
if (this.subscribed === false) {
|
62
65
|
return false;
|
63
66
|
}
|
64
|
-
if (!this._allowed) {
|
65
|
-
return false;
|
66
|
-
}
|
67
67
|
return super.isSubscribed;
|
68
68
|
}
|
69
69
|
|
@@ -127,11 +127,13 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
127
127
|
|
128
128
|
/** @internal */
|
129
129
|
setTrack(track?: Track) {
|
130
|
-
|
130
|
+
const prevStatus = this.subscriptionStatus;
|
131
|
+
const prevTrack = this.track;
|
132
|
+
if (prevTrack) {
|
131
133
|
// unregister listener
|
132
|
-
|
133
|
-
|
134
|
-
|
134
|
+
prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
|
135
|
+
prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
|
136
|
+
prevTrack.off(TrackEvent.Ended, this.handleEnded);
|
135
137
|
}
|
136
138
|
super.setTrack(track);
|
137
139
|
if (track) {
|
@@ -140,6 +142,22 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
140
142
|
track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
|
141
143
|
track.on(TrackEvent.Ended, this.handleEnded);
|
142
144
|
}
|
145
|
+
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
146
|
+
if (!!track !== !!prevTrack) {
|
147
|
+
// when undefined status changes, there's a subscription changed event
|
148
|
+
if (track) {
|
149
|
+
this.emit(TrackEvent.Subscribed, track);
|
150
|
+
} else {
|
151
|
+
this.emit(TrackEvent.Unsubscribed, prevTrack);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
/** @internal */
|
157
|
+
setAllowed(allowed: boolean) {
|
158
|
+
const prevStatus = this.subscriptionStatus;
|
159
|
+
this.allowed = allowed;
|
160
|
+
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
143
161
|
}
|
144
162
|
|
145
163
|
/** @internal */
|
@@ -149,6 +167,14 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
149
167
|
this.track?.setMuted(info.muted);
|
150
168
|
}
|
151
169
|
|
170
|
+
private emitSubscriptionUpdateIfChanged(previousStatus: TrackPublication.SubscriptionStatus) {
|
171
|
+
const currentStatus = this.subscriptionStatus;
|
172
|
+
if (previousStatus === currentStatus) {
|
173
|
+
return;
|
174
|
+
}
|
175
|
+
this.emit(TrackEvent.SubscriptionPermissionChanged, currentStatus, previousStatus);
|
176
|
+
}
|
177
|
+
|
152
178
|
private isManualOperationAllowed(): boolean {
|
153
179
|
if (this.isAdaptiveStream) {
|
154
180
|
log.warn('adaptive stream is enabled, cannot change track settings', {
|