[SDK] refactor track processor api to be lk independent

This commit is contained in:
Timo K.
2026-10-08 16:36:17 +02:00
parent aa897ae9bc
commit 5f914b8a01
6 changed files with 101 additions and 14 deletions
@@ -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;
}