livekit-client 1.1.8 → 1.2.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 +1 -0
- package/dist/livekit-client.esm.mjs +677 -187
- 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 +4 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.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/proto/livekit_models.d.ts +234 -0
- package/dist/src/proto/livekit_models.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc.d.ts +944 -6
- package/dist/src/proto/livekit_rtc.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 +3 -2
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -3
- 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/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +2 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.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 +3 -1
- package/src/api/SignalClient.ts +21 -6
- package/src/index.ts +6 -2
- package/src/options.ts +1 -0
- package/src/proto/livekit_models.ts +179 -4
- package/src/proto/livekit_rtc.ts +14 -1
- package/src/room/PCTransport.ts +39 -0
- package/src/room/RTCEngine.ts +44 -18
- package/src/room/Room.ts +30 -24
- 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/LocalAudioTrack.ts +16 -12
- package/src/room/track/LocalTrack.ts +41 -25
- package/src/room/track/LocalVideoTrack.ts +15 -11
- package/src/room/track/RemoteTrack.ts +1 -0
- package/src/room/track/RemoteTrackPublication.ts +37 -11
- package/dist/src/api/RequestQueue.d.ts +0 -13
- package/dist/src/api/RequestQueue.d.ts.map +0 -1
- package/src/api/RequestQueue.ts +0 -53
package/src/room/Room.ts
CHANGED
@@ -5,6 +5,7 @@ import log from '../logger';
|
|
5
5
|
import { RoomConnectOptions, RoomOptions } from '../options';
|
6
6
|
import {
|
7
7
|
DataPacket_Kind,
|
8
|
+
DisconnectReason,
|
8
9
|
ParticipantInfo,
|
9
10
|
ParticipantInfo_State,
|
10
11
|
ParticipantPermission,
|
@@ -150,8 +151,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
150
151
|
this.onTrackAdded(mediaTrack, stream, receiver);
|
151
152
|
},
|
152
153
|
)
|
153
|
-
.on(EngineEvent.Disconnected, () => {
|
154
|
-
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
154
|
+
.on(EngineEvent.Disconnected, (reason?: DisconnectReason) => {
|
155
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason);
|
155
156
|
})
|
156
157
|
.on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
|
157
158
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
@@ -357,7 +358,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
357
358
|
/**
|
358
359
|
* disconnects the room, emits [[RoomEvent.Disconnected]]
|
359
360
|
*/
|
360
|
-
disconnect = (stopTracks = true) => {
|
361
|
+
disconnect = async (stopTracks = true) => {
|
362
|
+
log.info('disconnect from room', { identity: this.localParticipant.identity });
|
361
363
|
if (this.state === ConnectionState.Connecting) {
|
362
364
|
// try aborting pending connection attempt
|
363
365
|
log.warn('abort connection attempt');
|
@@ -366,14 +368,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
366
368
|
}
|
367
369
|
// send leave
|
368
370
|
if (this.engine?.client.isConnected) {
|
369
|
-
this.engine.client.sendLeave();
|
371
|
+
await this.engine.client.sendLeave();
|
370
372
|
}
|
371
373
|
// close engine (also closes client)
|
372
374
|
if (this.engine) {
|
373
375
|
this.engine.close();
|
374
376
|
}
|
375
377
|
|
376
|
-
this.handleDisconnect(stopTracks);
|
378
|
+
this.handleDisconnect(stopTracks, DisconnectReason.CLIENT_INITIATED);
|
377
379
|
/* @ts-ignore */
|
378
380
|
this.engine = undefined;
|
379
381
|
};
|
@@ -551,14 +553,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
551
553
|
// at that time, ICE connectivity has not been established so the track is not
|
552
554
|
// technically subscribed.
|
553
555
|
// We'll defer these events until when the room is connected or eventually disconnected.
|
554
|
-
if (this.
|
555
|
-
|
556
|
+
if (this.connectFuture) {
|
557
|
+
this.connectFuture.promise.then(() => {
|
556
558
|
this.onTrackAdded(mediaTrack, stream, receiver);
|
557
|
-
}
|
559
|
+
});
|
558
560
|
return;
|
559
561
|
}
|
560
562
|
if (this.state === ConnectionState.Disconnected) {
|
561
563
|
log.warn('skipping incoming track after Room disconnected');
|
564
|
+
return;
|
562
565
|
}
|
563
566
|
const parts = unpackStreamId(stream.id);
|
564
567
|
const participantId = parts[0];
|
@@ -644,7 +647,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
644
647
|
);
|
645
648
|
};
|
646
649
|
|
647
|
-
private handleDisconnect(shouldStopTracks = true) {
|
650
|
+
private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
|
651
|
+
if (this.state === ConnectionState.Disconnected) {
|
652
|
+
return;
|
653
|
+
}
|
648
654
|
this.participants.forEach((p) => {
|
649
655
|
p.tracks.forEach((pub) => {
|
650
656
|
p.unpublishTrack(pub.trackSid);
|
@@ -660,6 +666,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
660
666
|
pub.track?.stop();
|
661
667
|
}
|
662
668
|
});
|
669
|
+
this.localParticipant.tracks.clear();
|
670
|
+
this.localParticipant.videoTracks.clear();
|
671
|
+
this.localParticipant.audioTracks.clear();
|
663
672
|
|
664
673
|
this.participants.clear();
|
665
674
|
this.activeSpeakers = [];
|
@@ -672,7 +681,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
672
681
|
navigator.mediaDevices?.removeEventListener('devicechange', this.handleDeviceChange);
|
673
682
|
}
|
674
683
|
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
675
|
-
this.emit(RoomEvent.Disconnected);
|
684
|
+
this.emit(RoomEvent.Disconnected, reason);
|
676
685
|
}
|
677
686
|
|
678
687
|
private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
|
@@ -821,18 +830,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
821
830
|
return;
|
822
831
|
}
|
823
832
|
|
824
|
-
pub.
|
825
|
-
participant.emit(
|
826
|
-
ParticipantEvent.TrackSubscriptionPermissionChanged,
|
827
|
-
pub,
|
828
|
-
pub.subscriptionStatus,
|
829
|
-
);
|
830
|
-
this.emitWhenConnected(
|
831
|
-
RoomEvent.TrackSubscriptionPermissionChanged,
|
832
|
-
pub,
|
833
|
-
pub.subscriptionStatus,
|
834
|
-
participant,
|
835
|
-
);
|
833
|
+
pub.setAllowed(update.allowed);
|
836
834
|
};
|
837
835
|
|
838
836
|
private handleDataPacket = (userPacket: UserPacket, kind: DataPacket_Kind) => {
|
@@ -969,7 +967,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
969
967
|
participant,
|
970
968
|
);
|
971
969
|
},
|
972
|
-
)
|
970
|
+
)
|
971
|
+
.on(ParticipantEvent.TrackSubscriptionPermissionChanged, (pub, status) => {
|
972
|
+
this.emitWhenConnected(
|
973
|
+
RoomEvent.TrackSubscriptionPermissionChanged,
|
974
|
+
pub,
|
975
|
+
status,
|
976
|
+
participant,
|
977
|
+
);
|
978
|
+
});
|
973
979
|
|
974
980
|
// update info at the end after callbacks have been set up
|
975
981
|
if (info) {
|
@@ -1090,7 +1096,7 @@ export default Room;
|
|
1090
1096
|
export type RoomEventCallbacks = {
|
1091
1097
|
reconnecting: () => void;
|
1092
1098
|
reconnected: () => void;
|
1093
|
-
disconnected: () => void;
|
1099
|
+
disconnected: (reason?: DisconnectReason) => void;
|
1094
1100
|
/** @deprecated stateChanged has been renamed to connectionStateChanged */
|
1095
1101
|
stateChanged: (state: ConnectionState) => void;
|
1096
1102
|
connectionStateChanged: (state: ConnectionState) => void;
|
package/src/room/events.ts
CHANGED
@@ -402,6 +402,8 @@ export enum TrackEvent {
|
|
402
402
|
Muted = 'muted',
|
403
403
|
Unmuted = 'unmuted',
|
404
404
|
Ended = 'ended',
|
405
|
+
Subscribed = 'subscribed',
|
406
|
+
Unsubscribed = 'unsubscribed',
|
405
407
|
/** @internal */
|
406
408
|
UpdateSettings = 'updateSettings',
|
407
409
|
/** @internal */
|
@@ -433,4 +435,9 @@ export enum TrackEvent {
|
|
433
435
|
* Only fires on LocalTracks
|
434
436
|
*/
|
435
437
|
UpstreamResumed = 'upstreamResumed',
|
438
|
+
/**
|
439
|
+
* @internal
|
440
|
+
* Fires on RemoteTrackPublication
|
441
|
+
*/
|
442
|
+
SubscriptionPermissionChanged = 'subscriptionPermissionChanged',
|
436
443
|
}
|
@@ -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 */
|
@@ -35,22 +35,26 @@ export default class LocalAudioTrack extends LocalTrack {
|
|
35
35
|
}
|
36
36
|
|
37
37
|
async mute(): Promise<LocalAudioTrack> {
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
await this.muteQueue.run(async () => {
|
39
|
+
// disabled special handling as it will cause BT headsets to switch communication modes
|
40
|
+
if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
|
41
|
+
log.debug('stopping mic track');
|
42
|
+
// also stop the track, so that microphone indicator is turned off
|
43
|
+
this._mediaStreamTrack.stop();
|
44
|
+
}
|
45
|
+
await super.mute();
|
46
|
+
});
|
45
47
|
return this;
|
46
48
|
}
|
47
49
|
|
48
50
|
async unmute(): Promise<LocalAudioTrack> {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
await this.muteQueue.run(async () => {
|
52
|
+
if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
|
53
|
+
log.debug('reacquiring mic track');
|
54
|
+
await this.restartTrack();
|
55
|
+
}
|
56
|
+
await super.unmute();
|
57
|
+
});
|
54
58
|
return this;
|
55
59
|
}
|
56
60
|
|
@@ -1,9 +1,10 @@
|
|
1
|
+
import Queue from 'async-await-queue';
|
1
2
|
import log from '../../logger';
|
2
3
|
import DeviceManager from '../DeviceManager';
|
3
4
|
import { TrackInvalidError } from '../errors';
|
4
5
|
import { TrackEvent } from '../events';
|
5
|
-
import { VideoCodec } from './options';
|
6
6
|
import { getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, isMobile } from '../utils';
|
7
|
+
import { VideoCodec } from './options';
|
7
8
|
import { attachToElement, detachTrack, Track } from './Track';
|
8
9
|
|
9
10
|
export default class LocalTrack extends Track {
|
@@ -21,6 +22,8 @@ export default class LocalTrack extends Track {
|
|
21
22
|
|
22
23
|
protected providedByUser: boolean;
|
23
24
|
|
25
|
+
protected muteQueue: Queue;
|
26
|
+
|
24
27
|
protected constructor(
|
25
28
|
mediaTrack: MediaStreamTrack,
|
26
29
|
kind: Track.Kind,
|
@@ -33,6 +36,7 @@ export default class LocalTrack extends Track {
|
|
33
36
|
this.reacquireTrack = false;
|
34
37
|
this.wasMuted = false;
|
35
38
|
this.providedByUser = userProvidedTrack;
|
39
|
+
this.muteQueue = new Queue();
|
36
40
|
}
|
37
41
|
|
38
42
|
get id(): string {
|
@@ -101,7 +105,9 @@ export default class LocalTrack extends Track {
|
|
101
105
|
// on Safari, the old audio track must be stopped before attempting to acquire
|
102
106
|
// the new track, otherwise the new track will stop with
|
103
107
|
// 'A MediaStreamTrack ended due to a capture failure`
|
104
|
-
this.
|
108
|
+
if (!this.providedByUser) {
|
109
|
+
this._mediaStreamTrack.stop();
|
110
|
+
}
|
105
111
|
|
106
112
|
track.addEventListener('ended', this.handleEnded);
|
107
113
|
log.debug('replace MediaStreamTrack');
|
@@ -111,6 +117,8 @@ export default class LocalTrack extends Track {
|
|
111
117
|
}
|
112
118
|
this._mediaStreamTrack = track;
|
113
119
|
|
120
|
+
await this.resumeUpstream();
|
121
|
+
|
114
122
|
this.attachedElements.forEach((el) => {
|
115
123
|
attachToElement(track, el);
|
116
124
|
});
|
@@ -160,6 +168,8 @@ export default class LocalTrack extends Track {
|
|
160
168
|
|
161
169
|
this._mediaStreamTrack = newTrack;
|
162
170
|
|
171
|
+
await this.resumeUpstream();
|
172
|
+
|
163
173
|
this.attachedElements.forEach((el) => {
|
164
174
|
attachToElement(newTrack, el);
|
165
175
|
});
|
@@ -170,6 +180,7 @@ export default class LocalTrack extends Track {
|
|
170
180
|
}
|
171
181
|
|
172
182
|
protected setTrackMuted(muted: boolean) {
|
183
|
+
log.debug(`setting ${this.kind} track ${muted ? 'muted' : 'unmuted'}`);
|
173
184
|
if (this.isMuted === muted) {
|
174
185
|
return;
|
175
186
|
}
|
@@ -215,31 +226,36 @@ export default class LocalTrack extends Track {
|
|
215
226
|
};
|
216
227
|
|
217
228
|
async pauseUpstream() {
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
this.
|
229
|
-
|
229
|
+
this.muteQueue.run(async () => {
|
230
|
+
if (this._isUpstreamPaused === true) {
|
231
|
+
return;
|
232
|
+
}
|
233
|
+
if (!this.sender) {
|
234
|
+
log.warn('unable to pause upstream for an unpublished track');
|
235
|
+
return;
|
236
|
+
}
|
237
|
+
|
238
|
+
this._isUpstreamPaused = true;
|
239
|
+
this.emit(TrackEvent.UpstreamPaused, this);
|
240
|
+
const emptyTrack =
|
241
|
+
this.kind === Track.Kind.Audio ? getEmptyAudioStreamTrack() : getEmptyVideoStreamTrack();
|
242
|
+
await this.sender.replaceTrack(emptyTrack);
|
243
|
+
});
|
230
244
|
}
|
231
245
|
|
232
246
|
async resumeUpstream() {
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
247
|
+
this.muteQueue.run(async () => {
|
248
|
+
if (this._isUpstreamPaused === false) {
|
249
|
+
return;
|
250
|
+
}
|
251
|
+
if (!this.sender) {
|
252
|
+
log.warn('unable to resume upstream for an unpublished track');
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
this._isUpstreamPaused = false;
|
256
|
+
this.emit(TrackEvent.UpstreamResumed, this);
|
257
|
+
|
258
|
+
await this.sender.replaceTrack(this._mediaStreamTrack);
|
259
|
+
});
|
244
260
|
}
|
245
261
|
}
|
@@ -86,21 +86,25 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
86
86
|
}
|
87
87
|
|
88
88
|
async mute(): Promise<LocalVideoTrack> {
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
89
|
+
await this.muteQueue.run(async () => {
|
90
|
+
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
91
|
+
log.debug('stopping camera track');
|
92
|
+
// also stop the track, so that camera indicator is turned off
|
93
|
+
this._mediaStreamTrack.stop();
|
94
|
+
}
|
95
|
+
await super.mute();
|
96
|
+
});
|
95
97
|
return this;
|
96
98
|
}
|
97
99
|
|
98
100
|
async unmute(): Promise<LocalVideoTrack> {
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
await this.muteQueue.run(async () => {
|
102
|
+
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
103
|
+
log.debug('reacquiring camera track');
|
104
|
+
await this.restartTrack();
|
105
|
+
}
|
106
|
+
await super.unmute();
|
107
|
+
});
|
104
108
|
return this;
|
105
109
|
}
|
106
110
|
|
@@ -21,6 +21,7 @@ export default abstract class RemoteTrack extends Track {
|
|
21
21
|
setMuted(muted: boolean) {
|
22
22
|
if (this.isMuted !== muted) {
|
23
23
|
this.isMuted = muted;
|
24
|
+
this._mediaStreamTrack.enabled = !muted;
|
24
25
|
this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this);
|
25
26
|
}
|
26
27
|
}
|