Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-pens-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"livekit-client": patch
---

Prevent unmute -> mute -> unmute cycle for track restarts that happen during unmute
9 changes: 6 additions & 3 deletions src/room/track/LocalAudioTrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
!this.isUserProvided
) {
this.log.debug('reacquiring mic track', this.logContext);
await this.restartTrack();
await this.restart(undefined, true);
}
await super.unmute();

Expand All @@ -103,8 +103,11 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
await this.restart(constraints);
}

protected async restart(constraints?: MediaTrackConstraints): Promise<typeof this> {
const track = await super.restart(constraints);
protected async restart(
constraints?: MediaTrackConstraints,
isUnmuting?: boolean,
): Promise<typeof this> {
const track = await super.restart(constraints, isUnmuting);
this.checkForSilence();
return track;
}
Expand Down
13 changes: 9 additions & 4 deletions src/room/track/LocalTrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,11 @@ export default abstract class LocalTrack<
return this._mediaStreamTrack.getSettings();
}

private async setMediaStreamTrack(newTrack: MediaStreamTrack, force?: boolean) {
private async setMediaStreamTrack(
newTrack: MediaStreamTrack,
force?: boolean,
isUnmuting?: boolean,
) {
if (newTrack === this._mediaStreamTrack && !force) {
return;
}
Expand Down Expand Up @@ -204,7 +208,8 @@ export default abstract class LocalTrack<
this._mediaStreamTrack = newTrack;
if (newTrack) {
// sync muted state with the enabled state of the newly provided track
this._mediaStreamTrack.enabled = !this.isMuted;
// if restarting as part of an unmute, set enabled to true directly to avoid mute cycling
this._mediaStreamTrack.enabled = isUnmuting ? true : !this.isMuted;
// when a valid track is replace, we'd want to start producing
await this.resumeUpstream();
this.attachedElements.forEach((el) => {
Expand Down Expand Up @@ -323,7 +328,7 @@ export default abstract class LocalTrack<
}
}

protected async restart(constraints?: MediaTrackConstraints) {
protected async restart(constraints?: MediaTrackConstraints, isUnmuting?: boolean) {
this.manuallyStopped = false;
const unlock = await this.trackChangeLock.lock();

Expand Down Expand Up @@ -366,7 +371,7 @@ export default abstract class LocalTrack<
newTrack.addEventListener('ended', this.handleEnded);
this.log.debug('re-acquired MediaStreamTrack', this.logContext);

await this.setMediaStreamTrack(newTrack);
await this.setMediaStreamTrack(newTrack, false, isUnmuting);
this._constraints = constraints;
this.pendingDeviceChange = false;
this.emit(TrackEvent.Restarted, this);
Expand Down
2 changes: 1 addition & 1 deletion src/room/track/LocalVideoTrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {

if (this.source === Track.Source.Camera && !this.isUserProvided) {
this.log.debug('reacquiring camera track', this.logContext);
await this.restartTrack();
await this.restart(undefined, true);
}
await super.unmute();
return this;
Expand Down
Loading