Skip to content

Commit c786143

Browse files
authored
Presence of Track subscription to take priority over allowed status. (#306)
* Presence of Track subscription to take priority over allowed status. Client may end up with a copy of incorrect RemoteTrackPublication.allowed status, if rapid successions of subscribe/unsubscribe/permission updates occur. * removed unused function * fix disconnect failure due to request queue flushing * improve event handling for Track subscriptions * changeset * improved disconnection handling * mark internal
1 parent e1ddb16 commit c786143

File tree

8 files changed

+92
-38
lines changed

8 files changed

+92
-38
lines changed

.changeset/lucky-icons-punch.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'livekit-client': patch
3+
---
4+
5+
Determine track allowed status primarily by precense of Track

.changeset/olive-maps-trade.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'livekit-client': patch
3+
---
4+
5+
Improved Track event handling for permission changed

.changeset/violet-hats-scream.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'livekit-client': patch
3+
---
4+
5+
Room.disconnect is now an async function

src/api/SignalClient.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import 'webrtc-adapter';
21
import Queue from 'async-await-queue';
2+
import 'webrtc-adapter';
33
import log from '../logger';
44
import {
55
ClientInfo,

src/room/Room.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
358358
/**
359359
* disconnects the room, emits [[RoomEvent.Disconnected]]
360360
*/
361-
disconnect = (stopTracks = true) => {
361+
disconnect = async (stopTracks = true) => {
362+
log.info('disconnect from room', { identity: this.localParticipant.identity });
362363
if (this.state === ConnectionState.Connecting) {
363364
// try aborting pending connection attempt
364365
log.warn('abort connection attempt');
@@ -367,14 +368,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
367368
}
368369
// send leave
369370
if (this.engine?.client.isConnected) {
370-
this.engine.client.sendLeave();
371+
await this.engine.client.sendLeave();
371372
}
372373
// close engine (also closes client)
373374
if (this.engine) {
374375
this.engine.close();
375376
}
376377

377-
this.handleDisconnect(stopTracks);
378+
this.handleDisconnect(stopTracks, DisconnectReason.CLIENT_INITIATED);
378379
/* @ts-ignore */
379380
this.engine = undefined;
380381
};
@@ -647,6 +648,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
647648
};
648649

649650
private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
651+
if (this.state === ConnectionState.Disconnected) {
652+
return;
653+
}
650654
this.participants.forEach((p) => {
651655
p.tracks.forEach((pub) => {
652656
p.unpublishTrack(pub.trackSid);
@@ -662,6 +666,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
662666
pub.track?.stop();
663667
}
664668
});
669+
this.localParticipant.tracks.clear();
670+
this.localParticipant.videoTracks.clear();
671+
this.localParticipant.audioTracks.clear();
665672

666673
this.participants.clear();
667674
this.activeSpeakers = [];
@@ -823,18 +830,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
823830
return;
824831
}
825832

826-
pub._allowed = update.allowed;
827-
participant.emit(
828-
ParticipantEvent.TrackSubscriptionPermissionChanged,
829-
pub,
830-
pub.subscriptionStatus,
831-
);
832-
this.emitWhenConnected(
833-
RoomEvent.TrackSubscriptionPermissionChanged,
834-
pub,
835-
pub.subscriptionStatus,
836-
participant,
837-
);
833+
pub.setAllowed(update.allowed);
838834
};
839835

840836
private handleDataPacket = (userPacket: UserPacket, kind: DataPacket_Kind) => {
@@ -971,7 +967,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
971967
participant,
972968
);
973969
},
974-
);
970+
)
971+
.on(ParticipantEvent.TrackSubscriptionPermissionChanged, (pub, status) => {
972+
this.emitWhenConnected(
973+
RoomEvent.TrackSubscriptionPermissionChanged,
974+
pub,
975+
status,
976+
participant,
977+
);
978+
});
975979

976980
// update info at the end after callbacks have been set up
977981
if (info) {

src/room/events.ts

+7
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ export enum TrackEvent {
402402
Muted = 'muted',
403403
Unmuted = 'unmuted',
404404
Ended = 'ended',
405+
Subscribed = 'subscribed',
406+
Unsubscribed = 'unsubscribed',
405407
/** @internal */
406408
UpdateSettings = 'updateSettings',
407409
/** @internal */
@@ -433,4 +435,9 @@ export enum TrackEvent {
433435
* Only fires on LocalTracks
434436
*/
435437
UpstreamResumed = 'upstreamResumed',
438+
/**
439+
* @internal
440+
* Fires on RemoteTrackPublication
441+
*/
442+
SubscriptionPermissionChanged = 'subscriptionPermissionChanged',
436443
}

src/room/participant/RemoteParticipant.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import RemoteAudioTrack from '../track/RemoteAudioTrack';
77
import RemoteTrackPublication from '../track/RemoteTrackPublication';
88
import RemoteVideoTrack from '../track/RemoteVideoTrack';
99
import { Track } from '../track/Track';
10+
import { TrackPublication } from '../track/TrackPublication';
1011
import { AdaptiveStreamSettings, RemoteTrack } from '../track/types';
1112
import Participant, { ParticipantEventCallbacks } from './Participant';
1213

@@ -49,8 +50,17 @@ export default class RemoteParticipant extends Participant {
4950
});
5051
this.signalClient.sendUpdateSubscription(sub);
5152
});
52-
publication.on(TrackEvent.Ended, (track: RemoteTrack) => {
53-
this.emit(ParticipantEvent.TrackUnsubscribed, track, publication);
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);
5464
});
5565
}
5666

