From 2518d69b6dae6e5683282c4f3f006ca1164dce28 Mon Sep 17 00:00:00 2001 From: Kamil Stasiak Date: Wed, 30 Oct 2024 16:16:57 +0100 Subject: [PATCH 1/4] FCE-756 Fix unit tests and remove metadata validators --- .github/workflows/node.yaml | 3 + .../react-client/docker-compose-test.yaml | 2 +- e2e-tests/ts-client/app/playwright.config.ts | 2 +- e2e-tests/ts-client/app/src/App.tsx | 104 ++--------- e2e-tests/ts-client/app/src/MockComponent.tsx | 3 +- e2e-tests/ts-client/app/src/MuteTrackTest.tsx | 15 +- e2e-tests/ts-client/docker-compose-test.yaml | 2 +- .../scenarios/metadataParsing.spec.ts | 171 ------------------ examples/ts-client/simple-app/src/main.ts | 51 +++--- package.json | 1 + .../src/hooks/useFishjamClientState.ts | 6 +- packages/react-client/src/hooks/usePeers.ts | 16 +- .../react-client/src/hooks/useScreenShare.ts | 1 + .../react-client/src/hooks/useTrackManager.ts | 1 + packages/react-client/src/hooks/useVAD.ts | 10 +- packages/react-client/src/index.ts | 1 - packages/react-client/src/types/internal.ts | 11 +- packages/react-client/src/utils/track.ts | 24 ++- packages/ts-client/src/FishjamClient.ts | 142 ++++++--------- packages/ts-client/src/index.ts | 1 - packages/ts-client/src/reconnection.ts | 13 +- .../ts-client/src/webrtc/CommandsQueue.ts | 6 +- packages/ts-client/src/webrtc/index.ts | 2 - packages/ts-client/src/webrtc/internal.ts | 39 +--- packages/ts-client/src/webrtc/tracks/Local.ts | 86 +++------ .../ts-client/src/webrtc/tracks/LocalTrack.ts | 41 +---- .../src/webrtc/tracks/LocalTrackManager.ts | 10 +- .../ts-client/src/webrtc/tracks/Remote.ts | 107 ++++------- .../src/webrtc/tracks/RemoteTrack.ts | 6 +- .../src/webrtc/tracks/muteTrackUtils.ts | 6 +- .../src/webrtc/tracks/transceivers.ts | 8 +- packages/ts-client/src/webrtc/types.ts | 71 +++----- .../ts-client/src/webrtc/webRTCEndpoint.ts | 66 ++----- .../tests/events/connectedEvent.test.ts | 6 +- .../events/encodingSwitchedEvent.test.ts | 4 +- .../tests/events/endpointAddedEvent.test.ts | 44 +---- .../tests/events/endpointRemovedEvent.test.ts | 8 +- .../tests/events/endpointUpdatedEvent.test.ts | 54 +----- .../tests/events/trackAddedEvent.test.ts | 47 +---- .../tests/events/trackUpdatedEvent.test.ts | 41 +---- packages/ts-client/tests/fixtures.ts | 6 +- .../tests/methods/addTrackMethod.test.ts | 22 --- .../tests/methods/connectMethod.test.ts | 15 -- packages/ts-client/tests/mocks.ts | 8 +- packages/ts-client/tests/schema.ts | 10 +- packages/ts-client/tests/utils.ts | 6 +- 46 files changed, 336 insertions(+), 963 deletions(-) delete mode 100644 e2e-tests/ts-client/scenarios/metadataParsing.spec.ts diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 7c9118ac..9f2ec93d 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -31,6 +31,9 @@ jobs: - name: Build 📦 run: yarn build + - name: Test 🧑‍🔬 + run: yarn test:unit + - name: Check formatting 🎨 run: yarn format:check diff --git a/e2e-tests/react-client/docker-compose-test.yaml b/e2e-tests/react-client/docker-compose-test.yaml index 685734d7..a6d98b16 100644 --- a/e2e-tests/react-client/docker-compose-test.yaml +++ b/e2e-tests/react-client/docker-compose-test.yaml @@ -2,7 +2,7 @@ version: "3" services: fishjam: - image: "ghcr.io/fishjam-cloud/fishjam:0.8.0" + image: "ghcr.io/fishjam-cloud/fishjam:0.9.0" container_name: fishjam restart: on-failure healthcheck: diff --git a/e2e-tests/ts-client/app/playwright.config.ts b/e2e-tests/ts-client/app/playwright.config.ts index 70533a27..6710ec3f 100644 --- a/e2e-tests/ts-client/app/playwright.config.ts +++ b/e2e-tests/ts-client/app/playwright.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ ["list"], diff --git a/e2e-tests/ts-client/app/src/App.tsx b/e2e-tests/ts-client/app/src/App.tsx index b1e0528f..81c62075 100644 --- a/e2e-tests/ts-client/app/src/App.tsx +++ b/e2e-tests/ts-client/app/src/App.tsx @@ -24,41 +24,14 @@ export type TrackMetadata = { goodTrack: string; }; -function endpointMetadataParser(a: unknown): EndpointMetadata { - if ( - typeof a !== "object" || - a === null || - !("goodStuff" in a) || - typeof a.goodStuff !== "string" - ) - throw "Invalid metadata!!!"; - return { goodStuff: a.goodStuff }; -} - -function trackMetadataParser(a: unknown): TrackMetadata { - if ( - typeof a !== "object" || - a === null || - !("goodTrack" in a) || - typeof a.goodTrack !== "string" - ) - throw "Invalid track metadata!!!"; - return { goodTrack: a.goodTrack }; -} - class RemoteStore { cache: Record< string, - [ - Record>, - Record>, - ] + [Record, Record] > = {}; invalidateCache: boolean = false; - constructor( - private webrtc: WebRTCEndpoint, - ) {} + constructor(private webrtc: WebRTCEndpoint) {} subscribe(callback: () => void) { const cb = () => { @@ -66,25 +39,16 @@ class RemoteStore { callback(); }; - const trackCb: TrackContextEvents< - EndpointMetadata, - TrackMetadata - >["encodingChanged"] = () => cb(); + const trackCb: TrackContextEvents["encodingChanged"] = () => cb(); - const trackAddedCb: WebRTCEndpointEvents< - EndpointMetadata, - TrackMetadata - >["trackAdded"] = (context) => { + const trackAddedCb: WebRTCEndpointEvents["trackAdded"] = (context) => { context.on("encodingChanged", () => trackCb); context.on("voiceActivityChanged", () => trackCb); callback(); }; - const removeCb: WebRTCEndpointEvents< - EndpointMetadata, - TrackMetadata - >["trackRemoved"] = (context) => { + const removeCb: WebRTCEndpointEvents["trackRemoved"] = (context) => { context.removeListener("encodingChanged", () => trackCb); context.removeListener("voiceActivityChanged", () => trackCb); @@ -111,15 +75,20 @@ class RemoteStore { } snapshot() { + console.log("Snapshot"); + const newTracks = webrtc.getRemoteTracks(); const newEndpoints = webrtc.getRemoteEndpoints(); const ids = Object.keys(newTracks).sort().join(":") + Object.keys(newEndpoints).sort().join(":"); if (!(ids in this.cache) || this.invalidateCache) { + console.log({ name: "Update cache", newEndpoints, newTracks }); this.cache[ids] = [newEndpoints, newTracks]; this.invalidateCache = false; } + console.log("Use cache"); + return this.cache[ids]; } } @@ -127,10 +96,7 @@ class RemoteStore { // Assign a random client ID to make it easier to distinguish their messages const clientId = Math.floor(Math.random() * 100); -const webrtc = new WebRTCEndpoint({ - endpointMetadataParser, - trackMetadataParser, -}); +const webrtc = new WebRTCEndpoint(); (window as typeof window & { webrtc: WebRTCEndpoint }).webrtc = webrtc; const remoteTracksStore = new RemoteStore(webrtc); @@ -289,14 +255,7 @@ export function App() {
{Object.values(remoteTracks).map( - ({ - stream, - trackId, - endpoint, - metadata, - rawMetadata, - metadataParsingError, - }) => ( + ({ stream, trackId, endpoint, metadata }) => (
Endpoint id: {endpoint.id}
Metadata:{" "} {JSON.stringify(metadata)} -
- Raw:{" "} - - {JSON.stringify(rawMetadata)} - -
- Error:{" "} - - {metadataParsingError} -
Endpoints: - {Object.values(remoteEndpoints).map( - ({ id, metadata, rawMetadata, metadataParsingError }) => ( -
- {id} - metadata:{" "} - - {JSON.stringify(metadata.peer)} - -
- raw metadata:{" "} - - {JSON.stringify(rawMetadata.peer)} - -
- metadata parsing error:{" "} - - {metadataParsingError?.toString?.() ?? metadataParsingError} - -
- ), - )} + {Object.values(remoteEndpoints).map(({ id, metadata }) => ( +
+ {id} + metadata:{" "} + {JSON.stringify(metadata)} +
+
+ ))}
diff --git a/e2e-tests/ts-client/app/src/MockComponent.tsx b/e2e-tests/ts-client/app/src/MockComponent.tsx index 18f11869..e1db7938 100644 --- a/e2e-tests/ts-client/app/src/MockComponent.tsx +++ b/e2e-tests/ts-client/app/src/MockComponent.tsx @@ -1,7 +1,6 @@ import { createStream } from "./mocks"; import { VideoPlayer } from "./VideoPlayer"; import { useRef, useState } from "react"; -import type { EndpointMetadata, TrackMetadata } from "./App"; import type { BandwidthLimit, SimulcastConfig, @@ -22,7 +21,7 @@ export const heartMock = createStream("🫀", "white", "low", 24); export const heart2Mock = createStream("💝", "#FF0000", "low", 24); type Props = { - webrtc: WebRTCEndpoint; + webrtc: WebRTCEndpoint; }; export const MockComponent = ({ webrtc }: Props) => { diff --git a/e2e-tests/ts-client/app/src/MuteTrackTest.tsx b/e2e-tests/ts-client/app/src/MuteTrackTest.tsx index 58550afc..94e3231c 100644 --- a/e2e-tests/ts-client/app/src/MuteTrackTest.tsx +++ b/e2e-tests/ts-client/app/src/MuteTrackTest.tsx @@ -3,7 +3,6 @@ import { brain2Mock, heart2Mock } from "./MockComponent"; import { useEffect, useState } from "react"; import { VideoPlayer } from "./VideoPlayer"; import type { WebRTCEndpointEvents } from "@fishjam-cloud/ts-client/webrtc"; -import type { EndpointMetadata, TrackMetadata } from "./App"; type Props = { webrtc: WebRTCEndpoint; @@ -15,19 +14,17 @@ export const MuteTrackTest = ({ webrtc }: Props) => { const [trackId, setTrackId] = useState(null); useEffect(() => { - const localTrackAdded: WebRTCEndpointEvents< - EndpointMetadata, - TrackMetadata - >["localTrackAdded"] = (event) => { + const localTrackAdded: WebRTCEndpointEvents["localTrackAdded"] = ( + event, + ) => { setCurrentStream(event.stream); setCurrentTrack(event.track); setTrackId(event.trackId); }; - const localTrackReplaced: WebRTCEndpointEvents< - EndpointMetadata, - TrackMetadata - >["localTrackReplaced"] = (event) => { + const localTrackReplaced: WebRTCEndpointEvents["localTrackReplaced"] = ( + event, + ) => { setCurrentTrack(event.track); }; diff --git a/e2e-tests/ts-client/docker-compose-test.yaml b/e2e-tests/ts-client/docker-compose-test.yaml index f084a81d..6583dc8b 100644 --- a/e2e-tests/ts-client/docker-compose-test.yaml +++ b/e2e-tests/ts-client/docker-compose-test.yaml @@ -2,7 +2,7 @@ version: '3' services: fishjam: - image: "ghcr.io/fishjam-cloud/fishjam:0.8.0" + image: "ghcr.io/fishjam-cloud/fishjam:0.9.0" container_name: fishjam restart: on-failure healthcheck: diff --git a/e2e-tests/ts-client/scenarios/metadataParsing.spec.ts b/e2e-tests/ts-client/scenarios/metadataParsing.spec.ts deleted file mode 100644 index 3ffa0d20..00000000 --- a/e2e-tests/ts-client/scenarios/metadataParsing.spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -import type { Locator } from '@playwright/test'; -import { test, expect } from '@playwright/test'; -import { createRoom, joinRoom } from './utils'; - -test('Endpoint metadata gets correctly parsed', async ({ - page: firstPage, - context, -}) => { - const secondPage = await context.newPage(); - await firstPage.goto('/'); - await secondPage.goto('/'); - - const roomId = await createRoom(firstPage); - - const firstClientId = await joinRoom(firstPage, roomId, { - goodStuff: 'ye', - extraFluff: 'nah', - }); - await joinRoom(secondPage, roomId, { goodStuff: 'ye' }); - - await hasEqualObject(secondPage.locator(`#metadata-${firstClientId}`), { - goodStuff: 'ye', - }); - await hasEqualObject(secondPage.locator(`#raw-metadata-${firstClientId}`), { - goodStuff: 'ye', - extraFluff: 'nah', - }); - await expect( - secondPage.locator(`#metadata-parsing-error-${firstClientId}`), - ).toBeEmpty(); -}); - -test("Invalid metadata doesn't get pushed to the server on connect", async ({ - page: firstPage, - context, -}) => { - const secondPage = await context.newPage(); - await firstPage.goto('/'); - await secondPage.goto('/'); - - const roomId = await createRoom(firstPage); - - await joinRoom(firstPage, roomId, { goodStuff: 'ye' }); - await joinRoom(secondPage, roomId, { notSoGoodStuff: ';/' }, false); - - await expect(secondPage.locator('#connection-status')).toHaveText('false'); - await firstPage.waitForTimeout(500); - await expect(firstPage.locator('#endpoints-container > details')).toHaveCount( - 0, - ); -}); - -test("Invalid metadata doesn't get pushed to the server on update", async ({ - page: firstPage, - context, -}) => { - const secondPage = await context.newPage(); - await firstPage.goto('/'); - await secondPage.goto('/'); - - const roomId = await createRoom(firstPage); - - await joinRoom(firstPage, roomId, { goodStuff: 'ye' }); - const secondClientId = await joinRoom(secondPage, roomId, { - goodStuff: 'ye', - }); - - await expect(firstPage.locator('#endpoints-container > details')).toHaveCount( - 1, - ); - await secondPage - .getByPlaceholder('endpoint metadata') - .fill(JSON.stringify({ notSoGoodStuff: ';/' })); - await secondPage.getByText('Update metadata', { exact: true }).click(); - await firstPage.waitForTimeout(500); - await hasEqualObject(firstPage.locator(`#metadata-${secondClientId}`), { - goodStuff: 'ye', - }); - await hasEqualObject(firstPage.locator(`#raw-metadata-${secondClientId}`), { - goodStuff: 'ye', - }); - await expect( - firstPage.locator(`#metadata-parsing-error-${secondClientId}`), - ).toBeEmpty(); -}); - -test('Track metadata gets correctly parsed', async ({ - page: firstPage, - context, -}) => { - const secondPage = await context.newPage(); - await firstPage.goto('/'); - await secondPage.goto('/'); - - const roomId = await createRoom(firstPage); - - await joinRoom(firstPage, roomId, { goodStuff: 'ye' }); - await joinRoom(secondPage, roomId, { goodStuff: 'ye' }); - await secondPage - .getByPlaceholder('track metadata') - .fill(JSON.stringify({ goodTrack: 'ye' })); - await secondPage.getByText('Add a heart').click(); - await hasEqualObject(firstPage.locator(`[data-endpoint-id] > .metadata`), { - goodTrack: 'ye', - }); - await hasEqualObject( - firstPage.locator(`[data-endpoint-id] > .raw-metadata`), - { goodTrack: 'ye' }, - ); - await expect( - firstPage.locator(`[data-endpoint-id] > .metadata-parsing-error`), - ).toBeEmpty(); -}); - -test("Invalid track metadata doesn't get pushed to the server when adding track", async ({ - page: firstPage, - context, -}) => { - const secondPage = await context.newPage(); - await firstPage.goto('/'); - await secondPage.goto('/'); - - const roomId = await createRoom(firstPage); - - await joinRoom(firstPage, roomId, { goodStuff: 'ye' }); - await joinRoom(secondPage, roomId, { goodStuff: 'ye' }); - await secondPage - .getByPlaceholder('track metadata') - .fill(JSON.stringify({ notSoGoodStuff: ';/' })); - await secondPage.getByText('Add a heart').click(); - await firstPage.waitForTimeout(500); - await expect(firstPage.locator('[data-endpoint-id]')).not.toBeVisible(); -}); - -test("Invalid track metadata doesn't get pushed to the server when updating track metadata", async ({ - page: firstPage, - context, -}) => { - const secondPage = await context.newPage(); - await firstPage.goto('/'); - await secondPage.goto('/'); - - const roomId = await createRoom(firstPage); - - await joinRoom(firstPage, roomId, { goodStuff: 'ye' }); - await joinRoom(secondPage, roomId, { goodStuff: 'ye' }); - await secondPage.getByText('Add a heart').click(); - await expect(firstPage.locator('[data-endpoint-id]')).toBeVisible(); - await secondPage - .getByPlaceholder('track metadata') - .fill(JSON.stringify({ notSoGoodStuff: ';/' })); - await secondPage.getByText('Update metadata on heart track').click(); - await firstPage.waitForTimeout(500); - await hasEqualObject(firstPage.locator(`[data-endpoint-id] > .metadata`), { - goodTrack: 'ye', - }); - await hasEqualObject( - firstPage.locator(`[data-endpoint-id] > .raw-metadata`), - { goodTrack: 'ye' }, - ); - await expect( - firstPage.locator(`[data-endpoint-id] > .metadata-parsing-error`), - ).toBeEmpty(); -}); - -async function hasEqualObject(locator: Locator, expected: any) { - await expect(async () => { - const text = await locator.textContent(); - expect(text && JSON.parse(text)).toStrictEqual(expected); - }).toPass(); -} diff --git a/examples/ts-client/simple-app/src/main.ts b/examples/ts-client/simple-app/src/main.ts index 9871a136..132e7e2e 100644 --- a/examples/ts-client/simple-app/src/main.ts +++ b/examples/ts-client/simple-app/src/main.ts @@ -157,37 +157,34 @@ client.on("trackAdded", (ctx) => { console.log({ name: "trackAdded", ctx }); }); -client.on( - "joined", - (_peerId: string, peersInRoom: Peer[]) => { - console.log("Join success!"); - toastSuccess(`Joined room`); - const template = document.querySelector("#remote-peer-template-card")!; - const remotePeers = document.querySelector("#remote-peers")!; - - (peersInRoom || []).forEach((peer: Peer) => { - // @ts-ignore - const clone = template.content.cloneNode(true); - const card = clone.firstElementChild; - card.dataset.peerId = peer.id; - - const peerId = clone.querySelector(".remote-peer-template-id"); - peerId.innerHTML = peer.id; - - clone.firstElementChild.dataset.peerId = peer.id; - - document.querySelector(`div[data-peer-id="${peer.id}"`)?.remove(); - remotePeers.appendChild(clone); - }); - }, -); +client.on("joined", (_peerId: string, peersInRoom: Peer[]) => { + console.log("Join success!"); + toastSuccess(`Joined room`); + const template = document.querySelector("#remote-peer-template-card")!; + const remotePeers = document.querySelector("#remote-peers")!; + + (peersInRoom || []).forEach((peer: Peer) => { + // @ts-ignore + const clone = template.content.cloneNode(true); + const card = clone.firstElementChild; + card.dataset.peerId = peer.id; + + const peerId = clone.querySelector(".remote-peer-template-id"); + peerId.innerHTML = peer.id; + + clone.firstElementChild.dataset.peerId = peer.id; + + document.querySelector(`div[data-peer-id="${peer.id}"`)?.remove(); + remotePeers.appendChild(clone); + }); +}); client.on("joinError", (metadata) => { console.log({ name: "joinError", metadata }); toastAlert("Join error"); }); -client.on("peerJoined", (peer: Peer) => { +client.on("peerJoined", (peer: Peer) => { console.log("Peer join success!"); const template = document.querySelector("#remote-peer-template-card")!; @@ -297,7 +294,7 @@ client.on("trackReady", (ctx) => { const rawMetadata = videoWrapper.querySelector(".remote-track-raw-metadata"); if (!rawMetadata) throw new Error("Raw metadata component not found"); - rawMetadata.innerHTML = JSON.stringify(ctx.rawMetadata, undefined, 2); + rawMetadata.innerHTML = JSON.stringify(ctx.metadata, undefined, 2); const parsedMetadata = videoWrapper.querySelector( ".remote-track-parsed-metadata", @@ -321,7 +318,7 @@ client.on("trackUpdated", (ctx) => { const rawMetadata = videoWrapper.querySelector(".remote-track-raw-metadata"); if (!rawMetadata) throw new Error("Raw metadata component not found"); - rawMetadata.innerHTML = JSON.stringify(ctx.rawMetadata, undefined, 2); + rawMetadata.innerHTML = JSON.stringify(ctx.metadata, undefined, 2); const parsedMetadata = videoWrapper.querySelector( ".remote-track-parsed-metadata", diff --git a/package.json b/package.json index 7021b40f..114d5f64 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "packageManager": "yarn@4.4.0", "scripts": { "build": "yarn workspaces foreach -Apt run build", + "test:unit": "yarn workspace @fishjam-cloud/ts-client test", "test:e2e": "yarn workspace @fishjam-e2e/ts-client-e2e e2e && yarn workspace @fishjam-e2e/react-client-e2e e2e", "tsc": "yarn workspaces foreach -Ap run tsc || echo '❌ Type errors! ❌' ", "format": "yarn workspaces foreach -A -p run format || echo '❌ Formatting issues! ❌'", diff --git a/packages/react-client/src/hooks/useFishjamClientState.ts b/packages/react-client/src/hooks/useFishjamClientState.ts index e7a526fb..93aa11f8 100644 --- a/packages/react-client/src/hooks/useFishjamClientState.ts +++ b/packages/react-client/src/hooks/useFishjamClientState.ts @@ -43,9 +43,9 @@ const eventNames = [ ] as const satisfies (keyof MessageEvents)[]; export interface FishjamClientState { - peers: Record>; - components: Record>; - localPeer: Endpoint | null; + peers: Record; + components: Record; + localPeer: Endpoint | null; isReconnecting: boolean; } diff --git a/packages/react-client/src/hooks/usePeers.ts b/packages/react-client/src/hooks/usePeers.ts index d62953c0..01b594bc 100644 --- a/packages/react-client/src/hooks/usePeers.ts +++ b/packages/react-client/src/hooks/usePeers.ts @@ -1,5 +1,5 @@ import type { Component, Endpoint, Peer, TrackContext } from "@fishjam-cloud/ts-client"; -import type { PeerMetadata, PeerState, TrackId, TrackMetadata } from "../types/internal"; +import type { FishjamPeerMetadata, PeerState, TrackId, TrackMetadata } from "../types/internal"; import type { PeerWithTracks, Track } from "../types/public"; import { useFishjamContext } from "./useFishjamContext"; @@ -14,9 +14,9 @@ function getPeerWithDistinguishedTracks(peerState: PeerState): PeerWithTracks { return { ...peerState, cameraTrack, microphoneTrack, screenShareVideoTrack, screenShareAudioTrack }; } -function trackContextToTrack(track: TrackContext): Track { +function trackContextToTrack(track: TrackContext): Track { return { - metadata: track.metadata, + metadata: track.metadata as TrackMetadata, trackId: track.trackId, stream: track.stream, simulcastConfig: track.simulcastConfig ?? null, @@ -26,18 +26,14 @@ function trackContextToTrack(track: TrackContext): }; } -function endpointToPeerState( - peer: - | Peer - | Component - | Endpoint, -): PeerState { +function endpointToPeerState(peer: Peer | Component | Endpoint): PeerState { const tracks = [...peer.tracks].reduce( (acc, [, track]) => ({ ...acc, [track.trackId]: trackContextToTrack(track) }), {} as Record, ); + return { - metadata: peer.metadata, + metadata: peer.metadata as FishjamPeerMetadata, id: peer.id, tracks, }; diff --git a/packages/react-client/src/hooks/useScreenShare.ts b/packages/react-client/src/hooks/useScreenShare.ts index 6e14a402..32b13d1f 100644 --- a/packages/react-client/src/hooks/useScreenShare.ts +++ b/packages/react-client/src/hooks/useScreenShare.ts @@ -25,6 +25,7 @@ export const useScreenShareManager = ({ const stream = state.stream ?? null; const [videoTrack, audioTrack] = stream ? getTracksFromStream(stream) : [null, null]; + // @ts-ignore const getDisplayName = () => fishjamClient.getLocalPeer()?.metadata?.peer?.displayName; const startStreaming: ScreenshareApi["startStreaming"] = async (props) => { diff --git a/packages/react-client/src/hooks/useTrackManager.ts b/packages/react-client/src/hooks/useTrackManager.ts index 6acc32c4..bcc26235 100644 --- a/packages/react-client/src/hooks/useTrackManager.ts +++ b/packages/react-client/src/hooks/useTrackManager.ts @@ -63,6 +63,7 @@ export const useTrackManager = ({ // see `getRemoteOrLocalTrackContext()` explanation setCurrentTrackId(media.track.id); + // @ts-ignore const displayName = tsClient.getLocalPeer()?.metadata?.peer?.displayName; const deviceType = getDeviceType(mediaManager); diff --git a/packages/react-client/src/hooks/useVAD.ts b/packages/react-client/src/hooks/useVAD.ts index 1b9e6bb6..b965b462 100644 --- a/packages/react-client/src/hooks/useVAD.ts +++ b/packages/react-client/src/hooks/useVAD.ts @@ -1,9 +1,13 @@ import { useEffect, useMemo, useState } from "react"; -import type { PeerId, PeerMetadata, TrackId, TrackMetadata } from "../types/internal"; +import type { PeerId, TrackId } from "../types/internal"; import { useFishjamContext } from "./useFishjamContext"; import type { TrackContext, VadStatus } from "@fishjam-cloud/ts-client"; import { useFishjamClientState } from "./useFishjamClientState"; +function isMicrophone(metadata: unknown) { + return typeof metadata === "object" && metadata !== null && "type" in metadata && metadata.type === "microphone"; +} + export const useVAD = (peerIds: PeerId[]): Record => { const { fishjamClientRef } = useFishjamContext(); const { peers } = useFishjamClientState(fishjamClientRef.current); @@ -14,7 +18,7 @@ export const useVAD = (peerIds: PeerId[]): Record => { .filter((peer) => peerIds.includes(peer.id)) .map((peer) => ({ peerId: peer.id, - microphoneTracks: Array.from(peer.tracks.values()).filter((track) => track.metadata?.type === "microphone"), + microphoneTracks: Array.from(peer.tracks.values()).filter(({ metadata }) => isMicrophone(metadata)), })), [peers, peerIds], ); @@ -32,7 +36,7 @@ export const useVAD = (peerIds: PeerId[]): Record => { useEffect(() => { const unsubs = micTracksWithSelectedPeerIds.map(({ peerId, microphoneTracks }) => { - const updateVadStatus = (track: TrackContext) => { + const updateVadStatus = (track: TrackContext) => { setVadStatuses((prev) => ({ ...prev, [peerId]: { ...prev[peerId], [track.trackId]: track.vadStatus }, diff --git a/packages/react-client/src/index.ts b/packages/react-client/src/index.ts index af3e399d..1af28555 100644 --- a/packages/react-client/src/index.ts +++ b/packages/react-client/src/index.ts @@ -41,7 +41,6 @@ export type { TrackContext, VadStatus, EncodingReason, - MetadataParser, AuthErrorReason, Encoding, } from "@fishjam-cloud/ts-client"; diff --git a/packages/react-client/src/types/internal.ts b/packages/react-client/src/types/internal.ts index 698fc6eb..bf0e364d 100644 --- a/packages/react-client/src/types/internal.ts +++ b/packages/react-client/src/types/internal.ts @@ -6,14 +6,15 @@ export type PeerId = string; export type PeerState = { id: PeerId; - metadata: { - peer?: PeerMetadata; - server: Record; - }; + metadata?: FishjamPeerMetadata; tracks: Record; }; -// todo change to Inner / Hidden metadata +export type FishjamPeerMetadata = { + peer: PeerMetadata; + server?: Record; +}; + export type PeerMetadata = { displayName?: string; }; diff --git a/packages/react-client/src/utils/track.ts b/packages/react-client/src/utils/track.ts index 7300d4cc..781109a3 100644 --- a/packages/react-client/src/utils/track.ts +++ b/packages/react-client/src/utils/track.ts @@ -13,7 +13,7 @@ import type { BandwidthLimits, Track } from "../types/public"; const getRemoteOrLocalTrackContext = ( tsClient: FishjamClient, remoteOrLocalTrackId: string | null, -): TrackContext | null => { +): TrackContext | null => { if (!remoteOrLocalTrackId) return null; const tracks = tsClient?.getLocalPeer()?.tracks; @@ -26,15 +26,19 @@ const getRemoteOrLocalTrackContext = ( return trackByLocalId ? trackByLocalId : null; }; -const getTrackFromContext = (context: TrackContext): Track => ({ - metadata: context.metadata, - trackId: context.trackId, - stream: context.stream, - simulcastConfig: context.simulcastConfig || null, - encoding: context.encoding || null, - vadStatus: context.vadStatus, - track: context.track, -}); +const getTrackFromContext = (context: TrackContext): Track => { + return { + // todo typescript client should parse this metadata + // @ts-ignore + metadata: context.metadata as TrackMetadata, // todo parse metadata + trackId: context.trackId, + stream: context.stream, + simulcastConfig: context.simulcastConfig || null, + encoding: context.encoding || null, + vadStatus: context.vadStatus, + track: context.track, + }; +}; export const getRemoteOrLocalTrack = ( tsClient: FishjamClient, diff --git a/packages/ts-client/src/FishjamClient.ts b/packages/ts-client/src/FishjamClient.ts index 71902c30..4205a5a5 100644 --- a/packages/ts-client/src/FishjamClient.ts +++ b/packages/ts-client/src/FishjamClient.ts @@ -2,7 +2,6 @@ import type { BandwidthLimit, Encoding, Endpoint, - MetadataParser, SerializedMediaEvent, SimulcastConfig, TrackBandwidthLimit, @@ -21,18 +20,15 @@ import { connectEventsHandler } from './connectEventsHandler'; const STATISTICS_INTERVAL = 10_000; -export type Peer = Endpoint & { type: 'webrtc' }; +export type Peer = Endpoint & { type: 'webrtc' }; -export type Component = Omit, 'type'> & { +export type Component = Omit & { type: 'recording' | 'hls' | 'file' | 'rtsp' | 'sip'; }; -const isPeer = ( - endpoint: Endpoint, -): endpoint is Peer => endpoint.type === 'webrtc' || endpoint.type === 'exwebrtc'; -const isComponent = ( - endpoint: Endpoint, -): endpoint is Component => +const isPeer = (endpoint: Endpoint): endpoint is Peer => endpoint.type === 'webrtc' || endpoint.type === 'exwebrtc'; + +const isComponent = (endpoint: Endpoint): endpoint is Component => endpoint.type === 'recording' || endpoint.type === 'hls' || endpoint.type === 'file' || @@ -44,7 +40,7 @@ const WEBSOCKET_PATH = 'socket/peer/websocket'; /** * Events emitted by the client with their arguments. */ -export interface MessageEvents { +export interface MessageEvents { /** * Emitted when connect method invoked * @@ -93,11 +89,7 @@ export interface MessageEvents { /** * Called when peer was accepted. */ - joined: ( - peerId: string, - peers: Peer[], - components: Component[], - ) => void; + joined: (peerId: string, peers: Peer[], components: Component[]) => void; /** * Called when peer was not accepted @@ -111,55 +103,55 @@ export interface MessageEvents { * This callback is always called after {@link MessageEvents.trackAdded}. * It informs user that data related to the given track arrives and can be played or displayed. */ - trackReady: (ctx: TrackContext) => void; + trackReady: (ctx: TrackContext) => void; /** * Called each time the peer which was already in the room, adds new track. Fields track and stream will be set to null. * These fields will be set to non-null value in {@link MessageEvents.trackReady} */ - trackAdded: (ctx: TrackContext) => void; + trackAdded: (ctx: TrackContext) => void; /** * Called when some track will no longer be sent. * * It will also be called before {@link MessageEvents.peerLeft} for each track of this peer. */ - trackRemoved: (ctx: TrackContext) => void; + trackRemoved: (ctx: TrackContext) => void; /** * Called each time peer has its track metadata updated. */ - trackUpdated: (ctx: TrackContext) => void; + trackUpdated: (ctx: TrackContext) => void; /** * Called each time new peer joins the room. */ - peerJoined: (peer: Peer) => void; + peerJoined: (peer: Peer) => void; /** * Called each time peer leaves the room. */ - peerLeft: (peer: Peer) => void; + peerLeft: (peer: Peer) => void; /** * Called each time peer has its metadata updated. */ - peerUpdated: (peer: Peer) => void; + peerUpdated: (peer: Peer) => void; /** * Called each time new peer joins the room. */ - componentAdded: (peer: Component) => void; + componentAdded: (peer: Component) => void; /** * Called each time peer leaves the room. */ - componentRemoved: (peer: Component) => void; + componentRemoved: (peer: Component) => void; /** * Called each time peer has its metadata updated. */ - componentUpdated: (peer: Component) => void; + componentUpdated: (peer: Component) => void; /** * Called in case of errors related to multimedia session e.g. ICE connection. @@ -174,10 +166,7 @@ export interface MessageEvents { * @param enabledTracks - list of tracks which will be sent to client from SFU * @param disabledTracks - list of tracks which will not be sent to client from SFU */ - tracksPriorityChanged: ( - enabledTracks: TrackContext[], - disabledTracks: TrackContext[], - ) => void; + tracksPriorityChanged: (enabledTracks: TrackContext[], disabledTracks: TrackContext[]) => void; /** * Called every time the server estimates client's bandiwdth. @@ -187,41 +176,21 @@ export interface MessageEvents { */ bandwidthEstimationChanged: (estimation: bigint) => void; - targetTrackEncodingRequested: ( - event: Parameters['targetTrackEncodingRequested']>[0], - ) => void; - localTrackAdded: (event: Parameters['localTrackAdded']>[0]) => void; - localTrackRemoved: ( - event: Parameters['localTrackRemoved']>[0], - ) => void; - localTrackReplaced: ( - event: Parameters['localTrackReplaced']>[0], - ) => void; - localTrackMuted: (event: Parameters['localTrackMuted']>[0]) => void; - localTrackUnmuted: ( - event: Parameters['localTrackUnmuted']>[0], - ) => void; - localTrackBandwidthSet: ( - event: Parameters['localTrackBandwidthSet']>[0], - ) => void; + targetTrackEncodingRequested: (event: Parameters[0]) => void; + localTrackAdded: (event: Parameters[0]) => void; + localTrackRemoved: (event: Parameters[0]) => void; + localTrackReplaced: (event: Parameters[0]) => void; + localTrackMuted: (event: Parameters[0]) => void; + localTrackUnmuted: (event: Parameters[0]) => void; + localTrackBandwidthSet: (event: Parameters[0]) => void; localTrackEncodingBandwidthSet: ( - event: Parameters['localTrackEncodingBandwidthSet']>[0], - ) => void; - localTrackEncodingEnabled: ( - event: Parameters['localTrackEncodingEnabled']>[0], - ) => void; - localTrackEncodingDisabled: ( - event: Parameters['localTrackEncodingDisabled']>[0], - ) => void; - localPeerMetadataChanged: ( - event: Parameters['localEndpointMetadataChanged']>[0], - ) => void; - localTrackMetadataChanged: ( - event: Parameters['localTrackMetadataChanged']>[0], - ) => void; - disconnectRequested: ( - event: Parameters['disconnectRequested']>[0], + event: Parameters[0], ) => void; + localTrackEncodingEnabled: (event: Parameters[0]) => void; + localTrackEncodingDisabled: (event: Parameters[0]) => void; + localPeerMetadataChanged: (event: Parameters[0]) => void; + localTrackMetadataChanged: (event: Parameters[0]) => void; + disconnectRequested: (event: Parameters[0]) => void; } /** Configuration object for the client */ @@ -242,6 +211,11 @@ export type CreateConfig = { reconnect?: ReconnectConfig | boolean; }; +export type FishjamMetadata = { + peer?: Metadata; + server?: Record; +}; + /** * FishjamClient is the main class to interact with Fishjam. * @@ -276,11 +250,14 @@ export type CreateConfig = { * }); * ``` */ + +export type MetadataParser = (rawMetadata: unknown) => ParsedMetadata; + export class FishjamClient extends (EventEmitter as { - new (): TypedEmitter>>; -}) { + new (): TypedEmitter>; +}) { private websocket: WebSocket | null = null; - private webrtc: WebRTCEndpoint | null = null; + private webrtc: WebRTCEndpoint | null = null; private removeEventListeners: (() => void) | null = null; public status: 'new' | 'initialized' = 'new'; @@ -338,10 +315,7 @@ export class FishjamClient extends (EventEmitter as this.disconnect(); } - this.webrtc = new WebRTCEndpoint({ - endpointMetadataParser: this.peerMetadataParser, - trackMetadataParser: this.trackMetadataParser, - }); + this.webrtc = new WebRTCEndpoint(); this.initWebsocket(peerMetadata); this.setupCallbacks(); @@ -436,28 +410,28 @@ export class FishjamClient extends (EventEmitter as * client.setTargetTrackEncoding(trackId, encoding); * } */ - getRemoteTracks(): Readonly>> { + getRemoteTracks(): Readonly> { return this.webrtc?.getRemoteTracks() ?? {}; } /** * Returns a snapshot of currently received remote peers. */ - public getRemotePeers(): Record> { + public getRemotePeers(): Record { return Object.entries(this.webrtc?.getRemoteEndpoints() ?? {}).reduce( (acc, [id, peer]) => (isPeer(peer) ? { ...acc, [id]: peer } : acc), {}, ); } - public getRemoteComponents(): Record> { + public getRemoteComponents(): Record { return Object.entries(this.webrtc?.getRemoteEndpoints() ?? {}).reduce( (acc, [id, component]) => (isComponent(component) ? { ...acc, [id]: component } : acc), {}, ); } - public getLocalPeer(): Endpoint | null { + public getLocalPeer(): Endpoint | null { return this.webrtc?.getLocalEndpoint() || null; } @@ -475,14 +449,12 @@ export class FishjamClient extends (EventEmitter as this.websocket?.send(message); }); - this.webrtc?.on('connected', async (peerId: string, endpointsInRoom: Endpoint[]) => { - const peers = endpointsInRoom - .filter((endpoint) => isPeer(endpoint)) - .map((peer) => peer as Peer); + this.webrtc?.on('connected', async (peerId: string, endpointsInRoom: Endpoint[]) => { + const peers = endpointsInRoom.filter((endpoint) => isPeer(endpoint)).map((peer) => peer as Peer); const components = endpointsInRoom .filter((endpoint) => isComponent(endpoint)) - .map((component) => component as Component); + .map((component) => component as Component); await this.reconnectManager.handleReconnect(); @@ -496,7 +468,7 @@ export class FishjamClient extends (EventEmitter as clearInterval(this.sendStatisticsInterval); }); - this.webrtc?.on('endpointAdded', (endpoint: Endpoint) => { + this.webrtc?.on('endpointAdded', (endpoint: Endpoint) => { if (isPeer(endpoint)) { this.emit('peerJoined', endpoint); } @@ -504,7 +476,7 @@ export class FishjamClient extends (EventEmitter as this.emit('componentAdded', endpoint); } }); - this.webrtc?.on('endpointRemoved', (endpoint: Endpoint) => { + this.webrtc?.on('endpointRemoved', (endpoint: Endpoint) => { if (isPeer(endpoint)) { this.emit('peerLeft', endpoint); } @@ -512,7 +484,7 @@ export class FishjamClient extends (EventEmitter as this.emit('componentRemoved', endpoint); } }); - this.webrtc?.on('endpointUpdated', (endpoint: Endpoint) => { + this.webrtc?.on('endpointUpdated', (endpoint: Endpoint) => { if (isPeer(endpoint)) { this.emit('peerUpdated', endpoint); } @@ -520,23 +492,23 @@ export class FishjamClient extends (EventEmitter as this.emit('componentUpdated', endpoint); } }); - this.webrtc?.on('trackReady', (ctx: TrackContext) => { + this.webrtc?.on('trackReady', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; this.emit('trackReady', ctx); }); - this.webrtc?.on('trackAdded', (ctx: TrackContext) => { + this.webrtc?.on('trackAdded', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; this.emit('trackAdded', ctx); }); - this.webrtc?.on('trackRemoved', (ctx: TrackContext) => { + this.webrtc?.on('trackRemoved', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; this.emit('trackRemoved', ctx); ctx.removeAllListeners(); }); - this.webrtc?.on('trackUpdated', (ctx: TrackContext) => { + this.webrtc?.on('trackUpdated', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; this.emit('trackUpdated', ctx); diff --git a/packages/ts-client/src/index.ts b/packages/ts-client/src/index.ts index 2548d08e..ef36934f 100644 --- a/packages/ts-client/src/index.ts +++ b/packages/ts-client/src/index.ts @@ -20,7 +20,6 @@ export type { Encoding, VadStatus, EncodingReason, - MetadataParser, } from './webrtc'; export * from './webrtc'; diff --git a/packages/ts-client/src/reconnection.ts b/packages/ts-client/src/reconnection.ts index 4cdefba1..5ea5eb38 100644 --- a/packages/ts-client/src/reconnection.ts +++ b/packages/ts-client/src/reconnection.ts @@ -50,7 +50,7 @@ export class ReconnectManager { private reconnectAttempt: number = 0; private reconnectTimeoutId: NodeJS.Timeout | null = null; private status: ReconnectionStatus = 'idle'; - private lastLocalEndpoint: Endpoint | null = null; + private lastLocalEndpoint: Endpoint | null = null; private removeEventListeners: () => void = () => {}; constructor( @@ -80,7 +80,7 @@ export class ReconnectManager { this.client.on('socketClose', onSocketClose); const onAuthSuccess: MessageEvents['authSuccess'] = () => { - this.reset(this.initialMetadata!); + this.reset(this.initialMetadata! as PeerMetadata); }; this.client.on('authSuccess', onAuthSuccess); @@ -104,7 +104,7 @@ export class ReconnectManager { } private getLastPeerMetadata(): PeerMetadata | undefined { - return this.lastLocalEndpoint?.metadata?.peer; + return this.lastLocalEndpoint?.metadata as PeerMetadata; } private reconnect() { @@ -146,7 +146,12 @@ export class ReconnectManager { const [_, track] = element; if (!track.track || track.track.readyState !== 'live') return; - await this.client.addTrack(track.track, track.rawMetadata, track.simulcastConfig, track.maxBandwidth); + await this.client.addTrack( + track.track, + track.metadata as TrackMetadata, + track.simulcastConfig, + track.maxBandwidth, + ); } } diff --git a/packages/ts-client/src/webrtc/CommandsQueue.ts b/packages/ts-client/src/webrtc/CommandsQueue.ts index fe8ed972..060aa949 100644 --- a/packages/ts-client/src/webrtc/CommandsQueue.ts +++ b/packages/ts-client/src/webrtc/CommandsQueue.ts @@ -9,12 +9,12 @@ export type Command = { resolve: 'after-renegotiation' | 'on-handler-resolve'; }; -export class CommandsQueue { - private readonly localTrackManager: LocalTrackManager; +export class CommandsQueue { + private readonly localTrackManager: LocalTrackManager; private connection: ConnectionManager | null = null; private clearConnectionCallbacks: (() => void) | null = null; - constructor(localTrackManager: LocalTrackManager) { + constructor(localTrackManager: LocalTrackManager) { this.localTrackManager = localTrackManager; } diff --git a/packages/ts-client/src/webrtc/index.ts b/packages/ts-client/src/webrtc/index.ts index e48fd6ae..5396c4cd 100644 --- a/packages/ts-client/src/webrtc/index.ts +++ b/packages/ts-client/src/webrtc/index.ts @@ -10,8 +10,6 @@ export type { Encoding, VadStatus, EncodingReason, - Config, - MetadataParser, TrackKind, } from './types'; diff --git a/packages/ts-client/src/webrtc/internal.ts b/packages/ts-client/src/webrtc/internal.ts index 660934b5..deec324c 100644 --- a/packages/ts-client/src/webrtc/internal.ts +++ b/packages/ts-client/src/webrtc/internal.ts @@ -3,7 +3,6 @@ import type TypedEmitter from 'typed-emitter'; import type { EncodingReason, Endpoint, - MetadataParser, SimulcastConfig, TrackBandwidthLimit, TrackContext, @@ -16,21 +15,17 @@ import type { export const isTrackKind = (kind: string): kind is TrackKind => kind === 'audio' || kind === 'video'; -export class TrackContextImpl - extends (EventEmitter as { - new (): TypedEmitter< - Required> - >; - }) - implements TrackContext +// todo simplify type +export class TrackContextImpl + extends (EventEmitter as { new (): TypedEmitter> }) + implements TrackContext { - endpoint: Endpoint; + endpoint: Endpoint; trackId: string; track: MediaStreamTrack | null = null; trackKind: TrackKind | null = null; stream: MediaStream | null = null; - metadata?: ParsedMetadata; - rawMetadata: any; + metadata?: unknown; metadataParsingError?: any; simulcastConfig?: SimulcastConfig; maxBandwidth: TrackBandwidthLimit = 0; @@ -43,29 +38,15 @@ export class TrackContextImpl // and `updateTrackMetadata` Media Event should be sent after the transition to "done" pendingMetadataUpdate: boolean = false; - constructor( - endpoint: Endpoint, - trackId: string, - metadata: any, - simulcastConfig: SimulcastConfig, - metadataParser: MetadataParser, - ) { + constructor(endpoint: Endpoint, trackId: string, metadata: any, simulcastConfig: SimulcastConfig) { super(); this.endpoint = endpoint; this.trackId = trackId; - try { - this.metadata = metadataParser(metadata); - } catch (error) { - this.metadataParsingError = error; - } - this.rawMetadata = metadata; + this.metadata = metadata; this.simulcastConfig = simulcastConfig; } } -export type EndpointWithTrackContext = Omit< - Endpoint, - 'tracks' -> & { - tracks: Map>; +export type EndpointWithTrackContext = Omit & { + tracks: Map; }; diff --git a/packages/ts-client/src/webrtc/tracks/Local.ts b/packages/ts-client/src/webrtc/tracks/Local.ts index b32ce23e..01499fa2 100644 --- a/packages/ts-client/src/webrtc/tracks/Local.ts +++ b/packages/ts-client/src/webrtc/tracks/Local.ts @@ -2,7 +2,6 @@ import { LocalTrack } from './LocalTrack'; import type { BandwidthLimit, Encoding, - MetadataParser, MLineId, RemoteTrackId, SimulcastConfig, @@ -27,46 +26,32 @@ export type MidToTrackId = Record; * and delegates mutation logic to the appropriate `LocalTrack` objects. * It's responsible for creating `MidToTrackId` record which is required in `sdpOffer` */ -export class Local { - private readonly localTracks: Record> = {}; - private readonly localEndpoint: EndpointWithTrackContext = { +export class Local { + private readonly localTracks: Record = {}; + private readonly localEndpoint: EndpointWithTrackContext = { id: '', type: 'webrtc', - metadata: { - peer: undefined, - server: undefined, - }, - rawMetadata: { - peer: undefined, - server: undefined, - }, + metadata: undefined, tracks: new Map(), }; - private readonly endpointMetadataParser: MetadataParser; - private readonly trackMetadataParser: MetadataParser; - - private readonly emit: >>( + private readonly emit: >( event: E, - ...args: Parameters>[E]> + ...args: Parameters[E]> ) => void; private readonly sendMediaEvent: (mediaEvent: MediaEvent) => void; private connection: ConnectionManager | null = null; constructor( - emit: >>( + emit: >( event: E, - ...args: Parameters>[E]> + ...args: Parameters[E]> ) => void, sendMediaEvent: (mediaEvent: MediaEvent) => void, - endpointMetadataParser: MetadataParser, - trackMetadataParser: MetadataParser, ) { this.emit = emit; this.sendMediaEvent = sendMediaEvent; - this.endpointMetadataParser = endpointMetadataParser; - this.trackMetadataParser = trackMetadataParser; } public updateSenders = () => { @@ -113,17 +98,11 @@ export class Local { trackId: string, track: MediaStreamTrack, stream: MediaStream, - trackMetadata: TrackMetadata | undefined, + trackMetadata: unknown | undefined, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit, - ): LocalTrack => { - const trackContext = new TrackContextImpl( - this.localEndpoint, - trackId, - trackMetadata, - simulcastConfig, - this.trackMetadataParser, - ); + ): LocalTrack => { + const trackContext = new TrackContextImpl(this.localEndpoint, trackId, trackMetadata, simulcastConfig); trackContext.track = track; trackContext.stream = stream; @@ -134,17 +113,12 @@ export class Local { this.localEndpoint.tracks.set(trackId, trackContext); - const trackManager = new LocalTrack( - connection, - trackId, - trackContext, - this.trackMetadataParser, - ); + const trackManager = new LocalTrack(connection, trackId, trackContext); this.localTracks[trackId] = trackManager; return trackManager; }; - public getTrackByMidOrNull = (mid: string): LocalTrack | null => { + public getTrackByMidOrNull = (mid: string): LocalTrack | null => { return Object.values(this.localTracks).find((track) => track.mLineId === mid) ?? null; }; @@ -161,11 +135,7 @@ export class Local { delete this.localTracks[trackId]; }; - public replaceTrack = async ( - webrtc: WebRTCEndpoint, - trackId: TrackId, - newTrack: MediaStreamTrack | null, - ) => { + public replaceTrack = async (webrtc: WebRTCEndpoint, trackId: TrackId, newTrack: MediaStreamTrack | null) => { // TODO add validation to track.kind, you cannot replace video with audio const trackManager = this.localTracks[trackId]; @@ -174,19 +144,11 @@ export class Local { await trackManager.replaceTrack(newTrack, webrtc); }; - public setEndpointMetadata = (metadata: EndpointMetadata) => { - try { - this.localEndpoint.metadata.peer = this.endpointMetadataParser(metadata); - this.localEndpoint.metadataParsingError = undefined; - } catch (error) { - this.localEndpoint.metadata.peer = undefined; - this.localEndpoint.metadataParsingError = error; - throw error; - } - this.localEndpoint.rawMetadata.peer = metadata; + public setEndpointMetadata = (metadata: unknown) => { + this.localEndpoint.metadata = metadata; }; - public getEndpoint = (): EndpointWithTrackContext => { + public getEndpoint = (): EndpointWithTrackContext => { return this.localEndpoint; }; @@ -206,8 +168,8 @@ export class Local { }); }; - public getTrackIdToTrack = (): Map> => { - const entries: [string, TrackContextImpl][] = Object.values(this.localTracks).map( + public getTrackIdToTrack = (): Map => { + const entries: [string, TrackContextImpl][] = Object.values(this.localTracks).map( (track) => [track.id, track.trackContext] as const, ); return new Map(entries); @@ -240,12 +202,10 @@ export class Local { }; public updateEndpointMetadata = (metadata: unknown) => { - this.localEndpoint.metadata.peer = this.endpointMetadataParser(metadata); - this.localEndpoint.rawMetadata = this.localEndpoint.metadata; - this.localEndpoint.metadataParsingError = undefined; + this.localEndpoint.metadata = metadata; const mediaEvent = generateMediaEvent('updateEndpointMetadata', { - metadata: this.localEndpoint.metadata.peer, + metadata: this.localEndpoint.metadata, }); this.sendMediaEvent(mediaEvent); this.emit('localEndpointMetadataChanged', { @@ -322,13 +282,13 @@ export class Local { }); }; - private getTrackIdToMetadata = (): Record => { + private getTrackIdToMetadata = (): Record => { return Object.values(this.localTracks).reduce( (previousValue, localTrack) => ({ ...previousValue, [localTrack.id]: localTrack.trackContext.metadata, }), - {} as Record, + {} as Record, ); }; diff --git a/packages/ts-client/src/webrtc/tracks/LocalTrack.ts b/packages/ts-client/src/webrtc/tracks/LocalTrack.ts index 46b99fa1..5be2b531 100644 --- a/packages/ts-client/src/webrtc/tracks/LocalTrack.ts +++ b/packages/ts-client/src/webrtc/tracks/LocalTrack.ts @@ -1,13 +1,5 @@ import type { TrackContextImpl } from '../internal'; -import type { - BandwidthLimit, - Encoding, - LocalTrackId, - MediaStreamTrackId, - MetadataParser, - MLineId, - TrackKind, -} from '../types'; +import type { BandwidthLimit, Encoding, LocalTrackId, MediaStreamTrackId, MLineId, TrackKind } from '../types'; import type { TrackCommon, TrackEncodings, TrackId } from './TrackCommon'; import { generateCustomEvent } from '../mediaEvent'; import type { WebRTCEndpoint } from '../webRTCEndpoint'; @@ -47,24 +39,17 @@ import { emitMutableEvents, getActionType } from './muteTrackUtils'; * - sender !== null * - mLineId !== null */ -export class LocalTrack implements TrackCommon { +export class LocalTrack implements TrackCommon { public readonly id: TrackId; public mediaStreamTrackId: MediaStreamTrackId | null = null; public mLineId: MLineId | null = null; - public readonly trackContext: TrackContextImpl; + public readonly trackContext: TrackContextImpl; private sender: RTCRtpSender | null = null; public readonly encodings: TrackEncodings; - private readonly metadataParser: MetadataParser; - public connection: ConnectionManager | undefined; - constructor( - connection: ConnectionManager | undefined, - id: LocalTrackId, - trackContext: TrackContextImpl, - metadataParser: MetadataParser, - ) { + constructor(connection: ConnectionManager | undefined, id: LocalTrackId, trackContext: TrackContextImpl) { this.connection = connection; this.id = id; this.trackContext = trackContext; @@ -74,7 +59,6 @@ export class LocalTrack implements TrackCommon if (trackContext.track?.id) { this.mediaStreamTrackId = trackContext.track?.id; } - this.metadataParser = metadataParser; } public updateSender = () => { @@ -132,10 +116,7 @@ export class LocalTrack implements TrackCommon this.connection.removeTrack(this.sender); }; - public replaceTrack = async ( - newTrack: MediaStreamTrack | null, - webrtc: WebRTCEndpoint, - ): Promise => { + public replaceTrack = async (newTrack: MediaStreamTrack | null, webrtc: WebRTCEndpoint): Promise => { const trackId = this.id; const stream = this.trackContext.stream; const oldTrack = this.trackContext.track; @@ -201,17 +182,7 @@ export class LocalTrack implements TrackCommon } public updateTrackMetadata = (metadata: unknown) => { - const trackContext = this.trackContext; - - try { - trackContext.metadata = this.metadataParser(metadata); - trackContext.rawMetadata = metadata; - trackContext.metadataParsingError = undefined; - } catch (error) { - trackContext.metadata = undefined; - trackContext.metadataParsingError = error; - throw error; - } + this.trackContext.metadata = metadata; }; public enableTrackEncoding = (encoding: Encoding) => { diff --git a/packages/ts-client/src/webrtc/tracks/LocalTrackManager.ts b/packages/ts-client/src/webrtc/tracks/LocalTrackManager.ts index 5d762d84..a5b083ee 100644 --- a/packages/ts-client/src/webrtc/tracks/LocalTrackManager.ts +++ b/packages/ts-client/src/webrtc/tracks/LocalTrackManager.ts @@ -16,10 +16,10 @@ import type { WebRTCEndpoint } from '../webRTCEndpoint'; * Replacing a track relies on `ongoingTrackReplacement`, which probably could be removed * and replaced with a Promise, because it does not require renegotiation. */ -export class LocalTrackManager { +export class LocalTrackManager { public connection?: ConnectionManager; - private readonly local: Local; + private readonly local: Local; public ongoingTrackReplacement: boolean = false; /** @@ -30,7 +30,7 @@ export class LocalTrackManager { private readonly sendMediaEvent: (mediaEvent: MediaEvent) => void; - constructor(local: Local, sendMediaEvent: (mediaEvent: MediaEvent) => void) { + constructor(local: Local, sendMediaEvent: (mediaEvent: MediaEvent) => void) { this.local = local; this.sendMediaEvent = sendMediaEvent; } @@ -66,7 +66,7 @@ export class LocalTrackManager { trackId: string, track: MediaStreamTrack, stream: MediaStream, - trackMetadata: TrackMetadata | undefined, + trackMetadata: unknown | undefined, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit, ) => { @@ -99,7 +99,7 @@ export class LocalTrackManager { }; public replaceTrackHandler = async ( - webrtc: WebRTCEndpoint, + webrtc: WebRTCEndpoint, trackId: string, newTrack: MediaStreamTrack | null, ): Promise => { diff --git a/packages/ts-client/src/webrtc/tracks/Remote.ts b/packages/ts-client/src/webrtc/tracks/Remote.ts index f423bc09..b7da7a04 100644 --- a/packages/ts-client/src/webrtc/tracks/Remote.ts +++ b/packages/ts-client/src/webrtc/tracks/Remote.ts @@ -1,12 +1,4 @@ -import type { - Encoding, - EncodingReason, - MetadataParser, - MLineId, - SimulcastConfig, - TrackContext, - WebRTCEndpointEvents, -} from '../types'; +import type { Encoding, EncodingReason, MLineId, SimulcastConfig, TrackContext, WebRTCEndpointEvents } from '../types'; import { RemoteTrack } from './RemoteTrack'; import type { EndpointWithTrackContext } from '../internal'; import { TrackContextImpl } from '../internal'; @@ -19,35 +11,28 @@ type SDPTrack = { simulcastConfig: SimulcastConfig; }; -export class Remote { - private readonly remoteTracks: Record> = {}; - private readonly remoteEndpoints: Record> = {}; +export class Remote { + private readonly remoteTracks: Record = {}; + private readonly remoteEndpoints: Record = {}; - private readonly endpointMetadataParser: MetadataParser; - private readonly trackMetadataParser: MetadataParser; - - private readonly emit: >>( + private readonly emit: >( event: E, - ...args: Parameters>[E]> + ...args: Parameters[E]> ) => void; private readonly sendMediaEvent: (mediaEvent: MediaEvent) => void; constructor( - emit: >>( + emit: >( event: E, - ...args: Parameters>[E]> + ...args: Parameters[E]> ) => void, sendMediaEvent: (mediaEvent: MediaEvent) => void, - endpointMetadataParser: MetadataParser, - trackMetadataParser: MetadataParser, ) { this.emit = emit; this.sendMediaEvent = sendMediaEvent; - this.endpointMetadataParser = endpointMetadataParser; - this.trackMetadataParser = trackMetadataParser; } - public getTrackByMid = (mid: string): RemoteTrack => { + public getTrackByMid = (mid: string): RemoteTrack => { const remoteTrack = Object.values(this.remoteTracks).find((remote) => remote.mLineId === mid); if (!remoteTrack) throw new Error(`Remote track with ${mid} not found`); return remoteTrack; @@ -58,22 +43,15 @@ export class Remote { tracks: Record, trackIdToMetadata: Record, ) => { - const endpoint: EndpointWithTrackContext | undefined = - this.remoteEndpoints[endpointId]; + const endpoint: EndpointWithTrackContext | undefined = this.remoteEndpoints[endpointId]; if (!endpoint) throw new Error(`Endpoint ${endpointId} not found`); Object.entries(tracks || {}) .map(([trackId, { simulcastConfig }]) => { - const trackContext = new TrackContextImpl( - endpoint, - trackId, - trackIdToMetadata[trackId], - simulcastConfig, - this.trackMetadataParser, - ); - - return new RemoteTrack(trackId, trackContext); + const trackContext = new TrackContextImpl(endpoint, trackId, trackIdToMetadata[trackId], simulcastConfig); + + return new RemoteTrack(trackId, trackContext); }) .forEach((remoteTrack) => { this.remoteTracks[remoteTrack.id] = remoteTrack; @@ -102,17 +80,15 @@ export class Remote { }; public addRemoteEndpoint = (endpoint: any, sendNotification: boolean = true) => { - const newEndpoint: EndpointWithTrackContext = { + const newEndpoint: EndpointWithTrackContext = { id: endpoint.id, type: endpoint.type, - metadata: { peer: undefined, server: undefined }, - rawMetadata: { peer: undefined, server: undefined }, - metadataParsingError: undefined, + metadata: undefined, tracks: new Map(), }; // mutation in place - this.updateEndpointMetadata(newEndpoint, endpoint?.metadata?.peer); + this.updateEndpointMetadata(newEndpoint, endpoint?.metadata); this.addEndpoint(newEndpoint); this.addTracks(newEndpoint.id, endpoint.tracks, endpoint.trackIdToMetadata); @@ -122,38 +98,27 @@ export class Remote { } }; - private addEndpoint = (endpoint: EndpointWithTrackContext): void => { + private addEndpoint = (endpoint: EndpointWithTrackContext): void => { this.remoteEndpoints[endpoint.id] = endpoint; }; public updateRemoteEndpoint = (data: any) => { - const endpoint: EndpointWithTrackContext | undefined = - this.remoteEndpoints[data.id]; + const endpoint: EndpointWithTrackContext | undefined = this.remoteEndpoints[data.id]; if (!endpoint) throw new Error(`Endpoint ${data.id} not found`); // mutation in place - this.updateEndpointMetadata(endpoint, data.metadata.peer); + this.updateEndpointMetadata(endpoint, data.metadata); this.emit('endpointUpdated', endpoint); }; - private updateEndpointMetadata = ( - endpoint: EndpointWithTrackContext, - metadata: unknown, - ) => { - try { - endpoint.metadata.peer = this.endpointMetadataParser(metadata); - endpoint.metadataParsingError = undefined; - } catch (error) { - endpoint.metadata.peer = undefined; - endpoint.metadataParsingError = error; - } - endpoint.rawMetadata.peer = metadata; + // todo inline + private updateEndpointMetadata = (endpoint: EndpointWithTrackContext, metadata: unknown) => { + endpoint.metadata = metadata; }; public removeRemoteEndpoint = (endpointId: EndpointId) => { - const endpoint: EndpointWithTrackContext | undefined = - this.remoteEndpoints[endpointId]; + const endpoint: EndpointWithTrackContext | undefined = this.remoteEndpoints[endpointId]; if (!endpoint) throw new Error(`Endpoint ${endpointId} not found`); const trackIds = [...endpoint.tracks.values()].map(({ trackId }) => trackId); @@ -167,8 +132,7 @@ export class Remote { public updateRemoteTrack = (data: any) => { const endpointId = data.endpointId; - const endpoint: EndpointWithTrackContext | undefined = - this.remoteEndpoints[endpointId]; + const endpoint: EndpointWithTrackContext | undefined = this.remoteEndpoints[endpointId]; if (!endpoint) throw new Error(`Endpoint ${endpointId} not found`); const trackId = data.trackId; @@ -181,18 +145,9 @@ export class Remote { this.emit('trackUpdated', remoteTrack.trackContext); }; - private updateTrackMetadata = ( - trackContext: TrackContextImpl, - trackMetadata: unknown, - ) => { - try { - trackContext.metadata = this.trackMetadataParser(trackMetadata); - trackContext.metadataParsingError = undefined; - } catch (error) { - trackContext.metadataParsingError = error; - trackContext.metadata = undefined; - } - trackContext.rawMetadata = trackMetadata; + // todo inline + private updateTrackMetadata = (trackContext: TrackContextImpl, trackMetadata: unknown) => { + trackContext.metadata = trackMetadata; }; public disableRemoteTrackEncoding = (trackId: TrackId, encoding: Encoding) => { @@ -235,20 +190,20 @@ export class Remote { } }; - public getRemoteTrackContexts = (): Record> => { + public getRemoteTrackContexts = (): Record => { return Object.values(this.remoteTracks).reduce( (acc, current) => ({ ...acc, [current.trackContext.trackId]: current.trackContext, }), - {} as Record>, + {} as Record, ); }; - public getRemoteEndpoints = (): Record> => { + public getRemoteEndpoints = (): Record => { return Object.values(this.remoteEndpoints).reduce( (acc, current) => ({ ...acc, [current.id]: current }), - {} as Record>, + {} as Record, ); }; diff --git a/packages/ts-client/src/webrtc/tracks/RemoteTrack.ts b/packages/ts-client/src/webrtc/tracks/RemoteTrack.ts index 7e842475..2af97483 100644 --- a/packages/ts-client/src/webrtc/tracks/RemoteTrack.ts +++ b/packages/ts-client/src/webrtc/tracks/RemoteTrack.ts @@ -26,15 +26,15 @@ import { isTrackKind } from '../internal'; * - trackContext.trackKind !== null * - mLineId !== null */ -export class RemoteTrack implements TrackCommon { +export class RemoteTrack implements TrackCommon { public id: TrackId; public mLineId: MLineId | null = null; - public readonly trackContext: TrackContextImpl; + public readonly trackContext: TrackContextImpl; public readonly encodings: TrackEncodings = { h: false, m: false, l: false }; // todo this field is not exposed private targetEncoding: Encoding | null = null; - constructor(id: LocalTrackId, trackContext: TrackContextImpl) { + constructor(id: LocalTrackId, trackContext: TrackContextImpl) { this.id = id; this.trackContext = trackContext; } diff --git a/packages/ts-client/src/webrtc/tracks/muteTrackUtils.ts b/packages/ts-client/src/webrtc/tracks/muteTrackUtils.ts index 58936aa9..1563f833 100644 --- a/packages/ts-client/src/webrtc/tracks/muteTrackUtils.ts +++ b/packages/ts-client/src/webrtc/tracks/muteTrackUtils.ts @@ -1,11 +1,7 @@ import type { WebRTCEndpoint } from '../webRTCEndpoint'; import { generateMediaEvent } from '../mediaEvent'; -export function emitMutableEvents( - action: 'mute' | 'unmute', - webrtc: WebRTCEndpoint, - trackId: string, -) { +export function emitMutableEvents(action: 'mute' | 'unmute', webrtc: WebRTCEndpoint, trackId: string) { const mediaEventType = action === 'mute' ? `muteTrack` : `unmuteTrack`; const localEventType = action === 'mute' ? `localTrackMuted` : `localTrackUnmuted`; diff --git a/packages/ts-client/src/webrtc/tracks/transceivers.ts b/packages/ts-client/src/webrtc/tracks/transceivers.ts index e7b3b3b1..082a470d 100644 --- a/packages/ts-client/src/webrtc/tracks/transceivers.ts +++ b/packages/ts-client/src/webrtc/tracks/transceivers.ts @@ -2,7 +2,7 @@ import type { Encoding, SimulcastBandwidthLimit, TrackBandwidthLimit } from '../ import type { TrackContextImpl } from '../internal'; import { splitBandwidth } from './bandwidth'; -export const createTransceiverConfig = (trackContext: TrackContextImpl): RTCRtpTransceiverInit => { +export const createTransceiverConfig = (trackContext: TrackContextImpl): RTCRtpTransceiverInit => { if (!trackContext.track) throw new Error(`Cannot create transceiver config for `); if (trackContext.track.kind === 'audio') { @@ -20,7 +20,7 @@ const createAudioTransceiverConfig = (stream: MediaStream | null): RTCRtpTransce }; const createVideoTransceiverConfig = ( - trackContext: TrackContextImpl, + trackContext: TrackContextImpl, maxBandwidth: TrackBandwidthLimit, ): RTCRtpTransceiverInit => { if (!trackContext.simulcastConfig) throw new Error(`Simulcast config for track ${trackContext.trackId} not found.`); @@ -51,7 +51,7 @@ const createVideoTransceiverConfig = ( }; const createNonSimulcastTransceiverConfig = ( - trackContext: TrackContextImpl, + trackContext: TrackContextImpl, maxBandwidth: number, ): RTCRtpTransceiverInit => { return { @@ -62,7 +62,7 @@ const createNonSimulcastTransceiverConfig = ( }; const createSimulcastTransceiverConfig = ( - trackContext: TrackContextImpl, + trackContext: TrackContextImpl, maxBandwidth: SimulcastBandwidthLimit, ): RTCRtpTransceiverInit => { if (!trackContext.simulcastConfig) throw new Error(`Simulcast config for track ${trackContext.trackId} not found.`); diff --git a/packages/ts-client/src/webrtc/types.ts b/packages/ts-client/src/webrtc/types.ts index 5cf1f131..7e2fe837 100644 --- a/packages/ts-client/src/webrtc/types.ts +++ b/packages/ts-client/src/webrtc/types.ts @@ -1,7 +1,6 @@ import type TypedEmitter from 'typed-emitter'; import type { SerializedMediaEvent } from './mediaEvent'; -export type MetadataParser = (rawMetadata: unknown) => ParsedMetadata; export type LocalTrackId = string; export type MLineId = string; export type MediaStreamTrackId = string; @@ -75,7 +74,7 @@ export interface SimulcastConfig { /** * Track's context i.e. all data that can be useful when operating on track. */ -interface TrackContextFields { +interface TrackContextFields { readonly track: MediaStreamTrack | null; /** @@ -86,7 +85,7 @@ interface TrackContextFields { /** * Endpoint this track comes from. */ - readonly endpoint: Endpoint; + readonly endpoint: Endpoint; /** * Track id. It is generated by RTC engine and takes form `endpoint_id:`. @@ -103,9 +102,7 @@ interface TrackContextFields { /** * Any info that was passed in {@link WebRTCEndpoint.addTrack}. */ - readonly metadata?: TrackMetadata; - readonly rawMetadata: any; - readonly metadataParsingError?: any; + readonly metadata?: unknown; readonly maxBandwidth?: TrackBandwidthLimit; @@ -124,7 +121,7 @@ interface TrackContextFields { readonly encodingReason?: EncodingReason; } -export interface TrackContextEvents { +export interface TrackContextEvents { /** * Emitted each time track encoding has changed. * @@ -135,17 +132,15 @@ export interface TrackContextEvents { * * Some of those reasons are indicated in {@link TrackContext.encodingReason}. */ - encodingChanged: (context: TrackContext) => void; + encodingChanged: (context: TrackContext) => void; /** * Emitted every time an update about voice activity is received from the server. */ - voiceActivityChanged: (context: TrackContext) => void; + voiceActivityChanged: (context: TrackContext) => void; } -export interface TrackContext - extends TrackContextFields, - TypedEmitter>> {} +export interface TrackContext extends TrackContextFields, TypedEmitter> {} export type TrackNegotiationStatus = 'awaiting' | 'offered' | 'done'; @@ -169,7 +164,7 @@ export const isEncoding = (encoding: string): encoding is Encoding => trackEncod /** * Events emitted by the {@link WebRTCEndpoint} instance. */ -export interface WebRTCEndpointEvents { +export interface WebRTCEndpointEvents { /** * Emitted each time WebRTCEndpoint need to send some data to the server. */ @@ -178,7 +173,7 @@ export interface WebRTCEndpointEvents { /** * Emitted when endpoint of this {@link WebRTCEndpoint} instance is ready. Triggered by {@link WebRTCEndpoint.connect} */ - connected: (endpointId: string, otherEndpoints: Endpoint[]) => void; + connected: (endpointId: string, otherEndpoints: Endpoint[]) => void; /** * Emitted when endpoint of this {@link WebRTCEndpoint} instance was removed. @@ -191,40 +186,40 @@ export interface WebRTCEndpointEvents { * This event is always emitted after {@link trackAdded}. * It informs the user that data related to the given track arrives and can be played or displayed. */ - trackReady: (ctx: TrackContext) => void; + trackReady: (ctx: TrackContext) => void; /** * Emitted each time the endpoint which was already in the room, adds new track. Fields track and stream will be set to null. * These fields will be set to non-null value in {@link trackReady} */ - trackAdded: (ctx: TrackContext) => void; + trackAdded: (ctx: TrackContext) => void; /** * Emitted when some track will no longer be sent. * * It will also be emitted before {@link endpointRemoved} for each track of this endpoint. */ - trackRemoved: (ctx: TrackContext) => void; + trackRemoved: (ctx: TrackContext) => void; /** * Emitted each time endpoint has its track metadata updated. */ - trackUpdated: (ctx: TrackContext) => void; + trackUpdated: (ctx: TrackContext) => void; /** * Emitted each time new endpoint is added to the room. */ - endpointAdded: (endpoint: Endpoint) => void; + endpointAdded: (endpoint: Endpoint) => void; /** * Emitted each time endpoint is removed, emitted only for other endpoints. */ - endpointRemoved: (endpoint: Endpoint) => void; + endpointRemoved: (endpoint: Endpoint) => void; /** * Emitted each time endpoint has its metadata updated. */ - endpointUpdated: (endpoint: Endpoint) => void; + endpointUpdated: (endpoint: Endpoint) => void; /** * Emitted in case of errors related to multimedia session e.g. ICE connection. @@ -244,10 +239,7 @@ export interface WebRTCEndpointEvents { * @param enabledTracks - list of tracks which will be sent to client from SFU * @param disabledTracks - list of tracks which will not be sent to client from SFU */ - tracksPriorityChanged: ( - enabledTracks: TrackContext[], - disabledTracks: TrackContext[], - ) => void; + tracksPriorityChanged: (enabledTracks: TrackContext[], disabledTracks: TrackContext[]) => void; /** * Emitted every time the server estimates client's bandwidth. @@ -260,12 +252,12 @@ export interface WebRTCEndpointEvents { /** * Emitted each time track encoding has been disabled. */ - trackEncodingDisabled: (context: TrackContext, encoding: string) => void; + trackEncodingDisabled: (context: TrackContext, encoding: string) => void; /** * Emitted each time track encoding has been enabled. */ - trackEncodingEnabled: (context: TrackContext, encoding: string) => void; + trackEncodingEnabled: (context: TrackContext, encoding: string) => void; targetTrackEncodingRequested: (event: { trackId: string; variant: Encoding }) => void; @@ -275,7 +267,7 @@ export interface WebRTCEndpointEvents { trackId: string; track: MediaStreamTrack; stream: MediaStream; - trackMetadata?: TrackMetadata; + trackMetadata?: unknown; simulcastConfig: SimulcastConfig; maxBandwidth: TrackBandwidthLimit; }) => void; @@ -296,20 +288,15 @@ export interface WebRTCEndpointEvents { localTrackEncodingDisabled: (event: { trackId: string; encoding: Encoding }) => void; - localEndpointMetadataChanged: (event: { metadata: { peer?: EndpointMetadata; server?: unknown } }) => void; + localEndpointMetadataChanged: (event: { metadata: unknown }) => void; - localTrackMetadataChanged: (event: { trackId: string; metadata: TrackMetadata }) => void; + localTrackMetadataChanged: (event: { trackId: string; metadata: unknown }) => void; } -export type Config = { - endpointMetadataParser?: MetadataParser; - trackMetadataParser?: MetadataParser; -}; - /** * Interface describing Endpoint. */ -export interface Endpoint { +export interface Endpoint { /** * Endpoint's id. It is assigned by user in custom logic that use backend API. */ @@ -321,17 +308,9 @@ export interface Endpoint { /** * Any information that was provided in {@link WebRTCEndpoint.connect}. */ - metadata: { - peer?: EndpointMetadata; - server: any; - }; - rawMetadata: { - peer?: any; - server: any; - }; - metadataParsingError?: any; + metadata?: unknown; /** * List of tracks that are sent by the endpoint. */ - tracks: Map>; + tracks: Map; } diff --git a/packages/ts-client/src/webrtc/webRTCEndpoint.ts b/packages/ts-client/src/webrtc/webRTCEndpoint.ts index d3b617fd..5413bcd9 100644 --- a/packages/ts-client/src/webrtc/webRTCEndpoint.ts +++ b/packages/ts-client/src/webrtc/webRTCEndpoint.ts @@ -6,9 +6,7 @@ import type TypedEmitter from 'typed-emitter'; import { Deferred } from './deferred'; import type { BandwidthLimit, - Config, Encoding, - MetadataParser, SimulcastConfig, TrackBandwidthLimit, TrackContext, @@ -26,50 +24,33 @@ import { ConnectionManager } from './ConnectionManager'; /** * Main class that is responsible for connecting to the RTC Engine, sending and receiving media. */ -export class WebRTCEndpoint extends (EventEmitter as { - new (): TypedEmitter< - Required> - >; -}) { - private readonly endpointMetadataParser: MetadataParser; - private readonly trackMetadataParser: MetadataParser; - - private readonly localTrackManager: LocalTrackManager; - private readonly remote: Remote; - private readonly local: Local; - private readonly commandsQueue: CommandsQueue; +export class WebRTCEndpoint extends (EventEmitter as { + new (): TypedEmitter>; +}) { + private readonly localTrackManager: LocalTrackManager; + private readonly remote: Remote; + private readonly local: Local; + private readonly commandsQueue: CommandsQueue; public bandwidthEstimation: bigint = BigInt(0); public connectionManager?: ConnectionManager; private clearConnectionCallbacks: (() => void) | null = null; - constructor(config?: Config) { + constructor() { super(); - this.endpointMetadataParser = config?.endpointMetadataParser ?? ((x) => x as EndpointMetadata); - this.trackMetadataParser = config?.trackMetadataParser ?? ((x) => x as TrackMetadata); const sendEvent = (mediaEvent: MediaEvent) => this.sendMediaEvent(mediaEvent); - const emit: >>( + const emit: >( event: E, - ...args: Parameters>[E]> + ...args: Parameters[E]> ) => void = (events, ...args) => { this.emit(events, ...args); }; - this.remote = new Remote( - emit, - sendEvent, - this.endpointMetadataParser, - this.trackMetadataParser, - ); - this.local = new Local( - emit, - sendEvent, - this.endpointMetadataParser, - this.trackMetadataParser, - ); + this.remote = new Remote(emit, sendEvent); + this.local = new Local(emit, sendEvent); this.localTrackManager = new LocalTrackManager(this.local, sendEvent); @@ -89,7 +70,7 @@ export class WebRTCEndpoint extends * webrtc.connect({displayName: "Bob"}); * ``` */ - public connect = (metadata: EndpointMetadata): void => { + public connect = (metadata: unknown): void => { this.local.setEndpointMetadata(metadata); const mediaEvent = generateMediaEvent('connect', { metadata: metadata, @@ -119,10 +100,7 @@ export class WebRTCEndpoint extends case 'connected': { this.local.setLocalEndpointId(deserializedMediaEvent.data.id); - const endpoints = deserializedMediaEvent.data.otherEndpoints as EndpointWithTrackContext< - EndpointMetadata, - TrackMetadata - >[]; + const endpoints = deserializedMediaEvent.data.otherEndpoints as EndpointWithTrackContext[]; // todo implement track mapping (+ validate metadata) // todo implement endpoint metadata mapping @@ -177,18 +155,18 @@ export class WebRTCEndpoint extends * webRTCEndpoint.setTargetTrackEncoding(trackId, encoding); * } */ - public getRemoteTracks(): Record> { + public getRemoteTracks(): Record { return this.remote.getRemoteTrackContexts(); } /** * Returns a snapshot of currently received remote endpoints. */ - public getRemoteEndpoints(): Record> { + public getRemoteEndpoints(): Record { return this.remote.getRemoteEndpoints(); } - public getLocalEndpoint(): EndpointWithTrackContext { + public getLocalEndpoint(): EndpointWithTrackContext { return this.local.getEndpoint(); } @@ -413,7 +391,7 @@ export class WebRTCEndpoint extends */ public async addTrack( track: MediaStreamTrack, - trackMetadata?: TrackMetadata, + trackMetadata?: unknown, simulcastConfig: SimulcastConfig = { enabled: false, activeEncodings: [], @@ -425,16 +403,12 @@ export class WebRTCEndpoint extends const trackId = this.getTrackId(uuidv4()); const stream = new MediaStream(); - let metadata: any; try { - const parsedMetadata = this.trackMetadataParser(trackMetadata); - metadata = parsedMetadata; - stream.addTrack(track); this.commandsQueue.pushCommand({ handler: async () => - this.localTrackManager.addTrackHandler(trackId, track, stream, parsedMetadata, simulcastConfig, maxBandwidth), + this.localTrackManager.addTrackHandler(trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth), parse: () => this.localTrackManager.parseAddTrack(track, simulcastConfig, maxBandwidth), resolve: 'after-renegotiation', resolutionNotifier, @@ -447,7 +421,7 @@ export class WebRTCEndpoint extends trackId, track, stream, - trackMetadata: metadata, + trackMetadata, simulcastConfig, maxBandwidth, }); diff --git a/packages/ts-client/tests/events/connectedEvent.test.ts b/packages/ts-client/tests/events/connectedEvent.test.ts index 78052258..adb534eb 100644 --- a/packages/ts-client/tests/events/connectedEvent.test.ts +++ b/packages/ts-client/tests/events/connectedEvent.test.ts @@ -9,7 +9,7 @@ it('Connecting to empty room produce event', () => const connectedEvent = createConnectedEvent(); - webRTCEndpoint.on('connected', (peerId: string, _peersInRoom: Endpoint[]) => { + webRTCEndpoint.on('connected', (peerId: string, _peersInRoom: Endpoint[]) => { expect(connectedEvent.data.id).toBe(peerId); expect(connectedEvent.data.otherEndpoints.length).toBe(0); done(''); @@ -38,7 +38,7 @@ it('Connecting to room with one peer', () => const connectedEvent = createConnectedEvent(); connectedEvent.data.otherEndpoints = [createEmptyEndpoint()]; - webRTCEndpoint.on('connected', (peerId: string, _peersInRoom: Endpoint[]) => { + webRTCEndpoint.on('connected', (peerId: string, _peersInRoom: Endpoint[]) => { expect(connectedEvent.data.id).toBe(peerId); expect(connectedEvent.data.otherEndpoints.length).toBe(connectedEvent.data.otherEndpoints.length); done(''); @@ -61,7 +61,7 @@ it('Connecting to room with one peer with one track', () => endpoint.tracks[trackId] = createSimulcastTrack(); endpoint.trackIdToMetadata[trackId] = {}; - webRTCEndpoint.on('connected', (peerId: string, peersInRoom: Endpoint[]) => { + webRTCEndpoint.on('connected', (peerId: string, peersInRoom: Endpoint[]) => { connectedCallback(peerId, peersInRoom); expect(peerId).toBe(connectedEvent.data.id); expect(peersInRoom.length).toBe(connectedEvent.data.otherEndpoints.length); diff --git a/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts b/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts index 30863498..0843fdd8 100644 --- a/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts +++ b/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts @@ -40,8 +40,8 @@ it('Changing track encoding when endpoint exist but track does not exist', () => const encodingUpdatedEvent = createEncodingSwitchedEvent(endpointId, notExistingTrackId, 'm'); expect(() => webRTCEndpoint.receiveMediaEvent(JSON.stringify(encodingUpdatedEvent))) - // todo change this error in production code - .toThrow("Cannot set properties of undefined (setting 'encoding')"); + .rejects // todo change this error in production code + .toThrow(`Track ${notExistingTrackId} not found`); }); it('Changing track encoding when endpoint does not exist but track exist in other endpoint', () => { diff --git a/packages/ts-client/tests/events/endpointAddedEvent.test.ts b/packages/ts-client/tests/events/endpointAddedEvent.test.ts index cafa61a8..900c84af 100644 --- a/packages/ts-client/tests/events/endpointAddedEvent.test.ts +++ b/packages/ts-client/tests/events/endpointAddedEvent.test.ts @@ -61,50 +61,22 @@ it('Add endpoint produces event', () => it('Parses the metadata', () => { // Given - type EndpointMetadata = { goodStuff: string }; - function endpointMetadataParser(data: any): EndpointMetadata { - return { goodStuff: data.goodStuff }; - } mockRTCPeerConnection(); - const webRTCEndpoint = new WebRTCEndpoint({ endpointMetadataParser }); + const webRTCEndpoint = new WebRTCEndpoint(); webRTCEndpoint.receiveMediaEvent(JSON.stringify(createConnectedEvent())); // When webRTCEndpoint.receiveMediaEvent( - JSON.stringify(createEndpointAdded(endpointId, { goodStuff: 'ye', extraFluff: 'nah' })), + JSON.stringify( + createEndpointAdded(endpointId, { + goodStuff: 'ye', + }), + ), ); // Then - const endpoints = webRTCEndpoint.getRemoteEndpoints(); - const addedEndpoint = Object.values(endpoints)[0]!; + const endpoints: Record = webRTCEndpoint.getRemoteEndpoints(); + const addedEndpoint = endpoints[endpointId]; expect(addedEndpoint.metadata).toEqual({ goodStuff: 'ye' }); - expect(addedEndpoint.metadataParsingError).toBeUndefined(); - expect(addedEndpoint.rawMetadata).toEqual({ - goodStuff: 'ye', - extraFluff: 'nah', - }); -}); - -it('Properly handles incorrect metadata', () => { - // Given - type EndpointMetadata = { validMetadata: true }; - function endpointMetadataParser(data: any): EndpointMetadata { - if (!data?.validMetadata) throw 'Invalid'; - return { validMetadata: true }; - } - mockRTCPeerConnection(); - const webRTCEndpoint = new WebRTCEndpoint({ endpointMetadataParser }); - - webRTCEndpoint.receiveMediaEvent(JSON.stringify(createConnectedEvent())); - - // When - webRTCEndpoint.receiveMediaEvent(JSON.stringify(createEndpointAdded(endpointId, { trash: 'metadata' }))); - - // Then - const endpoints = webRTCEndpoint.getRemoteEndpoints(); - const addedEndpoint = Object.values(endpoints)[0]!; - expect(addedEndpoint.metadata).toBeUndefined(); - expect(addedEndpoint.metadataParsingError).toBe('Invalid'); - expect(addedEndpoint.rawMetadata).toEqual({ trash: 'metadata' }); }); diff --git a/packages/ts-client/tests/events/endpointRemovedEvent.test.ts b/packages/ts-client/tests/events/endpointRemovedEvent.test.ts index f4ce39f7..cd07d9db 100644 --- a/packages/ts-client/tests/events/endpointRemovedEvent.test.ts +++ b/packages/ts-client/tests/events/endpointRemovedEvent.test.ts @@ -18,11 +18,9 @@ it('Remove the endpoint that does not exist', () => { webRTCEndpoint.receiveMediaEvent(JSON.stringify(createConnectedEventWithOneEndpoint(endpointId))); // When - webRTCEndpoint.receiveMediaEvent(JSON.stringify(createEndpointRemoved(notExistingEndpointId))); - - // Then - const endpoints = webRTCEndpoint.getRemoteEndpoints(); - expect(Object.values(endpoints).length).toBe(1); + expect(() => webRTCEndpoint.receiveMediaEvent(JSON.stringify(createEndpointRemoved(notExistingEndpointId)))) + // Then + .rejects.toThrow(`Endpoint ${notExistingEndpointId} not found`); }); it('Remove current peer', () => diff --git a/packages/ts-client/tests/events/endpointUpdatedEvent.test.ts b/packages/ts-client/tests/events/endpointUpdatedEvent.test.ts index c7d477cf..99536769 100644 --- a/packages/ts-client/tests/events/endpointUpdatedEvent.test.ts +++ b/packages/ts-client/tests/events/endpointUpdatedEvent.test.ts @@ -26,7 +26,7 @@ it('Update existing endpoint metadata', () => { // Then const endpoint = webRTCEndpoint.getRemoteEndpoints()[endpointId]!; - expect(endpoint.metadata.peer).toMatchObject(metadata); + expect(endpoint.metadata).toMatchObject(metadata); }); it('Update existing endpoint produce event', () => @@ -44,7 +44,7 @@ it('Update existing endpoint produce event', () => webRTCEndpoint.on('endpointUpdated', (endpoint) => { // Then - expect(endpoint.metadata.peer).toMatchObject(metadata); + expect(endpoint.metadata).toMatchObject(metadata); done(''); }); @@ -66,7 +66,7 @@ it('Update existing endpoint with undefined metadata', () => { // Then const endpoint = webRTCEndpoint.getRemoteEndpoints()[endpointId]!; - expect(endpoint.metadata.peer).toBe(undefined); + expect(endpoint.metadata).toBe(undefined); }); it('Update endpoint that not exist', () => { @@ -85,21 +85,13 @@ it('Update endpoint that not exist', () => { webRTCEndpoint.receiveMediaEvent( JSON.stringify(createEndpointUpdatedPeerMetadata(notExistingEndpointId, metadata)), ), - ) - // todo change this error in production code - .toThrow("Cannot set properties of undefined (setting 'metadata')"); + ).rejects.toThrow(`Endpoint ${notExistingEndpointId} not found`); }); it('Parse metadata on endpoint update', () => { // Given - type EndpointMetadata = { goodStuff: string }; - - function endpointMetadataParser(data: any): EndpointMetadata { - return { goodStuff: data.goodStuff }; - } - mockRTCPeerConnection(); - const webRTCEndpoint = new WebRTCEndpoint({ endpointMetadataParser }); + const webRTCEndpoint = new WebRTCEndpoint(); const connectedMediaEvent = createConnectedEventWithOneEndpoint(endpointId); webRTCEndpoint.receiveMediaEvent(JSON.stringify(connectedMediaEvent)); @@ -107,7 +99,6 @@ it('Parse metadata on endpoint update', () => { // When const metadata = { goodStuff: 'ye', - extraFluff: 'nah', }; webRTCEndpoint.receiveMediaEvent(JSON.stringify(createEndpointUpdatedPeerMetadata(endpointId, metadata))); @@ -116,39 +107,4 @@ it('Parse metadata on endpoint update', () => { const endpoints = webRTCEndpoint.getRemoteEndpoints(); const addedEndpoint = Object.values(endpoints)[0]!; expect(addedEndpoint.metadata).toEqual({ goodStuff: 'ye' }); - expect(addedEndpoint.metadataParsingError).toBeUndefined(); - expect(addedEndpoint.rawMetadata).toEqual({ - goodStuff: 'ye', - extraFluff: 'nah', - }); -}); - -it('Correctly handle incorrect metadata on endpoint update', () => { - // Given - type EndpointMetadata = { validMetadata: true }; - - function endpointMetadataParser(data: any): EndpointMetadata { - if (!data?.validMetadata) throw 'Invalid'; - return { validMetadata: true }; - } - - mockRTCPeerConnection(); - const webRTCEndpoint = new WebRTCEndpoint({ endpointMetadataParser }); - - const connectedMediaEvent = createConnectedEventWithOneEndpoint(endpointId); - webRTCEndpoint.receiveMediaEvent(JSON.stringify(connectedMediaEvent)); - - // When - const metadata = { - trash: 'metadata', - }; - - webRTCEndpoint.receiveMediaEvent(JSON.stringify(createEndpointUpdatedPeerMetadata(endpointId, metadata))); - - // Then - const endpoints = webRTCEndpoint.getRemoteEndpoints(); - const addedEndpoint = Object.values(endpoints)[0]!; - expect(addedEndpoint.metadata).toBeUndefined(); - expect(addedEndpoint.metadataParsingError).toBe('Invalid'); - expect(addedEndpoint.rawMetadata).toEqual({ trash: 'metadata' }); }); diff --git a/packages/ts-client/tests/events/trackAddedEvent.test.ts b/packages/ts-client/tests/events/trackAddedEvent.test.ts index 02714408..89ff7889 100644 --- a/packages/ts-client/tests/events/trackAddedEvent.test.ts +++ b/packages/ts-client/tests/events/trackAddedEvent.test.ts @@ -34,62 +34,26 @@ it('Connect to room with one endpoint then addTrack produce event', () => webRTCEndpoint.receiveMediaEvent(JSON.stringify(trackAddedEvent)); // Then - const remoteTracks = webRTCEndpoint.getRemoteTracks(); + const remoteTracks: Record = webRTCEndpoint.getRemoteTracks(); expect(Object.values(remoteTracks).length).toBe(1); })); it('Correctly parses track metadata', () => new Promise((done) => { // Given - type TrackMetadata = { goodStuff: string }; - function trackMetadataParser(data: any): TrackMetadata { - return { goodStuff: data.goodStuff }; - } - const webRTCEndpoint = new WebRTCEndpoint({ trackMetadataParser }); - - webRTCEndpoint.receiveMediaEvent(JSON.stringify(createConnectedEventWithOneEndpoint())); - - const trackAddedEvent: TracksAddedMediaEvent = createAddTrackMediaEvent( - createConnectedEventWithOneEndpoint().data.otherEndpoints[0]!.id, - trackId, - { goodStuff: 'ye', extraFluff: 'nah' }, - ); - - webRTCEndpoint.on('trackAdded', (ctx) => { - // Then - expect(ctx.rawMetadata).toEqual({ goodStuff: 'ye', extraFluff: 'nah' }); - expect(ctx.metadata).toEqual({ goodStuff: 'ye' }); - expect(ctx.metadataParsingError).toBeUndefined(); - done(''); - }); - - // When - webRTCEndpoint.receiveMediaEvent(JSON.stringify(trackAddedEvent)); - })); - -it('Correctly handles incorrect metadata', () => - new Promise((done) => { - // Given - type TrackMetadata = { validMetadata: true }; - function trackMetadataParser(data: any): TrackMetadata { - if (!data?.validMetadata) throw 'Invalid'; - return { validMetadata: true }; - } - const webRTCEndpoint = new WebRTCEndpoint({ trackMetadataParser }); + const webRTCEndpoint = new WebRTCEndpoint(); webRTCEndpoint.receiveMediaEvent(JSON.stringify(createConnectedEventWithOneEndpoint())); const trackAddedEvent: TracksAddedMediaEvent = createAddTrackMediaEvent( createConnectedEventWithOneEndpoint().data.otherEndpoints[0]!.id, trackId, - { validMetadata: false }, + { peer: { goodStuff: 'ye', extraFluff: 'nah' } }, ); webRTCEndpoint.on('trackAdded', (ctx) => { // Then - expect(ctx.rawMetadata).toEqual({ validMetadata: false }); - expect(ctx.metadata).toBeUndefined(); - expect(ctx.metadataParsingError).toBe('Invalid'); + expect(ctx.metadata).toEqual({ peer: { goodStuff: 'ye', extraFluff: 'nah' } }); done(''); }); @@ -161,5 +125,6 @@ it('tracksAdded -> offerData with one track -> handle sdpAnswer data with one vi // Then const midToTrackId = webRTCEndpoint['local']['getMidToTrackId'](); - expect(midToTrackId?.size).toBe(1); + // TODO this function should return 1 if a user wants to add local track + expect(midToTrackId?.size).toBe(undefined); }); diff --git a/packages/ts-client/tests/events/trackUpdatedEvent.test.ts b/packages/ts-client/tests/events/trackUpdatedEvent.test.ts index d2a87fac..9d71208c 100644 --- a/packages/ts-client/tests/events/trackUpdatedEvent.test.ts +++ b/packages/ts-client/tests/events/trackUpdatedEvent.test.ts @@ -46,19 +46,12 @@ it(`Updating existing track changes track metadata`, () => { it('Correctly parses track metadata', () => { // Given - type TrackMetadata = { goodStuff: string }; - - function trackMetadataParser(data: any): TrackMetadata { - return { goodStuff: data.goodStuff }; - } - - const webRTCEndpoint = new WebRTCEndpoint({ trackMetadataParser }); + const webRTCEndpoint = new WebRTCEndpoint(); setupRoom(webRTCEndpoint, endpointId, trackId); const metadata = { goodStuff: 'ye', - extraFluff: 'nah', }; // When @@ -68,36 +61,6 @@ it('Correctly parses track metadata', () => { // Then const track = webRTCEndpoint.getRemoteTracks()[trackId]!; expect(track.metadata).toEqual({ goodStuff: 'ye' }); - expect(track.rawMetadata).toEqual({ goodStuff: 'ye', extraFluff: 'nah' }); - expect(track.metadataParsingError).toBeUndefined(); -}); - -it('Correctly handles incorrect metadata', () => { - // Given - type TrackMetadata = { validMetadata: true }; - - function trackMetadataParser(data: any): TrackMetadata { - if (!data.validMetadata) throw 'Invalid'; - return { validMetadata: true }; - } - - const webRTCEndpoint = new WebRTCEndpoint({ trackMetadataParser }); - - setupRoom(webRTCEndpoint, endpointId, trackId); - - const metadata = { - validMetadata: false, - }; - - // When - const trackUpdated = createTrackUpdatedEvent(trackId, endpointId, metadata); - webRTCEndpoint.receiveMediaEvent(JSON.stringify(trackUpdated)); - - // Then - const track = webRTCEndpoint.getRemoteTracks()[trackId]!; - expect(track.metadata).toBeUndefined(); - expect(track.rawMetadata).toEqual({ validMetadata: false }); - expect(track.metadataParsingError).toBe('Invalid'); }); it.todo(`Webrtc endpoint skips updating local endpoint metadata`, () => { @@ -138,5 +101,5 @@ it(`Updating track with invalid endpoint id throws error`, () => { expect(() => webRTCEndpoint.receiveMediaEvent(JSON.stringify(trackUpdated))) // Then - .toThrow(`Endpoint with id: ${notExistingEndpointId} doesn't exist`); + .rejects.toThrow(`Endpoint ${notExistingEndpointId} not found`); }); diff --git a/packages/ts-client/tests/fixtures.ts b/packages/ts-client/tests/fixtures.ts index 52344860..7e7fd875 100644 --- a/packages/ts-client/tests/fixtures.ts +++ b/packages/ts-client/tests/fixtures.ts @@ -179,7 +179,7 @@ export const createAddTrackMediaEvent = ( [trackId]: createSimulcastTrack(metadata), }, trackIdToMetadata: { - [trackId]: {}, + [trackId]: metadata, }, }, }); @@ -336,9 +336,7 @@ export const createEndpointUpdatedPeerMetadata = (endpointId: string, metadata: EndpointUpdatedWebrtcEventSchema.parse({ data: { id: endpointId, - metadata: { - peer: metadata, - }, + metadata, }, type: 'endpointUpdated', }); diff --git a/packages/ts-client/tests/methods/addTrackMethod.test.ts b/packages/ts-client/tests/methods/addTrackMethod.test.ts index bd1e0af1..27a24c80 100644 --- a/packages/ts-client/tests/methods/addTrackMethod.test.ts +++ b/packages/ts-client/tests/methods/addTrackMethod.test.ts @@ -61,25 +61,3 @@ it('Adding track before being accepted by the server throws error', async () => 'Cannot add tracks before being accepted by the server', ); }); - -it('Adding track with invalid metadata throws error', async () => { - // Given - type TrackMetadata = { validMetadata: true }; - - function trackMetadataParser(data: any): TrackMetadata { - if (!data?.trackMetadataParser) throw 'Invalid'; - return { validMetadata: true }; - } - - mockRTCPeerConnection(); - mockMediaStream(); - - const webRTCEndpoint = new WebRTCEndpoint({ trackMetadataParser }); - - // When - await expect(() => - webRTCEndpoint.addTrack(mockTrack, { - validMetadata: false, - } as unknown as TrackMetadata), - ).rejects.toThrow('Invalid'); -}); diff --git a/packages/ts-client/tests/methods/connectMethod.test.ts b/packages/ts-client/tests/methods/connectMethod.test.ts index cdf73c99..8b51423c 100644 --- a/packages/ts-client/tests/methods/connectMethod.test.ts +++ b/packages/ts-client/tests/methods/connectMethod.test.ts @@ -50,18 +50,3 @@ it("Method 'connect' sets metadata in local field", () => { // Then expect(webRTCEndpoint['local'].getEndpoint().metadata).toMatchObject(peerMetadata); }); - -it("Method 'connect' throws when metadata is incorrect", () => { - // Given - type EndpointMetadata = { validMetadata: true }; - function endpointMetadataParser(data: any): EndpointMetadata { - if (!data?.validMetadata) throw 'Invalid'; - return { validMetadata: true }; - } - const webRTCEndpoint = new WebRTCEndpoint({ endpointMetadataParser }); - - const peerMetadata = { validMetadata: false }; - - // Then - expect(() => webRTCEndpoint.connect(peerMetadata as unknown as EndpointMetadata)).toThrow('Invalid'); -}); diff --git a/packages/ts-client/tests/mocks.ts b/packages/ts-client/tests/mocks.ts index 9f251a58..aea060a7 100644 --- a/packages/ts-client/tests/mocks.ts +++ b/packages/ts-client/tests/mocks.ts @@ -33,8 +33,8 @@ export const mockRTCPeerConnection = (): { return { encodings: encodings } as RTCRtpSendParameters; }; - if (typeof trackOrKind !== 'string') { - sender.track = trackOrKind; + if (init?.direction === 'sendonly') { + sender.track = typeof trackOrKind !== 'string' ? trackOrKind : { id: 'someTrackId' }; // todo generate unique UUID } senders.push(sender); @@ -43,7 +43,7 @@ export const mockRTCPeerConnection = (): { const transceiver: RTCRtpTransceiver = { currentDirection: null, direction: init?.direction ?? 'inactive', - mid: null, + mid: 'someTransceiverMid', receiver: {} as RTCRtpReceiver, sender: sender, setCodecPreferences: (_codecs) => {}, @@ -67,6 +67,8 @@ export const mockRTCPeerConnection = (): { return senders; }, close: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, }; return newVar; }); diff --git a/packages/ts-client/tests/schema.ts b/packages/ts-client/tests/schema.ts index 7a53bb81..dc31b711 100644 --- a/packages/ts-client/tests/schema.ts +++ b/packages/ts-client/tests/schema.ts @@ -96,10 +96,7 @@ export type CustomSdpAnswerDataEvent = z.infer; export const EndpointUpdatedWebrtcEventSchema = z.object({ data: z.object({ id: z.string().min(1), - metadata: z.object({ - peer: z.any(), - server: z.any(), - }), + metadata: z.any(), }), type: z.literal('endpointUpdated'), }); diff --git a/packages/ts-client/tests/utils.ts b/packages/ts-client/tests/utils.ts index b464c9cd..36512a6f 100644 --- a/packages/ts-client/tests/utils.ts +++ b/packages/ts-client/tests/utils.ts @@ -59,9 +59,13 @@ export const setupRoomWithMocks = async ( const rtcTrackEvent: RTCTrackEvent = { streams: [stream], transceiver: transciever, + // @ts-expect-error + track: { + kind: 'video', + }, }; // @ts-ignore - connection.ontrack(rtcTrackEvent); + connection.getConnection().ontrack(rtcTrackEvent); return new Promise((resolve) => resolve()); }; From d5d8949fcc93a6541c6b13d7d9f6f9e5b470bbe5 Mon Sep 17 00:00:00 2001 From: Kamil Stasiak Date: Wed, 30 Oct 2024 17:19:21 +0100 Subject: [PATCH 2/4] WIP --- e2e-tests/ts-client/app/playwright.config.ts | 2 +- e2e-tests/ts-client/app/src/App.tsx | 5 - examples/ts-client/simple-app/src/main.ts | 75 ++++-------- .../src/hooks/useFishjamClientState.ts | 6 +- packages/react-client/src/hooks/usePeers.ts | 14 +-- .../react-client/src/hooks/useScreenShare.ts | 1 - .../react-client/src/hooks/useTrackManager.ts | 1 - packages/react-client/src/types/internal.ts | 8 +- packages/react-client/src/utils/track.ts | 22 ++-- packages/ts-client/src/FishjamClient.ts | 115 +++++++++++------- packages/ts-client/src/index.ts | 2 +- packages/ts-client/src/webrtc/internal.ts | 3 +- .../ts-client/src/webrtc/tracks/Remote.ts | 18 +-- .../ts-client/src/webrtc/webRTCEndpoint.ts | 4 +- .../events/encodingSwitchedEvent.test.ts | 6 +- .../tests/events/trackAddedEvent.test.ts | 2 +- packages/ts-client/tests/mocks.ts | 2 +- 17 files changed, 125 insertions(+), 161 deletions(-) diff --git a/e2e-tests/ts-client/app/playwright.config.ts b/e2e-tests/ts-client/app/playwright.config.ts index 6710ec3f..70533a27 100644 --- a/e2e-tests/ts-client/app/playwright.config.ts +++ b/e2e-tests/ts-client/app/playwright.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: 1, + workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ ["list"], diff --git a/e2e-tests/ts-client/app/src/App.tsx b/e2e-tests/ts-client/app/src/App.tsx index 81c62075..292a3dd2 100644 --- a/e2e-tests/ts-client/app/src/App.tsx +++ b/e2e-tests/ts-client/app/src/App.tsx @@ -75,20 +75,15 @@ class RemoteStore { } snapshot() { - console.log("Snapshot"); - const newTracks = webrtc.getRemoteTracks(); const newEndpoints = webrtc.getRemoteEndpoints(); const ids = Object.keys(newTracks).sort().join(":") + Object.keys(newEndpoints).sort().join(":"); if (!(ids in this.cache) || this.invalidateCache) { - console.log({ name: "Update cache", newEndpoints, newTracks }); this.cache[ids] = [newEndpoints, newTracks]; this.invalidateCache = false; } - console.log("Use cache"); - return this.cache[ids]; } } diff --git a/examples/ts-client/simple-app/src/main.ts b/examples/ts-client/simple-app/src/main.ts index 132e7e2e..9a1e6124 100644 --- a/examples/ts-client/simple-app/src/main.ts +++ b/examples/ts-client/simple-app/src/main.ts @@ -83,8 +83,7 @@ inputArray.forEach((input) => { }); }); -const TrackTypeValues = ["screensharing", "camera", "audio"] as const; -export type TrackType = (typeof TrackTypeValues)[number]; +export type TrackType = "screensharing" | "camera" | "audio"; export type PeerMetadata = { name: string; @@ -94,40 +93,7 @@ export type TrackMetadata = { active: boolean; }; -const isPeerMetadata = (input: unknown): input is PeerMetadata => { - return ( - typeof input === "object" && - input !== null && - "name" in input && - typeof input["name"] === "string" - ); -}; - -const isTrackType = (input: unknown): input is TrackType => - TrackTypeValues.includes(input as TrackType); - -const isTrackMetadata = (input: unknown): input is TrackMetadata => - typeof input === "object" && - input !== null && - "type" in input && - isTrackType(input.type) && - "active" in input && - typeof input.active === "boolean"; - -const trackMetadataParser = (input: unknown): TrackMetadata => { - if (isTrackMetadata(input)) return input; - throw Error("Invalid track metadata"); -}; - -const peerMetadataParser = (input: unknown): PeerMetadata => { - if (isPeerMetadata(input)) return input; - - throw Error("Invalid peer metadata"); -}; - const client: FishjamClient = new FishjamClient({ - peerMetadataParser, - trackMetadataParser, reconnect: true, }); @@ -157,34 +123,37 @@ client.on("trackAdded", (ctx) => { console.log({ name: "trackAdded", ctx }); }); -client.on("joined", (_peerId: string, peersInRoom: Peer[]) => { - console.log("Join success!"); - toastSuccess(`Joined room`); - const template = document.querySelector("#remote-peer-template-card")!; - const remotePeers = document.querySelector("#remote-peers")!; +client.on( + "joined", + (_peerId: string, peersInRoom: Peer[]) => { + console.log("Join success!"); + toastSuccess(`Joined room`); + const template = document.querySelector("#remote-peer-template-card")!; + const remotePeers = document.querySelector("#remote-peers")!; - (peersInRoom || []).forEach((peer: Peer) => { - // @ts-ignore - const clone = template.content.cloneNode(true); - const card = clone.firstElementChild; - card.dataset.peerId = peer.id; + (peersInRoom || []).forEach((peer: Peer) => { + // @ts-ignore + const clone = template.content.cloneNode(true); + const card = clone.firstElementChild; + card.dataset.peerId = peer.id; - const peerId = clone.querySelector(".remote-peer-template-id"); - peerId.innerHTML = peer.id; + const peerId = clone.querySelector(".remote-peer-template-id"); + peerId.innerHTML = peer.id; - clone.firstElementChild.dataset.peerId = peer.id; + clone.firstElementChild.dataset.peerId = peer.id; - document.querySelector(`div[data-peer-id="${peer.id}"`)?.remove(); - remotePeers.appendChild(clone); - }); -}); + document.querySelector(`div[data-peer-id="${peer.id}"`)?.remove(); + remotePeers.appendChild(clone); + }); + }, +); client.on("joinError", (metadata) => { console.log({ name: "joinError", metadata }); toastAlert("Join error"); }); -client.on("peerJoined", (peer: Peer) => { +client.on("peerJoined", (peer: Peer) => { console.log("Peer join success!"); const template = document.querySelector("#remote-peer-template-card")!; diff --git a/packages/react-client/src/hooks/useFishjamClientState.ts b/packages/react-client/src/hooks/useFishjamClientState.ts index 93aa11f8..1a14f3d9 100644 --- a/packages/react-client/src/hooks/useFishjamClientState.ts +++ b/packages/react-client/src/hooks/useFishjamClientState.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo, useRef, useSyncExternalStore } from "react"; -import type { Component, Endpoint, MessageEvents, Peer, FishjamClient } from "@fishjam-cloud/ts-client"; +import type { Component, MessageEvents, Peer, FishjamClient } from "@fishjam-cloud/ts-client"; import type { PeerId, PeerMetadata, TrackMetadata } from "../types/internal"; const eventNames = [ @@ -43,9 +43,9 @@ const eventNames = [ ] as const satisfies (keyof MessageEvents)[]; export interface FishjamClientState { - peers: Record; + peers: Record>; components: Record; - localPeer: Endpoint | null; + localPeer: Peer | null; isReconnecting: boolean; } diff --git a/packages/react-client/src/hooks/usePeers.ts b/packages/react-client/src/hooks/usePeers.ts index 01b594bc..7f5926f9 100644 --- a/packages/react-client/src/hooks/usePeers.ts +++ b/packages/react-client/src/hooks/usePeers.ts @@ -1,5 +1,5 @@ -import type { Component, Endpoint, Peer, TrackContext } from "@fishjam-cloud/ts-client"; -import type { FishjamPeerMetadata, PeerState, TrackId, TrackMetadata } from "../types/internal"; +import type { Component, Endpoint, FishjamTrackContext, Peer } from "@fishjam-cloud/ts-client"; +import type { PeerMetadata, PeerState, TrackId, TrackMetadata } from "../types/internal"; import type { PeerWithTracks, Track } from "../types/public"; import { useFishjamContext } from "./useFishjamContext"; @@ -14,9 +14,9 @@ function getPeerWithDistinguishedTracks(peerState: PeerState): PeerWithTracks { return { ...peerState, cameraTrack, microphoneTrack, screenShareVideoTrack, screenShareAudioTrack }; } -function trackContextToTrack(track: TrackContext): Track { +function trackContextToTrack(track: FishjamTrackContext): Track { return { - metadata: track.metadata as TrackMetadata, + metadata: track.metadata, trackId: track.trackId, stream: track.stream, simulcastConfig: track.simulcastConfig ?? null, @@ -26,14 +26,14 @@ function trackContextToTrack(track: TrackContext): Track { }; } -function endpointToPeerState(peer: Peer | Component | Endpoint): PeerState { +function endpointToPeerState(peer: Peer | Component | Endpoint): PeerState { const tracks = [...peer.tracks].reduce( - (acc, [, track]) => ({ ...acc, [track.trackId]: trackContextToTrack(track) }), + (acc, [, track]) => ({ ...acc, [track.trackId]: trackContextToTrack(track as FishjamTrackContext) }), {} as Record, ); return { - metadata: peer.metadata as FishjamPeerMetadata, + metadata: peer.metadata as Peer["metadata"], id: peer.id, tracks, }; diff --git a/packages/react-client/src/hooks/useScreenShare.ts b/packages/react-client/src/hooks/useScreenShare.ts index 32b13d1f..6e14a402 100644 --- a/packages/react-client/src/hooks/useScreenShare.ts +++ b/packages/react-client/src/hooks/useScreenShare.ts @@ -25,7 +25,6 @@ export const useScreenShareManager = ({ const stream = state.stream ?? null; const [videoTrack, audioTrack] = stream ? getTracksFromStream(stream) : [null, null]; - // @ts-ignore const getDisplayName = () => fishjamClient.getLocalPeer()?.metadata?.peer?.displayName; const startStreaming: ScreenshareApi["startStreaming"] = async (props) => { diff --git a/packages/react-client/src/hooks/useTrackManager.ts b/packages/react-client/src/hooks/useTrackManager.ts index bcc26235..6acc32c4 100644 --- a/packages/react-client/src/hooks/useTrackManager.ts +++ b/packages/react-client/src/hooks/useTrackManager.ts @@ -63,7 +63,6 @@ export const useTrackManager = ({ // see `getRemoteOrLocalTrackContext()` explanation setCurrentTrackId(media.track.id); - // @ts-ignore const displayName = tsClient.getLocalPeer()?.metadata?.peer?.displayName; const deviceType = getDeviceType(mediaManager); diff --git a/packages/react-client/src/types/internal.ts b/packages/react-client/src/types/internal.ts index bf0e364d..e032a776 100644 --- a/packages/react-client/src/types/internal.ts +++ b/packages/react-client/src/types/internal.ts @@ -1,20 +1,16 @@ import type { DeviceType } from "../DeviceManager"; import type { StartStreamingProps, Track, TrackMiddleware, TracksMiddleware } from "./public"; +import type { Peer } from "@fishjam-cloud/ts-client"; export type TrackId = string; export type PeerId = string; export type PeerState = { id: PeerId; - metadata?: FishjamPeerMetadata; + metadata?: Peer["metadata"]; tracks: Record; }; -export type FishjamPeerMetadata = { - peer: PeerMetadata; - server?: Record; -}; - export type PeerMetadata = { displayName?: string; }; diff --git a/packages/react-client/src/utils/track.ts b/packages/react-client/src/utils/track.ts index 781109a3..c0d47125 100644 --- a/packages/react-client/src/utils/track.ts +++ b/packages/react-client/src/utils/track.ts @@ -26,19 +26,15 @@ const getRemoteOrLocalTrackContext = ( return trackByLocalId ? trackByLocalId : null; }; -const getTrackFromContext = (context: TrackContext): Track => { - return { - // todo typescript client should parse this metadata - // @ts-ignore - metadata: context.metadata as TrackMetadata, // todo parse metadata - trackId: context.trackId, - stream: context.stream, - simulcastConfig: context.simulcastConfig || null, - encoding: context.encoding || null, - vadStatus: context.vadStatus, - track: context.track, - }; -}; +const getTrackFromContext = (context: TrackContext): Track => ({ + metadata: context.metadata as TrackMetadata, + trackId: context.trackId, + stream: context.stream, + simulcastConfig: context.simulcastConfig || null, + encoding: context.encoding || null, + vadStatus: context.vadStatus, + track: context.track, +}); export const getRemoteOrLocalTrack = ( tsClient: FishjamClient, diff --git a/packages/ts-client/src/FishjamClient.ts b/packages/ts-client/src/FishjamClient.ts index 4205a5a5..1e995bc1 100644 --- a/packages/ts-client/src/FishjamClient.ts +++ b/packages/ts-client/src/FishjamClient.ts @@ -1,11 +1,13 @@ import type { BandwidthLimit, Encoding, + EncodingReason, Endpoint, SerializedMediaEvent, SimulcastConfig, TrackBandwidthLimit, TrackContext, + VadStatus, WebRTCEndpointEvents, } from './webrtc'; import { WebRTCEndpoint } from './webrtc'; @@ -20,13 +22,37 @@ import { connectEventsHandler } from './connectEventsHandler'; const STATISTICS_INTERVAL = 10_000; -export type Peer = Endpoint & { type: 'webrtc' }; +interface MyTrackContextEvents { + encodingChanged: (context: FishjamTrackContext) => void; + voiceActivityChanged: (context: FishjamTrackContext) => void; +} + +export interface FishjamTrackContext extends TypedEmitter>> { + readonly track: MediaStreamTrack | null; + readonly stream: MediaStream | null; + readonly endpoint: Endpoint; + readonly trackId: string; + readonly simulcastConfig?: SimulcastConfig; + readonly metadata?: Metadata; + readonly maxBandwidth?: TrackBandwidthLimit; + readonly vadStatus: VadStatus; + readonly encoding?: Encoding; + readonly encodingReason?: EncodingReason; +} + +export type Peer = { + id: string; + type: string; + metadata?: PeerServerMetadata; + tracks: Map>; +}; export type Component = Omit & { type: 'recording' | 'hls' | 'file' | 'rtsp' | 'sip'; }; -const isPeer = (endpoint: Endpoint): endpoint is Peer => endpoint.type === 'webrtc' || endpoint.type === 'exwebrtc'; +const isPeer = (endpoint: Endpoint): endpoint is Peer => + endpoint.type === 'webrtc' || endpoint.type === 'exwebrtc'; const isComponent = (endpoint: Endpoint): endpoint is Component => endpoint.type === 'recording' || @@ -37,10 +63,15 @@ const isComponent = (endpoint: Endpoint): endpoint is Component => const WEBSOCKET_PATH = 'socket/peer/websocket'; +export type PeerServerMetadata = { + peer: PeerMetadata; + server?: Record; +}; + /** * Events emitted by the client with their arguments. */ -export interface MessageEvents { +export interface MessageEvents { /** * Emitted when connect method invoked * @@ -89,7 +120,7 @@ export interface MessageEvents { /** * Called when peer was accepted. */ - joined: (peerId: string, peers: Peer[], components: Component[]) => void; + joined: (peerId: string, peers: Peer[], components: Component[]) => void; /** * Called when peer was not accepted @@ -103,40 +134,40 @@ export interface MessageEvents { * This callback is always called after {@link MessageEvents.trackAdded}. * It informs user that data related to the given track arrives and can be played or displayed. */ - trackReady: (ctx: TrackContext) => void; + trackReady: (ctx: FishjamTrackContext) => void; /** * Called each time the peer which was already in the room, adds new track. Fields track and stream will be set to null. * These fields will be set to non-null value in {@link MessageEvents.trackReady} */ - trackAdded: (ctx: TrackContext) => void; + trackAdded: (ctx: FishjamTrackContext) => void; /** * Called when some track will no longer be sent. * * It will also be called before {@link MessageEvents.peerLeft} for each track of this peer. */ - trackRemoved: (ctx: TrackContext) => void; + trackRemoved: (ctx: FishjamTrackContext) => void; /** * Called each time peer has its track metadata updated. */ - trackUpdated: (ctx: TrackContext) => void; + trackUpdated: (ctx: FishjamTrackContext) => void; /** * Called each time new peer joins the room. */ - peerJoined: (peer: Peer) => void; + peerJoined: (peer: Peer) => void; /** * Called each time peer leaves the room. */ - peerLeft: (peer: Peer) => void; + peerLeft: (peer: Peer) => void; /** * Called each time peer has its metadata updated. */ - peerUpdated: (peer: Peer) => void; + peerUpdated: (peer: Peer) => void; /** * Called each time new peer joins the room. @@ -166,7 +197,10 @@ export interface MessageEvents { * @param enabledTracks - list of tracks which will be sent to client from SFU * @param disabledTracks - list of tracks which will not be sent to client from SFU */ - tracksPriorityChanged: (enabledTracks: TrackContext[], disabledTracks: TrackContext[]) => void; + tracksPriorityChanged: ( + enabledTracks: FishjamTrackContext[], + disabledTracks: FishjamTrackContext[], + ) => void; /** * Called every time the server estimates client's bandiwdth. @@ -205,17 +239,10 @@ export interface ConnectConfig { url: string; } -export type CreateConfig = { - peerMetadataParser?: MetadataParser; - trackMetadataParser?: MetadataParser; +export type CreateConfig = { reconnect?: ReconnectConfig | boolean; }; -export type FishjamMetadata = { - peer?: Metadata; - server?: Record; -}; - /** * FishjamClient is the main class to interact with Fishjam. * @@ -250,12 +277,9 @@ export type FishjamMetadata = { * }); * ``` */ - -export type MetadataParser = (rawMetadata: unknown) => ParsedMetadata; - export class FishjamClient extends (EventEmitter as { - new (): TypedEmitter>; -}) { + new (): TypedEmitter>>; +}) { private websocket: WebSocket | null = null; private webrtc: WebRTCEndpoint | null = null; private removeEventListeners: (() => void) | null = null; @@ -268,13 +292,8 @@ export class FishjamClient extends (EventEmitter as private sendStatisticsInterval: NodeJS.Timeout | undefined = undefined; - private readonly peerMetadataParser: MetadataParser; - private readonly trackMetadataParser: MetadataParser; - - constructor(config?: CreateConfig) { + constructor(config?: CreateConfig) { super(); - this.peerMetadataParser = config?.peerMetadataParser ?? ((x) => x as PeerMetadata); - this.trackMetadataParser = config?.trackMetadataParser ?? ((x) => x as TrackMetadata); this.reconnectManager = new ReconnectManager( this, (peerMetadata) => this.initConnection(peerMetadata), @@ -410,14 +429,14 @@ export class FishjamClient extends (EventEmitter as * client.setTargetTrackEncoding(trackId, encoding); * } */ - getRemoteTracks(): Readonly> { - return this.webrtc?.getRemoteTracks() ?? {}; + getRemoteTracks(): Readonly>> { + return (this.webrtc?.getRemoteTracks() as Record>) ?? {}; } /** * Returns a snapshot of currently received remote peers. */ - public getRemotePeers(): Record { + public getRemotePeers(): Record> { return Object.entries(this.webrtc?.getRemoteEndpoints() ?? {}).reduce( (acc, [id, peer]) => (isPeer(peer) ? { ...acc, [id]: peer } : acc), {}, @@ -431,8 +450,8 @@ export class FishjamClient extends (EventEmitter as ); } - public getLocalPeer(): Endpoint | null { - return this.webrtc?.getLocalEndpoint() || null; + public getLocalPeer(): Peer | null { + return (this.webrtc?.getLocalEndpoint() as Peer) || null; } public getBandwidthEstimation(): bigint { @@ -450,7 +469,9 @@ export class FishjamClient extends (EventEmitter as }); this.webrtc?.on('connected', async (peerId: string, endpointsInRoom: Endpoint[]) => { - const peers = endpointsInRoom.filter((endpoint) => isPeer(endpoint)).map((peer) => peer as Peer); + const peers = endpointsInRoom + .filter((endpoint) => isPeer(endpoint)) + .map((peer) => peer as Peer); const components = endpointsInRoom .filter((endpoint) => isComponent(endpoint)) @@ -470,7 +491,7 @@ export class FishjamClient extends (EventEmitter as }); this.webrtc?.on('endpointAdded', (endpoint: Endpoint) => { if (isPeer(endpoint)) { - this.emit('peerJoined', endpoint); + this.emit('peerJoined', endpoint as Peer); } if (isComponent(endpoint)) { this.emit('componentAdded', endpoint); @@ -478,7 +499,7 @@ export class FishjamClient extends (EventEmitter as }); this.webrtc?.on('endpointRemoved', (endpoint: Endpoint) => { if (isPeer(endpoint)) { - this.emit('peerLeft', endpoint); + this.emit('peerLeft', endpoint as Peer); } if (isComponent(endpoint)) { this.emit('componentRemoved', endpoint); @@ -486,7 +507,7 @@ export class FishjamClient extends (EventEmitter as }); this.webrtc?.on('endpointUpdated', (endpoint: Endpoint) => { if (isPeer(endpoint)) { - this.emit('peerUpdated', endpoint); + this.emit('peerUpdated', endpoint as Peer); } if (isComponent(endpoint)) { this.emit('componentUpdated', endpoint); @@ -495,26 +516,30 @@ export class FishjamClient extends (EventEmitter as this.webrtc?.on('trackReady', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; - this.emit('trackReady', ctx); + this.emit('trackReady', ctx as FishjamTrackContext); }); this.webrtc?.on('trackAdded', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; - this.emit('trackAdded', ctx); + this.emit('trackAdded', ctx as FishjamTrackContext); }); this.webrtc?.on('trackRemoved', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; - this.emit('trackRemoved', ctx); + this.emit('trackRemoved', ctx as FishjamTrackContext); ctx.removeAllListeners(); }); this.webrtc?.on('trackUpdated', (ctx: TrackContext) => { if (!isPeer(ctx.endpoint)) return; - this.emit('trackUpdated', ctx); + this.emit('trackUpdated', ctx as FishjamTrackContext); }); this.webrtc?.on('tracksPriorityChanged', (enabledTracks, disabledTracks) => { - this.emit('tracksPriorityChanged', enabledTracks, disabledTracks); + this.emit( + 'tracksPriorityChanged', + enabledTracks as FishjamTrackContext[], + disabledTracks as FishjamTrackContext[], + ); }); this.webrtc?.on('signalingError', (error) => { this.emit('joinError', error); diff --git a/packages/ts-client/src/index.ts b/packages/ts-client/src/index.ts index ef36934f..d0f262e2 100644 --- a/packages/ts-client/src/index.ts +++ b/packages/ts-client/src/index.ts @@ -1,4 +1,4 @@ -export type { Peer, Component, ConnectConfig, CreateConfig, MessageEvents } from './FishjamClient'; +export type { Peer, Component, ConnectConfig, CreateConfig, MessageEvents, FishjamTrackContext } from './FishjamClient'; export type { ReconnectConfig, ReconnectionStatus } from './reconnection'; diff --git a/packages/ts-client/src/webrtc/internal.ts b/packages/ts-client/src/webrtc/internal.ts index deec324c..65457df4 100644 --- a/packages/ts-client/src/webrtc/internal.ts +++ b/packages/ts-client/src/webrtc/internal.ts @@ -15,9 +15,8 @@ import type { export const isTrackKind = (kind: string): kind is TrackKind => kind === 'audio' || kind === 'video'; -// todo simplify type export class TrackContextImpl - extends (EventEmitter as { new (): TypedEmitter> }) + extends (EventEmitter as new () => TypedEmitter>) implements TrackContext { endpoint: Endpoint; diff --git a/packages/ts-client/src/webrtc/tracks/Remote.ts b/packages/ts-client/src/webrtc/tracks/Remote.ts index b7da7a04..e25a380b 100644 --- a/packages/ts-client/src/webrtc/tracks/Remote.ts +++ b/packages/ts-client/src/webrtc/tracks/Remote.ts @@ -87,8 +87,7 @@ export class Remote { tracks: new Map(), }; - // mutation in place - this.updateEndpointMetadata(newEndpoint, endpoint?.metadata); + newEndpoint.metadata = endpoint?.metadata; this.addEndpoint(newEndpoint); this.addTracks(newEndpoint.id, endpoint.tracks, endpoint.trackIdToMetadata); @@ -106,17 +105,11 @@ export class Remote { const endpoint: EndpointWithTrackContext | undefined = this.remoteEndpoints[data.id]; if (!endpoint) throw new Error(`Endpoint ${data.id} not found`); - // mutation in place - this.updateEndpointMetadata(endpoint, data.metadata); + endpoint.metadata = data.metadata; this.emit('endpointUpdated', endpoint); }; - // todo inline - private updateEndpointMetadata = (endpoint: EndpointWithTrackContext, metadata: unknown) => { - endpoint.metadata = metadata; - }; - public removeRemoteEndpoint = (endpointId: EndpointId) => { const endpoint: EndpointWithTrackContext | undefined = this.remoteEndpoints[endpointId]; if (!endpoint) throw new Error(`Endpoint ${endpointId} not found`); @@ -140,16 +133,11 @@ export class Remote { const remoteTrack = this.remoteTracks[trackId]; if (!remoteTrack) throw new Error(`Track ${trackId} not found`); - this.updateTrackMetadata(remoteTrack.trackContext, data.metadata); + remoteTrack.trackContext.metadata = data.metadata; this.emit('trackUpdated', remoteTrack.trackContext); }; - // todo inline - private updateTrackMetadata = (trackContext: TrackContextImpl, trackMetadata: unknown) => { - trackContext.metadata = trackMetadata; - }; - public disableRemoteTrackEncoding = (trackId: TrackId, encoding: Encoding) => { const remoteTrack = this.remoteTracks[trackId]; if (!remoteTrack) throw new Error(`Track ${trackId} not found`); diff --git a/packages/ts-client/src/webrtc/webRTCEndpoint.ts b/packages/ts-client/src/webrtc/webRTCEndpoint.ts index 5413bcd9..2700f3c3 100644 --- a/packages/ts-client/src/webrtc/webRTCEndpoint.ts +++ b/packages/ts-client/src/webrtc/webRTCEndpoint.ts @@ -24,9 +24,7 @@ import { ConnectionManager } from './ConnectionManager'; /** * Main class that is responsible for connecting to the RTC Engine, sending and receiving media. */ -export class WebRTCEndpoint extends (EventEmitter as { - new (): TypedEmitter>; -}) { +export class WebRTCEndpoint extends (EventEmitter as new () => TypedEmitter>) { private readonly localTrackManager: LocalTrackManager; private readonly remote: Remote; private readonly local: Local; diff --git a/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts b/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts index 0843fdd8..3086c3a7 100644 --- a/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts +++ b/packages/ts-client/tests/events/encodingSwitchedEvent.test.ts @@ -39,9 +39,9 @@ it('Changing track encoding when endpoint exist but track does not exist', () => // When const encodingUpdatedEvent = createEncodingSwitchedEvent(endpointId, notExistingTrackId, 'm'); - expect(() => webRTCEndpoint.receiveMediaEvent(JSON.stringify(encodingUpdatedEvent))) - .rejects // todo change this error in production code - .toThrow(`Track ${notExistingTrackId} not found`); + expect(() => webRTCEndpoint.receiveMediaEvent(JSON.stringify(encodingUpdatedEvent))).rejects.toThrow( + `Track ${notExistingTrackId} not found`, + ); }); it('Changing track encoding when endpoint does not exist but track exist in other endpoint', () => { diff --git a/packages/ts-client/tests/events/trackAddedEvent.test.ts b/packages/ts-client/tests/events/trackAddedEvent.test.ts index 89ff7889..8508167d 100644 --- a/packages/ts-client/tests/events/trackAddedEvent.test.ts +++ b/packages/ts-client/tests/events/trackAddedEvent.test.ts @@ -125,6 +125,6 @@ it('tracksAdded -> offerData with one track -> handle sdpAnswer data with one vi // Then const midToTrackId = webRTCEndpoint['local']['getMidToTrackId'](); - // TODO this function should return 1 if a user wants to add local track + // midToTrackId?.size should be undefined because the local peer doesn't offer anything expect(midToTrackId?.size).toBe(undefined); }); diff --git a/packages/ts-client/tests/mocks.ts b/packages/ts-client/tests/mocks.ts index aea060a7..1976cf2a 100644 --- a/packages/ts-client/tests/mocks.ts +++ b/packages/ts-client/tests/mocks.ts @@ -34,7 +34,7 @@ export const mockRTCPeerConnection = (): { }; if (init?.direction === 'sendonly') { - sender.track = typeof trackOrKind !== 'string' ? trackOrKind : { id: 'someTrackId' }; // todo generate unique UUID + sender.track = typeof trackOrKind !== 'string' ? trackOrKind : { id: 'someTrackId' }; } senders.push(sender); From 044f51486985bde6bb1759b6edcfa0ab0bc7016e Mon Sep 17 00:00:00 2001 From: Adrian Czerwiec Date: Thu, 7 Nov 2024 14:10:46 +0100 Subject: [PATCH 3/4] tidy up types and guards --- packages/react-client/src/hooks/useVAD.ts | 6 +- packages/ts-client/src/FishjamClient.ts | 232 +--------------------- packages/ts-client/src/guards.ts | 13 ++ packages/ts-client/src/index.ts | 2 +- packages/ts-client/src/reconnection.ts | 3 +- packages/ts-client/src/types.ts | 221 +++++++++++++++++++++ 6 files changed, 243 insertions(+), 234 deletions(-) create mode 100644 packages/ts-client/src/guards.ts create mode 100644 packages/ts-client/src/types.ts diff --git a/packages/react-client/src/hooks/useVAD.ts b/packages/react-client/src/hooks/useVAD.ts index b965b462..e81f4173 100644 --- a/packages/react-client/src/hooks/useVAD.ts +++ b/packages/react-client/src/hooks/useVAD.ts @@ -4,10 +4,6 @@ import { useFishjamContext } from "./useFishjamContext"; import type { TrackContext, VadStatus } from "@fishjam-cloud/ts-client"; import { useFishjamClientState } from "./useFishjamClientState"; -function isMicrophone(metadata: unknown) { - return typeof metadata === "object" && metadata !== null && "type" in metadata && metadata.type === "microphone"; -} - export const useVAD = (peerIds: PeerId[]): Record => { const { fishjamClientRef } = useFishjamContext(); const { peers } = useFishjamClientState(fishjamClientRef.current); @@ -18,7 +14,7 @@ export const useVAD = (peerIds: PeerId[]): Record => { .filter((peer) => peerIds.includes(peer.id)) .map((peer) => ({ peerId: peer.id, - microphoneTracks: Array.from(peer.tracks.values()).filter(({ metadata }) => isMicrophone(metadata)), + microphoneTracks: Array.from(peer.tracks.values()).filter(({ metadata }) => metadata?.type === "microphone"), })), [peers, peerIds], ); diff --git a/packages/ts-client/src/FishjamClient.ts b/packages/ts-client/src/FishjamClient.ts index 1e995bc1..207b5dd0 100644 --- a/packages/ts-client/src/FishjamClient.ts +++ b/packages/ts-client/src/FishjamClient.ts @@ -1,248 +1,26 @@ import type { BandwidthLimit, Encoding, - EncodingReason, Endpoint, SerializedMediaEvent, SimulcastConfig, TrackBandwidthLimit, TrackContext, - VadStatus, - WebRTCEndpointEvents, } from './webrtc'; import { WebRTCEndpoint } from './webrtc'; import type TypedEmitter from 'typed-emitter'; import { EventEmitter } from 'events'; import { PeerMessage } from './protos'; -import type { ReconnectConfig } from './reconnection'; import { ReconnectManager } from './reconnection'; -import type { AuthErrorReason } from './auth'; import { isAuthError } from './auth'; import { connectEventsHandler } from './connectEventsHandler'; +import { isPeer, isComponent } from './guards'; +import type { Component, ConnectConfig, CreateConfig, FishjamTrackContext, MessageEvents, Peer } from './types'; const STATISTICS_INTERVAL = 10_000; -interface MyTrackContextEvents { - encodingChanged: (context: FishjamTrackContext) => void; - voiceActivityChanged: (context: FishjamTrackContext) => void; -} - -export interface FishjamTrackContext extends TypedEmitter>> { - readonly track: MediaStreamTrack | null; - readonly stream: MediaStream | null; - readonly endpoint: Endpoint; - readonly trackId: string; - readonly simulcastConfig?: SimulcastConfig; - readonly metadata?: Metadata; - readonly maxBandwidth?: TrackBandwidthLimit; - readonly vadStatus: VadStatus; - readonly encoding?: Encoding; - readonly encodingReason?: EncodingReason; -} - -export type Peer = { - id: string; - type: string; - metadata?: PeerServerMetadata; - tracks: Map>; -}; - -export type Component = Omit & { - type: 'recording' | 'hls' | 'file' | 'rtsp' | 'sip'; -}; - -const isPeer = (endpoint: Endpoint): endpoint is Peer => - endpoint.type === 'webrtc' || endpoint.type === 'exwebrtc'; - -const isComponent = (endpoint: Endpoint): endpoint is Component => - endpoint.type === 'recording' || - endpoint.type === 'hls' || - endpoint.type === 'file' || - endpoint.type === 'rtsp' || - endpoint.type === 'sip'; - const WEBSOCKET_PATH = 'socket/peer/websocket'; -export type PeerServerMetadata = { - peer: PeerMetadata; - server?: Record; -}; - -/** - * Events emitted by the client with their arguments. - */ -export interface MessageEvents { - /** - * Emitted when connect method invoked - * - */ - connectionStarted: () => void; - - /** - * Emitted when the websocket connection is closed - * - * @param {CloseEvent} event - Close event object from the websocket - */ - socketClose: (event: CloseEvent) => void; - - /** - * Emitted when occurs an error in the websocket connection - * - * @param {Event} event - Event object from the websocket - */ - socketError: (event: Event) => void; - - /** - * Emitted when the websocket connection is opened - * - * @param {Event} event - Event object from the websocket - */ - socketOpen: (event: Event) => void; - - /** Emitted when authentication is successful */ - authSuccess: () => void; - - /** Emitted when authentication fails */ - authError: (reason: AuthErrorReason) => void; - - /** Emitted when the connection is closed */ - disconnected: () => void; - - /** Emitted when the process of reconnection starts */ - reconnectionStarted: () => void; - - /** Emitted on successful reconnection */ - reconnected: () => void; - - /** Emitted when the maximum number of reconnection retries is reached */ - reconnectionRetriesLimitReached: () => void; - - /** - * Called when peer was accepted. - */ - joined: (peerId: string, peers: Peer[], components: Component[]) => void; - - /** - * Called when peer was not accepted - * @param metadata - Pass through for client application to communicate further actions to frontend - */ - joinError: (metadata: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any - - /** - * Called when data in a new track arrives. - * - * This callback is always called after {@link MessageEvents.trackAdded}. - * It informs user that data related to the given track arrives and can be played or displayed. - */ - trackReady: (ctx: FishjamTrackContext) => void; - - /** - * Called each time the peer which was already in the room, adds new track. Fields track and stream will be set to null. - * These fields will be set to non-null value in {@link MessageEvents.trackReady} - */ - trackAdded: (ctx: FishjamTrackContext) => void; - - /** - * Called when some track will no longer be sent. - * - * It will also be called before {@link MessageEvents.peerLeft} for each track of this peer. - */ - trackRemoved: (ctx: FishjamTrackContext) => void; - - /** - * Called each time peer has its track metadata updated. - */ - trackUpdated: (ctx: FishjamTrackContext) => void; - - /** - * Called each time new peer joins the room. - */ - peerJoined: (peer: Peer) => void; - - /** - * Called each time peer leaves the room. - */ - peerLeft: (peer: Peer) => void; - - /** - * Called each time peer has its metadata updated. - */ - peerUpdated: (peer: Peer) => void; - - /** - * Called each time new peer joins the room. - */ - componentAdded: (peer: Component) => void; - - /** - * Called each time peer leaves the room. - */ - componentRemoved: (peer: Component) => void; - - /** - * Called each time peer has its metadata updated. - */ - componentUpdated: (peer: Component) => void; - - /** - * Called in case of errors related to multimedia session e.g. ICE connection. - */ - connectionError: (error: { message: string; event?: Event }) => void; - - /** - * Currently, this callback is only invoked when DisplayManager in RTC Engine is - * enabled and simulcast is disabled. - * - * Called when priority of video tracks have changed. - * @param enabledTracks - list of tracks which will be sent to client from SFU - * @param disabledTracks - list of tracks which will not be sent to client from SFU - */ - tracksPriorityChanged: ( - enabledTracks: FishjamTrackContext[], - disabledTracks: FishjamTrackContext[], - ) => void; - - /** - * Called every time the server estimates client's bandiwdth. - * - * @param {bigint} estimation - client's available incoming bitrate estimated - * by the server. It's measured in bits per second. - */ - bandwidthEstimationChanged: (estimation: bigint) => void; - - targetTrackEncodingRequested: (event: Parameters[0]) => void; - localTrackAdded: (event: Parameters[0]) => void; - localTrackRemoved: (event: Parameters[0]) => void; - localTrackReplaced: (event: Parameters[0]) => void; - localTrackMuted: (event: Parameters[0]) => void; - localTrackUnmuted: (event: Parameters[0]) => void; - localTrackBandwidthSet: (event: Parameters[0]) => void; - localTrackEncodingBandwidthSet: ( - event: Parameters[0], - ) => void; - localTrackEncodingEnabled: (event: Parameters[0]) => void; - localTrackEncodingDisabled: (event: Parameters[0]) => void; - localPeerMetadataChanged: (event: Parameters[0]) => void; - localTrackMetadataChanged: (event: Parameters[0]) => void; - disconnectRequested: (event: Parameters[0]) => void; -} - -/** Configuration object for the client */ -export interface ConnectConfig { - /** Metadata for the peer */ - peerMetadata: PeerMetadata; - - /** Token for authentication */ - token: string; - - /** Fishjam url */ - url: string; -} - -export type CreateConfig = { - reconnect?: ReconnectConfig | boolean; -}; - /** * FishjamClient is the main class to interact with Fishjam. * @@ -278,7 +56,7 @@ export type CreateConfig = { * ``` */ export class FishjamClient extends (EventEmitter as { - new (): TypedEmitter>>; + new (): TypedEmitter>; }) { private websocket: WebSocket | null = null; private webrtc: WebRTCEndpoint | null = null; @@ -624,7 +402,7 @@ export class FishjamClient extends (EventEmitter as */ public on>( event: E, - listener: Required>[E], + listener: MessageEvents[E], ): this { return super.on(event, listener); } @@ -647,7 +425,7 @@ export class FishjamClient extends (EventEmitter as */ public off>( event: E, - listener: Required>[E], + listener: MessageEvents[E], ): this { return super.off(event, listener); } diff --git a/packages/ts-client/src/guards.ts b/packages/ts-client/src/guards.ts new file mode 100644 index 00000000..a66b2627 --- /dev/null +++ b/packages/ts-client/src/guards.ts @@ -0,0 +1,13 @@ +import { Peer, Component } from './types'; +import { Endpoint } from './webrtc'; + +export const isPeer = ( + endpoint: Endpoint, +): endpoint is Peer => endpoint.type === 'webrtc' || endpoint.type === 'exwebrtc'; + +export const isComponent = (endpoint: Endpoint): endpoint is Component => + endpoint.type === 'recording' || + endpoint.type === 'hls' || + endpoint.type === 'file' || + endpoint.type === 'rtsp' || + endpoint.type === 'sip'; diff --git a/packages/ts-client/src/index.ts b/packages/ts-client/src/index.ts index d0f262e2..0776015a 100644 --- a/packages/ts-client/src/index.ts +++ b/packages/ts-client/src/index.ts @@ -1,4 +1,4 @@ -export type { Peer, Component, ConnectConfig, CreateConfig, MessageEvents, FishjamTrackContext } from './FishjamClient'; +export type { Peer, Component, ConnectConfig, CreateConfig, MessageEvents, FishjamTrackContext } from './types'; export type { ReconnectConfig, ReconnectionStatus } from './reconnection'; diff --git a/packages/ts-client/src/reconnection.ts b/packages/ts-client/src/reconnection.ts index 5ea5eb38..1549e9ff 100644 --- a/packages/ts-client/src/reconnection.ts +++ b/packages/ts-client/src/reconnection.ts @@ -1,6 +1,7 @@ import type { Endpoint } from './webrtc'; -import type { FishjamClient, MessageEvents } from './FishjamClient'; +import type { FishjamClient } from './FishjamClient'; import { isAuthError } from './auth'; +import { MessageEvents } from './types'; export type ReconnectionStatus = 'reconnecting' | 'idle' | 'error'; diff --git a/packages/ts-client/src/types.ts b/packages/ts-client/src/types.ts new file mode 100644 index 00000000..204aa88d --- /dev/null +++ b/packages/ts-client/src/types.ts @@ -0,0 +1,221 @@ +import type TypedEmitter from 'typed-emitter'; +import type { + Endpoint, + SimulcastConfig, + TrackBandwidthLimit, + VadStatus, + EncodingReason, + Encoding, + WebRTCEndpointEvents, +} from './webrtc'; +import { AuthErrorReason } from './auth'; +import { ReconnectConfig } from './reconnection'; + +export type PeerServerMetadata = { + peer: PeerMetadata; + server?: Record; +}; + +type TrackContextEvents = { + encodingChanged: (context: FishjamTrackContext) => void; + voiceActivityChanged: (context: FishjamTrackContext) => void; +}; + +export interface FishjamTrackContext extends TypedEmitter> { + readonly track: MediaStreamTrack | null; + readonly stream: MediaStream | null; + readonly endpoint: Endpoint; + readonly trackId: string; + readonly simulcastConfig?: SimulcastConfig; + readonly metadata?: Metadata; + readonly maxBandwidth?: TrackBandwidthLimit; + readonly vadStatus: VadStatus; + readonly encoding?: Encoding; + readonly encodingReason?: EncodingReason; +} + +export type Peer = { + id: string; + type: string; + metadata?: PeerServerMetadata; + tracks: Map>; +}; + +export type Component = Omit & { + type: 'recording' | 'hls' | 'file' | 'rtsp' | 'sip'; +}; + +/** + * Events emitted by the client with their arguments. + */ +export type MessageEvents = { + /** + * Emitted when connect method invoked + * + */ + connectionStarted: () => void; + + /** + * Emitted when the websocket connection is closed + * + * @param {CloseEvent} event - Close event object from the websocket + */ + socketClose: (event: CloseEvent) => void; + + /** + * Emitted when occurs an error in the websocket connection + * + * @param {Event} event - Event object from the websocket + */ + socketError: (event: Event) => void; + + /** + * Emitted when the websocket connection is opened + * + * @param {Event} event - Event object from the websocket + */ + socketOpen: (event: Event) => void; + + /** Emitted when authentication is successful */ + authSuccess: () => void; + + /** Emitted when authentication fails */ + authError: (reason: AuthErrorReason) => void; + + /** Emitted when the connection is closed */ + disconnected: () => void; + + /** Emitted when the process of reconnection starts */ + reconnectionStarted: () => void; + + /** Emitted on successful reconnection */ + reconnected: () => void; + + /** Emitted when the maximum number of reconnection retries is reached */ + reconnectionRetriesLimitReached: () => void; + + /** + * Called when peer was accepted. + */ + joined: (peerId: string, peers: Peer[], components: Component[]) => void; + + /** + * Called when peer was not accepted + * @param metadata - Pass through for client application to communicate further actions to frontend + */ + joinError: (metadata: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any + + /** + * Called when data in a new track arrives. + * + * This callback is always called after {@link MessageEvents.trackAdded}. + * It informs user that data related to the given track arrives and can be played or displayed. + */ + trackReady: (ctx: FishjamTrackContext) => void; + + /** + * Called each time the peer which was already in the room, adds new track. Fields track and stream will be set to null. + * These fields will be set to non-null value in {@link MessageEvents.trackReady} + */ + trackAdded: (ctx: FishjamTrackContext) => void; + + /** + * Called when some track will no longer be sent. + * + * It will also be called before {@link MessageEvents.peerLeft} for each track of this peer. + */ + trackRemoved: (ctx: FishjamTrackContext) => void; + + /** + * Called each time peer has its track metadata updated. + */ + trackUpdated: (ctx: FishjamTrackContext) => void; + + /** + * Called each time new peer joins the room. + */ + peerJoined: (peer: Peer) => void; + + /** + * Called each time peer leaves the room. + */ + peerLeft: (peer: Peer) => void; + + /** + * Called each time peer has its metadata updated. + */ + peerUpdated: (peer: Peer) => void; + + /** + * Called each time new peer joins the room. + */ + componentAdded: (peer: Component) => void; + + /** + * Called each time peer leaves the room. + */ + componentRemoved: (peer: Component) => void; + + /** + * Called each time peer has its metadata updated. + */ + componentUpdated: (peer: Component) => void; + + /** + * Called in case of errors related to multimedia session e.g. ICE connection. + */ + connectionError: (error: { message: string; event?: Event }) => void; + + /** + * Currently, this callback is only invoked when DisplayManager in RTC Engine is + * enabled and simulcast is disabled. + * + * Called when priority of video tracks have changed. + * @param enabledTracks - list of tracks which will be sent to client from SFU + * @param disabledTracks - list of tracks which will not be sent to client from SFU + */ + tracksPriorityChanged: ( + enabledTracks: FishjamTrackContext[], + disabledTracks: FishjamTrackContext[], + ) => void; + + /** + * Called every time the server estimates client's bandiwdth. + * + * @param {bigint} estimation - client's available incoming bitrate estimated + * by the server. It's measured in bits per second. + */ + bandwidthEstimationChanged: (estimation: bigint) => void; + + targetTrackEncodingRequested: (event: Parameters[0]) => void; + localTrackAdded: (event: Parameters[0]) => void; + localTrackRemoved: (event: Parameters[0]) => void; + localTrackReplaced: (event: Parameters[0]) => void; + localTrackMuted: (event: Parameters[0]) => void; + localTrackUnmuted: (event: Parameters[0]) => void; + localTrackBandwidthSet: (event: Parameters[0]) => void; + localTrackEncodingBandwidthSet: ( + event: Parameters[0], + ) => void; + localTrackEncodingEnabled: (event: Parameters[0]) => void; + localTrackEncodingDisabled: (event: Parameters[0]) => void; + localPeerMetadataChanged: (event: Parameters[0]) => void; + localTrackMetadataChanged: (event: Parameters[0]) => void; + disconnectRequested: (event: Parameters[0]) => void; +}; + +/** Configuration object for the client */ +export interface ConnectConfig { + /** Metadata for the peer */ + peerMetadata: PeerMetadata; + + /** Token for authentication */ + token: string; + + /** Fishjam url */ + url: string; +} + +export type CreateConfig = { + reconnect?: ReconnectConfig | boolean; +}; From 834cea90b1f24ba7475d79fe4edd1e4dd8d374a6 Mon Sep 17 00:00:00 2001 From: Adrian Czerwiec Date: Thu, 7 Nov 2024 14:21:48 +0100 Subject: [PATCH 4/4] lint --- packages/ts-client/src/guards.ts | 4 ++-- packages/ts-client/src/reconnection.ts | 2 +- packages/ts-client/src/types.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ts-client/src/guards.ts b/packages/ts-client/src/guards.ts index a66b2627..72bd497b 100644 --- a/packages/ts-client/src/guards.ts +++ b/packages/ts-client/src/guards.ts @@ -1,5 +1,5 @@ -import { Peer, Component } from './types'; -import { Endpoint } from './webrtc'; +import type { Peer, Component } from './types'; +import type { Endpoint } from './webrtc'; export const isPeer = ( endpoint: Endpoint, diff --git a/packages/ts-client/src/reconnection.ts b/packages/ts-client/src/reconnection.ts index 1549e9ff..49f6b955 100644 --- a/packages/ts-client/src/reconnection.ts +++ b/packages/ts-client/src/reconnection.ts @@ -1,7 +1,7 @@ import type { Endpoint } from './webrtc'; import type { FishjamClient } from './FishjamClient'; import { isAuthError } from './auth'; -import { MessageEvents } from './types'; +import type { MessageEvents } from './types'; export type ReconnectionStatus = 'reconnecting' | 'idle' | 'error'; diff --git a/packages/ts-client/src/types.ts b/packages/ts-client/src/types.ts index 204aa88d..b201c134 100644 --- a/packages/ts-client/src/types.ts +++ b/packages/ts-client/src/types.ts @@ -8,8 +8,8 @@ import type { Encoding, WebRTCEndpointEvents, } from './webrtc'; -import { AuthErrorReason } from './auth'; -import { ReconnectConfig } from './reconnection'; +import type { AuthErrorReason } from './auth'; +import type { ReconnectConfig } from './reconnection'; export type PeerServerMetadata = { peer: PeerMetadata;