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,
|
createLivekitBackend,
|
||||||
type LivekitBackendOptions,
|
type LivekitBackendOptions,
|
||||||
} from "./src/media-backend/livekit/LivekitBackend";
|
} 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
|
// For a developer panel to check a transport url before using it
|
||||||
export {
|
export {
|
||||||
getSFUConfigWithOpenID as authenticateWithTransport,
|
getSFUConfigWithOpenID as authenticateWithTransport,
|
||||||
|
|||||||
+10
-12
@@ -30,23 +30,21 @@ export interface VideoCaptureSettings {
|
|||||||
codec?: VideoCodec;
|
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
|
* Transforms a camera track before it is published: background blur and the
|
||||||
* like. `processedTrack` is what gets published once `init` has resolved;
|
* like. `init` resolves with the track to publish in place of the captured
|
||||||
* `restart` is called with the new track where the camera changes. The shape
|
* one; `restart` does the same for a new capture, where the camera changes.
|
||||||
* is that of a LiveKit track processor, so one of those can be passed as is.
|
|
||||||
*/
|
*/
|
||||||
export interface VideoProcessor {
|
export interface VideoProcessor {
|
||||||
name: string;
|
name: string;
|
||||||
processedTrack?: MediaStreamTrack;
|
init(
|
||||||
init(opts: VideoProcessorInit): Promise<void>;
|
track: MediaStreamTrack,
|
||||||
restart(opts: VideoProcessorInit): Promise<void>;
|
element?: HTMLMediaElement,
|
||||||
|
): Promise<MediaStreamTrack>;
|
||||||
|
restart(
|
||||||
|
track: MediaStreamTrack,
|
||||||
|
element?: HTMLMediaElement,
|
||||||
|
): Promise<MediaStreamTrack>;
|
||||||
destroy(): Promise<void>;
|
destroy(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import {
|
|||||||
} from "../../media-api";
|
} from "../../media-api";
|
||||||
import { MatrixRTCError } from "../../errors";
|
import { MatrixRTCError } from "../../errors";
|
||||||
import { LazyBehavior } from "../../utils/LazyBehavior";
|
import { LazyBehavior } from "../../utils/LazyBehavior";
|
||||||
|
import { convertToLivekitProcessor } from "./videoProcessor";
|
||||||
|
|
||||||
export const mediaSources: Record<Track.Source, MediaSource> = {
|
export const mediaSources: Record<Track.Source, MediaSource> = {
|
||||||
[Track.Source.Microphone]: "microphone",
|
[Track.Source.Microphone]: "microphone",
|
||||||
@@ -219,7 +220,8 @@ export function createLocalLivekitMediaTrack(
|
|||||||
const track = videoTrack();
|
const track = videoTrack();
|
||||||
// A processor cannot be built on a track that has already ended
|
// A processor cannot be built on a track that has already ended
|
||||||
if (!track || track.mediaStreamTrack.readyState === "ended") return;
|
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();
|
else if (track.getProcessor()) await track.stopProcessor();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
type PublishRequest,
|
type PublishRequest,
|
||||||
type VideoCaptureSettings,
|
type VideoCaptureSettings,
|
||||||
} from "../../media-api";
|
} from "../../media-api";
|
||||||
|
import { convertToLivekitProcessor } from "./videoProcessor";
|
||||||
|
|
||||||
export type PublishRequestFor<S extends PublishRequest["source"]> = Extract<
|
export type PublishRequestFor<S extends PublishRequest["source"]> = Extract<
|
||||||
PublishRequest,
|
PublishRequest,
|
||||||
@@ -39,7 +40,7 @@ export function videoCaptureOptions({
|
|||||||
if (!deviceId && !processor && !capture?.resolution) return undefined;
|
if (!deviceId && !processor && !capture?.resolution) return undefined;
|
||||||
return {
|
return {
|
||||||
...(deviceId && { deviceId }),
|
...(deviceId && { deviceId }),
|
||||||
...(processor && { processor }),
|
...(processor && { processor: convertToLivekitProcessor(processor) }),
|
||||||
...(capture?.resolution && { resolution: capture.resolution }),
|
...(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