mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
Fix tracks not being resumed in case of a startPublishing vs track
creating race.
This commit is contained in:
@@ -286,6 +286,53 @@ describe("Publisher", () => {
|
|||||||
expect(track!.isUpstreamPaused).toBe(true);
|
expect(track!.isUpstreamPaused).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Resumes upstream for tracks published after startPublishing was called (slow camera)", async () => {
|
||||||
|
videoEnabled$.next(true);
|
||||||
|
|
||||||
|
// Simulate that the upstream ended up paused by the time the track gets
|
||||||
|
// published (e.g. paused while it was still unpublished).
|
||||||
|
const originalPublishTrack = localParticipant.publishTrack;
|
||||||
|
vi.mocked(localParticipant).publishTrack = vi
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(async (track: LocalTrack) => {
|
||||||
|
await track.pauseUpstream();
|
||||||
|
return originalPublishTrack(track);
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolvers = Promise.withResolvers<void>();
|
||||||
|
createTrackLock = resolvers.promise;
|
||||||
|
|
||||||
|
// Track creation is slow (e.g. camera hardware takes time to open)
|
||||||
|
await publisher.createAndSetupTracks();
|
||||||
|
// startPublishing runs before the camera track exists, so its
|
||||||
|
// resumeUpstreams call finds nothing to resume
|
||||||
|
await publisher.startPublishing();
|
||||||
|
expect(
|
||||||
|
localParticipant.getTrackPublication(Track.Source.Camera),
|
||||||
|
).toBeUndefined();
|
||||||
|
|
||||||
|
// The camera opens and the track gets published
|
||||||
|
resolvers.resolve();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
const track = localParticipant.getTrackPublication(
|
||||||
|
Track.Source.Camera,
|
||||||
|
)?.track;
|
||||||
|
expect(track).toBeDefined();
|
||||||
|
expect(track!.resumeUpstream).toHaveBeenCalled();
|
||||||
|
expect(track!.isUpstreamPaused).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Does not pause tracks published after the publisher was destroyed", async () => {
|
||||||
|
await publisher.destroy();
|
||||||
|
|
||||||
|
const track = createMockLocalTrack(Track.Source.Camera);
|
||||||
|
await localParticipant.publishTrack(track);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(track.pauseUpstream).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("Ensure resume upstream when published is called", async () => {
|
it("Ensure resume upstream when published is called", async () => {
|
||||||
videoEnabled$.next(true);
|
videoEnabled$.next(true);
|
||||||
await publisher.createAndSetupTracks();
|
await publisher.createAndSetupTracks();
|
||||||
|
|||||||
@@ -80,11 +80,15 @@ export class Publisher {
|
|||||||
|
|
||||||
this.connection.livekitRoom.localParticipant.on(
|
this.connection.livekitRoom.localParticipant.on(
|
||||||
ParticipantEvent.LocalTrackPublished,
|
ParticipantEvent.LocalTrackPublished,
|
||||||
this.onLocalTrackPublished.bind(this),
|
this.onLocalTrackPublished,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async destroy(): Promise<void> {
|
public async destroy(): Promise<void> {
|
||||||
|
this.connection.livekitRoom.localParticipant.off(
|
||||||
|
ParticipantEvent.LocalTrackPublished,
|
||||||
|
this.onLocalTrackPublished,
|
||||||
|
);
|
||||||
this.scope.end();
|
this.scope.end();
|
||||||
this.logger.info("Scope ended -> unset handler");
|
this.logger.info("Scope ended -> unset handler");
|
||||||
this.muteStates.audio.unsetHandler();
|
this.muteStates.audio.unsetHandler();
|
||||||
@@ -106,9 +110,9 @@ export class Publisher {
|
|||||||
// So for that we use pauseUpStream(): Stops sending media to the server by replacing
|
// So for that we use pauseUpStream(): Stops sending media to the server by replacing
|
||||||
// the sender track with null, but keeps the local MediaStreamTrack active.
|
// the sender track with null, but keeps the local MediaStreamTrack active.
|
||||||
// The user can still see/hear themselves locally, but remote participants see nothing.
|
// The user can still see/hear themselves locally, but remote participants see nothing.
|
||||||
private onLocalTrackPublished(
|
private onLocalTrackPublished = (
|
||||||
localTrackPublication: LocalTrackPublication,
|
localTrackPublication: LocalTrackPublication,
|
||||||
): void {
|
): void => {
|
||||||
this.logger.info("Local track published", localTrackPublication);
|
this.logger.info("Local track published", localTrackPublication);
|
||||||
const lkRoom = this.connection.livekitRoom;
|
const lkRoom = this.connection.livekitRoom;
|
||||||
if (!this.shouldPublish) {
|
if (!this.shouldPublish) {
|
||||||
@@ -116,6 +120,17 @@ export class Publisher {
|
|||||||
this.pauseUpstreams(lkRoom, [localTrackPublication.source]).catch((e) => {
|
this.pauseUpstreams(lkRoom, [localTrackPublication.source]).catch((e) => {
|
||||||
this.logger.error(`Failed to pause upstreams`, e);
|
this.logger.error(`Failed to pause upstreams`, e);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// If startPublishing() ran before this track existed (track creation is
|
||||||
|
// not awaited and e.g. camera hardware can take a while to open), its
|
||||||
|
// resumeUpstreams() call found no track and did nothing. Resume here so
|
||||||
|
// that a track published after startPublishing() actually sends media.
|
||||||
|
// This is a no-op if the upstream is not paused.
|
||||||
|
this.resumeUpstreams(lkRoom, [localTrackPublication.source]).catch(
|
||||||
|
(e) => {
|
||||||
|
this.logger.error(`Failed to resume upstreams`, e);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (localTrackPublication.source === Track.Source.Microphone) {
|
if (localTrackPublication.source === Track.Source.Microphone) {
|
||||||
const muteState = this.muteStates.audio;
|
const muteState = this.muteStates.audio;
|
||||||
|
|||||||
Reference in New Issue
Block a user