mirror of
https://github.com/vector-im/element-call.git
synced 2026-11-07 00:52:28 +00:00
fix race in processor setup vs camera start
This commit is contained in:
@@ -1746,6 +1746,43 @@ describe("a camera with a video processor", () => {
|
||||
expect(participation!.publish).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("turns the camera on only once its processor is on the track", async () => {
|
||||
const setHandler = vi.spyOn(MuteState.prototype, "setHandler");
|
||||
onTestFinished(() => setHandler.mockRestore());
|
||||
const effects$ = new BehaviorSubject<BackgroundEffectsState>({
|
||||
supported: true,
|
||||
processor: undefined,
|
||||
wait: "preparing",
|
||||
});
|
||||
let participation: RTCParticipation | undefined;
|
||||
withCallViewModel(
|
||||
{},
|
||||
(_vm, _session, { rtcParticipation }) => {
|
||||
participation = rtcParticipation;
|
||||
},
|
||||
{ backgroundEffectsState$: effects$ },
|
||||
);
|
||||
const camera = participation!.localMember$.value?.tracks$.value?.find(
|
||||
(track) => track.source === "camera",
|
||||
) as LocalVideoMediaTrack;
|
||||
// Attaching takes its time, as the pipeline is built on the track
|
||||
let attached!: () => void;
|
||||
vi.mocked(camera.setProcessor).mockReturnValueOnce(
|
||||
new Promise<void>((resolve) => {
|
||||
attached = resolve;
|
||||
}),
|
||||
);
|
||||
const turnedOn = cameraHandler(setHandler)(true);
|
||||
effects$.next({ supported: true, processor });
|
||||
await flushPromises();
|
||||
expect(camera.setProcessor).toHaveBeenCalledWith(processor);
|
||||
expect(camera.setEnabled).not.toHaveBeenCalledWith(true);
|
||||
|
||||
attached();
|
||||
await turnedOn;
|
||||
expect(camera.setEnabled).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
// The SDK builds the processor on the camera's restart, before the room
|
||||
// sees a frame, so the track is unmuted rather than published anew
|
||||
it("turns on a camera turned off before the processor was chosen", async () => {
|
||||
|
||||
@@ -533,6 +533,19 @@ export function createCallViewModel$(
|
||||
"screenShare",
|
||||
) as Behavior<LocalVideoMediaTrack | undefined>;
|
||||
|
||||
const processorAttached = followProcessor(
|
||||
scope,
|
||||
camera$,
|
||||
videoProcessor$,
|
||||
logger,
|
||||
);
|
||||
|
||||
// A camera turned on waits for its processor to be known, then to be on
|
||||
// the track: LiveKit restarts the track with whatever processor it has then.
|
||||
const cameraReady = async (): Promise<void> => {
|
||||
await processorKnown();
|
||||
await processorAttached();
|
||||
};
|
||||
const supply = (source: "microphone" | "camera"): TrackSupply => ({
|
||||
publish: async () =>
|
||||
rtcParticipation.publish(
|
||||
@@ -543,7 +556,7 @@ export function createCallViewModel$(
|
||||
source === "camera" ? videoProcessor$.value : undefined,
|
||||
),
|
||||
),
|
||||
ready: source === "camera" ? processorKnown : undefined,
|
||||
ready: source === "camera" ? cameraReady : undefined,
|
||||
});
|
||||
followMuteState(
|
||||
scope,
|
||||
@@ -606,8 +619,6 @@ export function createCallViewModel$(
|
||||
});
|
||||
});
|
||||
|
||||
followProcessor(scope, camera$, videoProcessor$, logger);
|
||||
|
||||
const members$ = scope.behavior<(LocalRTCMember | RemoteRTCMember)[]>(
|
||||
combineLatest(
|
||||
[rtcParticipation.localMember$, rtcParticipation.remoteMembers$],
|
||||
|
||||
@@ -97,18 +97,23 @@ export function followDevice(
|
||||
/**
|
||||
* Attached once the camera track exists rather than carried in the publish
|
||||
* request, so that a new track and a change of processor take the same path.
|
||||
* Answers with a wait for the latest attachment to have gone through: a
|
||||
* camera turned on before then would restart without its processor.
|
||||
*/
|
||||
export function followProcessor(
|
||||
scope: ObservableScope,
|
||||
camera$: Behavior<LocalVideoMediaTrack | undefined>,
|
||||
videoProcessor$: Behavior<VideoProcessor | undefined>,
|
||||
logger: Logger,
|
||||
): void {
|
||||
): () => Promise<void> {
|
||||
let attached = Promise.resolve();
|
||||
combineLatest([camera$, videoProcessor$])
|
||||
.pipe(scope.bind())
|
||||
.subscribe(([camera, processor]) => {
|
||||
camera?.setProcessor(processor).catch((e) => {
|
||||
logger.error("Failed to set the video processor", e);
|
||||
});
|
||||
attached =
|
||||
camera?.setProcessor(processor).catch((e) => {
|
||||
logger.error("Failed to set the video processor", e);
|
||||
}) ?? Promise.resolve();
|
||||
});
|
||||
return async () => attached;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user