Files
element-call-Github/sdk/src/media/LivekitMediaTrack.test.ts
T
2026-10-08 13:53:56 +02:00

161 lines
4.6 KiB
TypeScript

/*
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 { EventEmitter } from "events";
import {
ParticipantEvent,
type RemoteParticipant,
type Room as LivekitRoom,
Track,
TrackEvent,
type TrackPublication,
} from "livekit-client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ObservableScope } from "../../../src/state/ObservableScope";
import { type AudioMediaTrack } from "../api";
import { createLivekitMediaTrack } from "./LivekitMediaTrack";
describe("createLivekitMediaTrack", () => {
let scope: ObservableScope;
beforeEach(() => {
scope = new ObservableScope();
});
afterEach(() => scope.end());
it("describes the publication", () => {
const { participant, publication, room } = fakes();
const track = createLivekitMediaTrack(
scope,
participant,
publication as unknown as TrackPublication,
room,
"camera",
);
expect(track.kind).toBe("video");
expect(track.source).toBe("camera");
expect(track.id).toBe("TR_1");
expect(track.encrypted$.value).toBe(true);
});
it("attaches elements to the track, even one that arrives later", () => {
const { participant, publication, room, emitter } = fakes({
track: undefined,
});
const element = document.createElement("video");
const track = createLivekitMediaTrack(
scope,
participant,
publication as unknown as TrackPublication,
room,
"camera",
);
track.attach(element);
track.attach(element);
const livekitTrack = fakeTrack();
publication.track = livekitTrack;
emitter.emit(TrackEvent.Subscribed, livekitTrack);
expect(livekitTrack.attach).toHaveBeenCalledTimes(1);
expect(livekitTrack.attach).toHaveBeenCalledWith(element);
track.detach(element);
expect(livekitTrack.detach).toHaveBeenCalledWith(element);
});
it("detaches everything when its scope ends", () => {
const { participant, publication, room } = fakes();
const element = document.createElement("video");
const scope = new ObservableScope();
const track = createLivekitMediaTrack(
scope,
participant,
publication as unknown as TrackPublication,
room,
"camera",
);
track.attach(element);
scope.end();
expect(publication.track!.detach).toHaveBeenCalledWith(element);
});
it("follows the mute state", () => {
const { participant, publication, room, emitter } = fakes();
const track = createLivekitMediaTrack(
scope,
participant,
publication as unknown as TrackPublication,
room,
"camera",
);
expect(track.muted$.value).toBe(false);
publication.isMuted = true;
emitter.emit(ParticipantEvent.TrackMuted, publication);
expect(track.muted$.value).toBe(true);
});
it("scales a remote member's volume on the participant", () => {
const { participant, publication, room } = fakes({
kind: Track.Kind.Audio,
});
const track = createLivekitMediaTrack(
scope,
participant,
publication as unknown as TrackPublication,
room,
"microphone",
) as AudioMediaTrack;
track.setVolume(0.5);
expect(participant.setVolume).toHaveBeenCalledWith(
0.5,
Track.Source.Microphone,
);
});
});
type FakeTrack = Track & {
attach: ReturnType<typeof vi.fn>;
detach: ReturnType<typeof vi.fn>;
};
type FakePublication = Omit<TrackPublication, "track" | "isMuted"> & {
track: FakeTrack | undefined;
isMuted: boolean;
};
function fakeTrack(): FakeTrack {
return { attach: vi.fn(), detach: vi.fn() } as unknown as FakeTrack;
}
function fakes({
track = fakeTrack(),
kind = Track.Kind.Video,
}: { track?: FakeTrack | undefined; kind?: Track.Kind } = {}): {
participant: RemoteParticipant & { setVolume: ReturnType<typeof vi.fn> };
publication: FakePublication;
room: LivekitRoom;
emitter: EventEmitter;
} {
const emitter = new EventEmitter();
const participant = Object.assign(emitter, {
isLocal: false,
identity: "@alice:example.org:DEVICE",
setVolume: vi.fn(),
getTrackPublication: (): TrackPublication =>
publication as unknown as TrackPublication,
}) as unknown as RemoteParticipant & { setVolume: ReturnType<typeof vi.fn> };
const publication = Object.assign(emitter, {
kind,
trackSid: "TR_1",
isMuted: false,
isEncrypted: true,
track,
}) as unknown as FakePublication;
const room = new EventEmitter() as unknown as LivekitRoom;
return { participant, publication, room, emitter };
}