@@ -156,8 +166,6 @@ export default class RemoteParticipant extends Participant {
156166
track.start();
157167

158168
publication.setTrack(track);
159-
// subscription means participant has permissions to subscribe
160-
publication._allowed = true;
161169
// set participant volume on new microphone tracks
162170
if (
163171
this.volume !== undefined &&
@@ -166,7 +174,6 @@ export default class RemoteParticipant extends Participant {
166174
) {
167175
track.setVolume(this.volume);
168176
}
169-
this.emit(ParticipantEvent.TrackSubscribed, track, publication);
170177

171178
return publication;
172179
}
@@ -251,13 +258,8 @@ export default class RemoteParticipant extends Participant {
251258
// also send unsubscribe, if track is actively subscribed
252259
const { track } = publication;
253260
if (track) {
254-
const { isSubscribed } = publication;
255261
track.stop();
256262
publication.setTrack(undefined);
257-
// always send unsubscribed, since apps may rely on this
258-
if (isSubscribed) {
259-
this.emit(ParticipantEvent.TrackUnsubscribed, track, publication);
260-
}
261263
}
262264
if (sendUnpublish) {
263265
this.emit(ParticipantEvent.TrackUnpublished, publication);

src/room/track/RemoteTrackPublication.ts

+37-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default class RemoteTrackPublication extends TrackPublication {
1111
track?: RemoteTrack;
1212

1313
/** @internal */
14-
_allowed = true;
14+
protected allowed = true;
1515

1616
// keeps track of client's desire to subscribe to a track
1717
protected subscribed?: boolean;
@@ -28,6 +28,9 @@ export default class RemoteTrackPublication extends TrackPublication {
2828
*/
2929
setSubscribed(subscribed: boolean) {
3030
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;
3134

3235
const sub: UpdateSubscription = {
3336
trackSids: [this.trackSid],
@@ -46,11 +49,11 @@ export default class RemoteTrackPublication extends TrackPublication {
4649

4750
get subscriptionStatus(): TrackPublication.SubscriptionStatus {
4851
if (this.subscribed === false || !super.isSubscribed) {
52+
if (!this.allowed) {
53+
return TrackPublication.SubscriptionStatus.NotAllowed;
54+
}
4955
return TrackPublication.SubscriptionStatus.Unsubscribed;
5056
}
51-
if (!this._allowed) {
52-
return TrackPublication.SubscriptionStatus.NotAllowed;
53-
}
5457
return TrackPublication.SubscriptionStatus.Subscribed;
5558
}
5659

@@ -61,9 +64,6 @@ export default class RemoteTrackPublication extends TrackPublication {
6164
if (this.subscribed === false) {
6265
return false;
6366
}
64-
if (!this._allowed) {
65-
return false;
66-
}
6767
return super.isSubscribed;
6868
}
6969

@@ -127,11 +127,13 @@ export default class RemoteTrackPublication extends TrackPublication {
127127

128128
/** @internal */
129129
setTrack(track?: Track) {
130-
if (this.track) {
130+
const prevStatus = this.subscriptionStatus;
131+
const prevTrack = this.track;
132+
if (prevTrack) {
131133
// unregister listener
132-
this.track.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
133-
this.track.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
134-
this.track.off(TrackEvent.Ended, this.handleEnded);
134+
prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
135+
prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
136+
prevTrack.off(TrackEvent.Ended, this.handleEnded);
135137
}
136138
super.setTrack(track);
137139
if (track) {
@@ -140,6 +142,22 @@ export default class RemoteTrackPublication extends TrackPublication {
140142
track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
141143
track.on(TrackEvent.Ended, this.handleEnded);
142144
}
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);
143161
}
144162

145163
/** @internal */
@@ -149,6 +167,14 @@ export default class RemoteTrackPublication extends TrackPublication {
149167
this.track?.setMuted(info.muted);
150168
}
151169

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+
152178
private isManualOperationAllowed(): boolean {
153179
if (this.isAdaptiveStream) {
154180
log.warn('adaptive stream is enabled, cannot change track settings', {

0 commit comments

Comments
 (0)