mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
[SDK] refactor track processor api to be lk independent
This commit is contained in:
@@ -61,6 +61,8 @@ export {
|
||||
createLivekitBackend,
|
||||
type LivekitBackendOptions,
|
||||
} from "./src/media-backend/livekit/LivekitBackend";
|
||||
// For a host that creates LiveKit tracks itself, such as a preview before joining
|
||||
export { convertToLivekitProcessor } from "./src/media-backend/livekit/videoProcessor";
|
||||
// For a developer panel to check a transport url before using it
|
||||
export {
|
||||
getSFUConfigWithOpenID as authenticateWithTransport,
|
||||
|
||||
+10
-12
@@ -30,23 +30,21 @@ export interface VideoCaptureSettings {
|
||||
codec?: VideoCodec;
|
||||
}
|
||||
|
||||
/** What a processor is given to work on: the captured track and, where a view is attached, its element. */
|
||||
export interface VideoProcessorInit {
|
||||
track: MediaStreamTrack;
|
||||
element?: HTMLMediaElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a camera track before it is published: background blur and the
|
||||
* like. `processedTrack` is what gets published once `init` has resolved;
|
||||
* `restart` is called with the new track where the camera changes. The shape
|
||||
* is that of a LiveKit track processor, so one of those can be passed as is.
|
||||
* like. `init` resolves with the track to publish in place of the captured
|
||||
* one; `restart` does the same for a new capture, where the camera changes.
|
||||
*/
|
||||
export interface VideoProcessor {
|
||||
name: string;
|
||||
processedTrack?: MediaStreamTrack;
|
||||
init(opts: VideoProcessorInit): Promise<void>;
|
||||
restart(opts: VideoProcessorInit): Promise<void>;
|
||||
init(
|
||||
track: MediaStreamTrack,
|
||||
element?: HTMLMediaElement,
|
||||
): Promise<MediaStreamTrack>;
|
||||
restart(
|
||||
track: MediaStreamTrack,
|
||||
element?: HTMLMediaElement,
|
||||
): Promise<MediaStreamTrack>;
|
||||
destroy(): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
} from "../../media-api";
|
||||
import { MatrixRTCError } from "../../errors";
|
||||
import { LazyBehavior } from "../../utils/LazyBehavior";
|
||||
import { convertToLivekitProcessor } from "./videoProcessor";
|
||||
|
||||
export const mediaSources: Record<Track.Source, MediaSource> = {
|
||||
[Track.Source.Microphone]: "microphone",
|
||||
@@ -219,7 +220,8 @@ export function createLocalLivekitMediaTrack(
|
||||
const track = videoTrack();
|
||||
// A processor cannot be built on a track that has already ended
|
||||
if (!track || track.mediaStreamTrack.readyState === "ended") return;
|
||||
if (processor) await track.setProcessor(processor);
|
||||
if (processor)
|
||||
await track.setProcessor(convertToLivekitProcessor(processor));
|
||||
else if (track.getProcessor()) await track.stopProcessor();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type PublishRequest,
|
||||
type VideoCaptureSettings,
|
||||
} from "../../media-api";
|
||||
import { convertToLivekitProcessor } from "./videoProcessor";
|
||||
|
||||
export type PublishRequestFor<S extends PublishRequest["source"]> = Extract<
|
||||
PublishRequest,
|
||||
@@ -39,7 +40,7 @@ export function videoCaptureOptions({
|
||||
if (!deviceId && !processor && !capture?.resolution) return undefined;
|
||||
return {
|
||||
...(deviceId && { deviceId }),
|
||||
...(processor && { processor }),
|
||||
...(processor && { processor: convertToLivekitProcessor(processor) }),
|
||||
...(capture?.resolution && { resolution: capture.resolution }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Track, type VideoProcessorOptions } from "livekit-client";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { type VideoProcessor } from "../../media-api";
|
||||
import { convertToLivekitProcessor } from "./videoProcessor";
|
||||
|
||||
describe("convertToLivekitProcessor", () => {
|
||||
const captured = { id: "captured" } as MediaStreamTrack;
|
||||
const processed = { id: "processed" } as MediaStreamTrack;
|
||||
const restarted = { id: "restarted" } as MediaStreamTrack;
|
||||
const element = document.createElement("video");
|
||||
const options: VideoProcessorOptions = {
|
||||
kind: Track.Kind.Video,
|
||||
track: captured,
|
||||
element,
|
||||
};
|
||||
|
||||
function fakeProcessor(): VideoProcessor {
|
||||
return {
|
||||
name: "blur",
|
||||
init: vi.fn().mockResolvedValue(processed),
|
||||
restart: vi.fn().mockResolvedValue(restarted),
|
||||
destroy: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
}
|
||||
|
||||
it("holds the processed track where LiveKit reads it", async () => {
|
||||
const processor = fakeProcessor();
|
||||
const adapter = convertToLivekitProcessor(processor);
|
||||
expect(adapter.name).toBe("blur");
|
||||
expect(adapter.processedTrack).toBeUndefined();
|
||||
|
||||
await adapter.init(options);
|
||||
expect(processor.init).toHaveBeenCalledWith(captured, element);
|
||||
expect(adapter.processedTrack).toBe(processed);
|
||||
|
||||
await adapter.restart(options);
|
||||
expect(adapter.processedTrack).toBe(restarted);
|
||||
|
||||
await adapter.destroy();
|
||||
expect(processor.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Track, type TrackProcessor } from "livekit-client";
|
||||
|
||||
import { type VideoProcessor } from "../../media-api";
|
||||
|
||||
/**
|
||||
* A `VideoProcessor` as livekit-client wants it. LiveKit reads the processed
|
||||
* track off the processor after `init` and `restart` rather than taking it as
|
||||
* their result, so the adapter keeps it there.
|
||||
*/
|
||||
export function convertToLivekitProcessor(
|
||||
processor: VideoProcessor,
|
||||
): TrackProcessor<Track.Kind.Video> {
|
||||
const adapter: TrackProcessor<Track.Kind.Video> = {
|
||||
name: processor.name,
|
||||
init: async (opts) => {
|
||||
adapter.processedTrack = await processor.init(opts.track, opts.element);
|
||||
},
|
||||
restart: async (opts) => {
|
||||
adapter.processedTrack = await processor.restart(
|
||||
opts.track,
|
||||
opts.element,
|
||||
);
|
||||
},
|
||||
destroy: async () => processor.destroy(),
|
||||
};
|
||||
return adapter;
|
||||
}
|
||||
Reference in New Issue
Block a user