livekit-client 1.1.7 → 1.2.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 +1 -0
- package/dist/livekit-client.esm.mjs +667 -176
- 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/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.map +1 -1
- package/dist/src/room/participant/Participant.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/dist/src/room/track/Track.d.ts +7 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.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/RTCEngine.ts +45 -18
- package/src/room/Room.ts +46 -25
- package/src/room/events.ts +7 -0
- package/src/room/participant/LocalParticipant.ts +45 -7
- package/src/room/participant/Participant.ts +2 -0
- package/src/room/participant/RemoteParticipant.ts +16 -10
- 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/src/room/track/Track.ts +12 -0
- package/src/room/track/TrackPublication.ts +2 -0
- 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/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';
|
@@ -894,11 +894,49 @@ export default class LocalParticipant extends Participant {
|
|
894
894
|
this.unpublishTrack(track.track!);
|
895
895
|
};
|
896
896
|
|
897
|
-
private handleTrackEnded = (track: LocalTrack) => {
|
898
|
-
|
899
|
-
track
|
900
|
-
|
901
|
-
|
897
|
+
private handleTrackEnded = async (track: LocalTrack) => {
|
898
|
+
if (
|
899
|
+
track.source === Track.Source.ScreenShare ||
|
900
|
+
track.source === Track.Source.ScreenShareAudio
|
901
|
+
) {
|
902
|
+
log.debug('unpublishing local track due to TrackEnded', {
|
903
|
+
track: track.sid,
|
904
|
+
});
|
905
|
+
this.unpublishTrack(track);
|
906
|
+
} else if (track.isUserProvided) {
|
907
|
+
await track.pauseUpstream();
|
908
|
+
} else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
|
909
|
+
try {
|
910
|
+
if (isWeb()) {
|
911
|
+
try {
|
912
|
+
const currentPermissions = await navigator?.permissions.query({
|
913
|
+
// the permission query for camera and microphone currently not supported in Safari and Firefox
|
914
|
+
// @ts-ignore
|
915
|
+
name: track.source === Track.Source.Camera ? 'camera' : 'microphone',
|
916
|
+
});
|
917
|
+
if (currentPermissions && currentPermissions.state === 'denied') {
|
918
|
+
log.warn(`user has revoked access to ${track.source}`);
|
919
|
+
|
920
|
+
// detect granted change after permissions were denied to try and resume then
|
921
|
+
currentPermissions.onchange = () => {
|
922
|
+
if (currentPermissions.state !== 'denied') {
|
923
|
+
track.restartTrack();
|
924
|
+
currentPermissions.onchange = null;
|
925
|
+
}
|
926
|
+
};
|
927
|
+
throw new Error('GetUserMedia Permission denied');
|
928
|
+
}
|
929
|
+
} catch (e: any) {
|
930
|
+
// permissions query fails for firefox, we continue and try to restart the track
|
931
|
+
}
|
932
|
+
}
|
933
|
+
log.debug('track ended, attempting to use a different device');
|
934
|
+
await track.restartTrack();
|
935
|
+
} catch (e) {
|
936
|
+
log.warn(`could not restart track, pausing upstream instead`);
|
937
|
+
await track.pauseUpstream();
|
938
|
+
}
|
939
|
+
}
|
902
940
|
};
|
903
941
|
|
904
942
|
private getPublicationForTrack(
|
@@ -975,7 +1013,7 @@ export default class LocalParticipant extends Participant {
|
|
975
1013
|
this.tracks.forEach((track: LocalTrackPublication) => {
|
976
1014
|
if (track.track !== undefined) {
|
977
1015
|
infos.push({
|
978
|
-
cid: track.track.
|
1016
|
+
cid: track.track.mediaStreamID,
|
979
1017
|
track: track.trackInfo,
|
980
1018
|
});
|
981
1019
|
}
|
@@ -12,6 +12,7 @@ import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
|
12
12
|
import { Track } from '../track/Track';
|
13
13
|
import { TrackPublication } from '../track/TrackPublication';
|
14
14
|
import { RemoteTrack } from '../track/types';
|
15
|
+
import log from '../../logger';
|
15
16
|
|
16
17
|
export enum ConnectionQuality {
|
17
18
|
Excellent = 'excellent',
|
@@ -179,6 +180,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
179
180
|
}
|
180
181
|
// set this last so setMetadata can detect changes
|
181
182
|
this.participantInfo = info;
|
183
|
+
log.trace('update participant info', { info });
|
182
184
|
}
|
183
185
|
|
184
186
|
/** @internal */
|
@@ -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
|
}
|
@@ -218,6 +225,10 @@ export default class RemoteParticipant extends Participant {
|
|
218
225
|
// detect removed tracks
|
219
226
|
this.tracks.forEach((publication) => {
|
220
227
|
if (!validTracks.has(publication.trackSid)) {
|
228
|
+
log.trace('detected removed track on remote participant, unpublishing', {
|
229
|
+
publication,
|
230
|
+
participantSid: this.sid,
|
231
|
+
});
|
221
232
|
this.unpublishTrack(publication.trackSid, true);
|
222
233
|
}
|
223
234
|
});
|
@@ -247,13 +258,8 @@ export default class RemoteParticipant extends Participant {
|
|
247
258
|
// also send unsubscribe, if track is actively subscribed
|
248
259
|
const { track } = publication;
|
249
260
|
if (track) {
|
250
|
-
const { isSubscribed } = publication;
|
251
261
|
track.stop();
|
252
262
|
publication.setTrack(undefined);
|
253
|
-
// always send unsubscribed, since apps may rely on this
|
254
|
-
if (isSubscribed) {
|
255
|
-
this.emit(ParticipantEvent.TrackUnsubscribed, track, publication);
|
256
|
-
}
|
257
263
|
}
|
258
264
|
if (sendUnpublish) {
|
259
265
|
this.emit(ParticipantEvent.TrackUnpublished, publication);
|
@@ -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
|
}
|
@@ -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', {
|
package/src/room/track/Track.ts
CHANGED
@@ -37,6 +37,8 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
|
|
37
37
|
|
38
38
|
protected _mediaStreamTrack: MediaStreamTrack;
|
39
39
|
|
40
|
+
protected _mediaStreamID: string;
|
41
|
+
|
40
42
|
protected isInBackground: boolean;
|
41
43
|
|
42
44
|
private backgroundTimeout: ReturnType<typeof setTimeout> | undefined;
|
@@ -47,6 +49,7 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
|
|
47
49
|
super();
|
48
50
|
this.kind = kind;
|
49
51
|
this._mediaStreamTrack = mediaTrack;
|
52
|
+
this._mediaStreamID = mediaTrack.id;
|
50
53
|
this.source = Track.Source.Unknown;
|
51
54
|
if (isWeb()) {
|
52
55
|
this.isInBackground = document.visibilityState === 'hidden';
|
@@ -65,6 +68,15 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
|
|
65
68
|
return this._mediaStreamTrack;
|
66
69
|
}
|
67
70
|
|
71
|
+
/**
|
72
|
+
* @internal
|
73
|
+
* used for keep mediaStream's first id, since it's id might change
|
74
|
+
* if we disable/enable a track
|
75
|
+
*/
|
76
|
+
get mediaStreamID(): string {
|
77
|
+
return this._mediaStreamID;
|
78
|
+
}
|
79
|
+
|
68
80
|
/**
|
69
81
|
* creates a new HTMLAudioElement or HTMLVideoElement, attaches to it, and returns it
|
70
82
|
*/
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
|
+
import log from '../../logger';
|
2
3
|
import { TrackInfo } from '../../proto/livekit_models';
|
3
4
|
import { TrackEvent } from '../events';
|
4
5
|
import LocalAudioTrack from './LocalAudioTrack';
|
@@ -108,6 +109,7 @@ export class TrackPublication extends EventEmitter {
|
|
108
109
|
this.simulcasted = info.simulcast;
|
109
110
|
}
|
110
111
|
this.trackInfo = info;
|
112
|
+
log.trace('update publication info', { info });
|
111
113
|
}
|
112
114
|
}
|
113
115
|
|
@@ -1,13 +0,0 @@
|
|
1
|
-
export default class Queue {
|
2
|
-
private queue;
|
3
|
-
private running;
|
4
|
-
constructor();
|
5
|
-
enqueue(cb: () => void): void;
|
6
|
-
dequeue(): void;
|
7
|
-
run(): Promise<void>;
|
8
|
-
pause(): void;
|
9
|
-
reset(): void;
|
10
|
-
isRunning(): boolean;
|
11
|
-
isEmpty(): boolean;
|
12
|
-
}
|
13
|
-
//# sourceMappingURL=RequestQueue.d.ts.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"RequestQueue.d.ts","sourceRoot":"","sources":["../../../src/api/RequestQueue.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,OAAO,KAAK;IACxB,OAAO,CAAC,KAAK,CAAoB;IAEjC,OAAO,CAAC,OAAO,CAAU;;IAOzB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI;IAKtB,OAAO;IAMD,GAAG;IAWT,KAAK;IAKL,KAAK;IAML,SAAS;IAIT,OAAO;CAGR"}
|
package/src/api/RequestQueue.ts
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
import log from '../logger';
|
2
|
-
|
3
|
-
export default class Queue {
|
4
|
-
private queue: Array<() => void>;
|
5
|
-
|
6
|
-
private running: boolean;
|
7
|
-
|
8
|
-
constructor() {
|
9
|
-
this.queue = [];
|
10
|
-
this.running = false;
|
11
|
-
}
|
12
|
-
|
13
|
-
enqueue(cb: () => void) {
|
14
|
-
log.trace('enqueuing request to fire later');
|
15
|
-
this.queue.push(cb);
|
16
|
-
}
|
17
|
-
|
18
|
-
dequeue() {
|
19
|
-
const evt = this.queue.shift();
|
20
|
-
if (evt) evt();
|
21
|
-
log.trace('firing request from queue');
|
22
|
-
}
|
23
|
-
|
24
|
-
async run() {
|
25
|
-
if (this.running) return;
|
26
|
-
log.trace('start queue');
|
27
|
-
this.running = true;
|
28
|
-
while (this.running && this.queue.length > 0) {
|
29
|
-
this.dequeue();
|
30
|
-
}
|
31
|
-
this.running = false;
|
32
|
-
log.trace('queue finished');
|
33
|
-
}
|
34
|
-
|
35
|
-
pause() {
|
36
|
-
log.trace('pausing queue');
|
37
|
-
this.running = false;
|
38
|
-
}
|
39
|
-
|
40
|
-
reset() {
|
41
|
-
log.trace('resetting queue');
|
42
|
-
this.running = false;
|
43
|
-
this.queue = [];
|
44
|
-
}
|
45
|
-
|
46
|
-
isRunning() {
|
47
|
-
return this.running;
|
48
|
-
}
|
49
|
-
|
50
|
-
isEmpty() {
|
51
|
-
return this.queue.length === 0;
|
52
|
-
}
|
53
|
-
}
|