mirror of
https://github.com/vector-im/element-call.git
synced 2026-11-07 00:52:28 +00:00
Turn a camera back on rather than replace it for its processor
The SDK now builds a processor chosen while the camera was off when the camera turns on, so the call view model no longer unpublishes the camera and publishes a new one. TrackSupply.replace, the record of which camera took which processor, and followProcessor's applied callback go with it. Answers robintown's review on #4282 ("what's wrong with attaching processors to already published camera tracks?"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2d9b76ac1b
commit
b04237fada
@@ -1697,7 +1697,9 @@ describe("a camera with a video processor", () => {
|
||||
).toMatchObject({ processor });
|
||||
});
|
||||
|
||||
it("replaces a camera turned off before the processor was chosen", async () => {
|
||||
// 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 () => {
|
||||
const setHandler = vi.spyOn(MuteState.prototype, "setHandler");
|
||||
onTestFinished(() => setHandler.mockRestore());
|
||||
const videoProcessor$ = new BehaviorSubject<VideoProcessor | undefined>(
|
||||
@@ -1711,31 +1713,6 @@ describe("a camera with a video processor", () => {
|
||||
},
|
||||
{ videoProcessor$ },
|
||||
);
|
||||
// The camera is off, so it is stopped and can't take the processor
|
||||
videoProcessor$.next(processor);
|
||||
await cameraHandler(setHandler)(true);
|
||||
await flushPromises();
|
||||
|
||||
expect(participation!.unpublish).toHaveBeenCalledWith("camera");
|
||||
expect(participation!.publish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ source: "camera", processor }),
|
||||
);
|
||||
});
|
||||
|
||||
it("turns on a camera that took the processor while running", async () => {
|
||||
const setHandler = vi.spyOn(MuteState.prototype, "setHandler");
|
||||
onTestFinished(() => setHandler.mockRestore());
|
||||
const videoProcessor$ = new BehaviorSubject<VideoProcessor | undefined>(
|
||||
undefined,
|
||||
);
|
||||
let participation: RTCParticipation | undefined;
|
||||
withCallViewModel(
|
||||
{ videoEnabled: new Map([[localParticipant, constant(true)]]) },
|
||||
(_vm, _session, { rtcParticipation }) => {
|
||||
participation = rtcParticipation;
|
||||
},
|
||||
{ videoProcessor$ },
|
||||
);
|
||||
videoProcessor$.next(processor);
|
||||
await cameraHandler(setHandler)(true);
|
||||
await flushPromises();
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
type EncryptionSystem,
|
||||
generateItems,
|
||||
type LocalAudioMediaTrack,
|
||||
type LocalMediaTrack,
|
||||
type LocalRTCMember,
|
||||
type LocalVideoMediaTrack,
|
||||
type RTCParticipation,
|
||||
@@ -513,16 +512,6 @@ export function createCallViewModel$(
|
||||
"screenShare",
|
||||
) as Behavior<LocalVideoMediaTrack | undefined>;
|
||||
|
||||
// Camera tracks the processor reached while they ran. A camera turned off is
|
||||
// stopped, and a stopped track can't take one.
|
||||
const tookProcessor = new WeakMap<LocalVideoMediaTrack, VideoProcessor>();
|
||||
const missesProcessor = (track: LocalMediaTrack): boolean => {
|
||||
const processor = videoProcessor$.value;
|
||||
return (
|
||||
processor !== undefined &&
|
||||
tookProcessor.get(track as LocalVideoMediaTrack) !== processor
|
||||
);
|
||||
};
|
||||
const supply = (source: "microphone" | "camera"): TrackSupply => ({
|
||||
publish: async () =>
|
||||
rtcParticipation.publish(
|
||||
@@ -533,16 +522,6 @@ export function createCallViewModel$(
|
||||
source === "camera" ? videoProcessor$.value : undefined,
|
||||
),
|
||||
),
|
||||
// A camera turned off before the processor was chosen is replaced, so
|
||||
// that the new one starts with it
|
||||
replace:
|
||||
source === "camera"
|
||||
? async (track): Promise<boolean> => {
|
||||
if (!missesProcessor(track)) return false;
|
||||
await rtcParticipation.unpublish("camera");
|
||||
return true;
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
followMuteState(
|
||||
scope,
|
||||
@@ -582,13 +561,7 @@ export function createCallViewModel$(
|
||||
});
|
||||
});
|
||||
|
||||
followProcessor(
|
||||
scope,
|
||||
camera$,
|
||||
videoProcessor$,
|
||||
logger,
|
||||
(camera, processor) => tookProcessor.set(camera, processor),
|
||||
);
|
||||
followProcessor(scope, camera$, videoProcessor$, logger);
|
||||
|
||||
const members$ = scope.behavior<(LocalRTCMember | RemoteRTCMember)[]>(
|
||||
combineLatest(
|
||||
|
||||
@@ -18,14 +18,9 @@ import { combineLatest } from "rxjs";
|
||||
import { type MediaDevice } from "./MediaDevices";
|
||||
import { type Handler } from "./MuteStates";
|
||||
|
||||
/**
|
||||
* How a mute switch gets a track where there is none and, where given,
|
||||
* whether a track it has should be let go and published anew instead of
|
||||
* unmuted: resolves true once it is gone.
|
||||
*/
|
||||
/** How a mute switch gets a track where there is none. */
|
||||
export interface TrackSupply {
|
||||
publish: () => Promise<unknown>;
|
||||
replace?: (track: LocalMediaTrack) => Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,9 +46,7 @@ export function followMuteState(
|
||||
muteState.setHandler(async (enabled) => {
|
||||
desired = enabled;
|
||||
const track = track$.value;
|
||||
const replaced =
|
||||
enabled && track !== undefined && (await supply.replace?.(track));
|
||||
if (track !== undefined && !replaced) {
|
||||
if (track !== undefined) {
|
||||
desired = await track.setEnabled(enabled);
|
||||
return desired;
|
||||
}
|
||||
@@ -107,15 +100,11 @@ export function followProcessor(
|
||||
camera$: Behavior<LocalVideoMediaTrack | undefined>,
|
||||
videoProcessor$: Behavior<VideoProcessor | undefined>,
|
||||
logger: Logger,
|
||||
/** Told of each camera a processor reaches while it runs. */
|
||||
applied?: (camera: LocalVideoMediaTrack, processor: VideoProcessor) => void,
|
||||
): void {
|
||||
combineLatest([camera$, videoProcessor$])
|
||||
.pipe(scope.bind())
|
||||
.subscribe(([camera, processor]) => {
|
||||
if (camera === undefined) return;
|
||||
if (processor && !camera.muted$.value) applied?.(camera, processor);
|
||||
camera.setProcessor(processor).catch((e) => {
|
||||
camera?.setProcessor(processor).catch((e) => {
|
||||
logger.error("Failed to set the video processor", e);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user