From 68a4052d8ed2ba26684064bbda4d96b60f9e40a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Wed, 18 Mar 2026 14:18:22 +0100 Subject: [PATCH 01/17] Added api `stats()` method --- ts/smelter-core/src/api.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ts/smelter-core/src/api.ts b/ts/smelter-core/src/api.ts index fe488cd90..f881d5e30 100644 --- a/ts/smelter-core/src/api.ts +++ b/ts/smelter-core/src/api.ts @@ -31,6 +31,10 @@ export type RegisterOutputResponse = { endpoint_route?: string; }; +export type StatsResponse = { + stats_json?: string; +}; + export class ApiClient { private serverManager: SmelterManager; @@ -151,4 +155,12 @@ export class ApiClient { body: {}, }); } + + public async stats(): Promise { + return this.serverManager.sendRequest({ + method: 'GET', + route: `/stats`, + body: {}, + }) + } } From fe6a26083ea69d5bbaf3ba25eba0ab98c63b98fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Wed, 18 Mar 2026 14:31:23 +0100 Subject: [PATCH 02/17] Added `.stats()` method to `Smelter` class --- ts/smelter-core/src/live/compositor.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ts/smelter-core/src/live/compositor.ts b/ts/smelter-core/src/live/compositor.ts index d428333e6..5bf2028c3 100644 --- a/ts/smelter-core/src/live/compositor.ts +++ b/ts/smelter-core/src/live/compositor.ts @@ -1,6 +1,6 @@ import type { Renderers } from '@swmansion/smelter'; import { _smelterInternals } from '@swmansion/smelter'; -import type { RegisterInputResponse, RegisterOutputResponse } from '../api'; +import type { RegisterInputResponse, RegisterOutputResponse, StatsResponse } from '../api'; import { ApiClient } from '../api'; import Output from './output'; import type { SmelterManager } from '../smelterManager'; @@ -172,4 +172,8 @@ export class Smelter { this.logger.debug({ event }, 'New event received'); handleEvent(this.store, this.outputs, event); } + + public async stats(): Promise { + return this.api.stats(); + } } From 33bb68535658f6ac6c6647527cc2ca314b5f813f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Wed, 18 Mar 2026 15:08:40 +0100 Subject: [PATCH 03/17] Added `Smelter.stats()` wrappers to packages --- ts/examples/node-examples/src/audio.tsx | 11 +++++++++++ ts/smelter-core/src/api.ts | 1 - ts/smelter-core/src/index.ts | 2 +- ts/smelter-core/src/offline/compositor.ts | 6 +++++- ts/smelter-node/src/api.ts | 2 ++ ts/smelter-node/src/live/compositor.ts | 8 +++++++- ts/smelter-node/src/offline/compositor.ts | 8 +++++++- ts/smelter-web-client/src/api.ts | 2 ++ ts/smelter-web-client/src/smelter/live.ts | 8 +++++++- ts/smelter-web-client/src/smelter/offline.ts | 8 +++++++- ts/smelter-web-wasm/src/compositor/compositor.ts | 9 ++++++++- 11 files changed, 57 insertions(+), 8 deletions(-) diff --git a/ts/examples/node-examples/src/audio.tsx b/ts/examples/node-examples/src/audio.tsx index 066f6f1e5..8a3202c88 100644 --- a/ts/examples/node-examples/src/audio.tsx +++ b/ts/examples/node-examples/src/audio.tsx @@ -4,6 +4,10 @@ import { downloadAllAssets, ffplayStartRtmpServerAsync } from './utils'; import path from 'path'; import { useState, useEffect } from 'react'; +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + function ExampleApp() { const [streamWithAudio, setStream] = useState('input_1'); useEffect(() => { @@ -85,5 +89,12 @@ async function run() { }); await smelter.start(); + + while (true) { + await sleep(1000); + console.clear(); + let json_string = await smelter.stats(); + console.dir(json_string, { depth: null }); + } } void run(); diff --git a/ts/smelter-core/src/api.ts b/ts/smelter-core/src/api.ts index f881d5e30..f6e7053f7 100644 --- a/ts/smelter-core/src/api.ts +++ b/ts/smelter-core/src/api.ts @@ -160,7 +160,6 @@ export class ApiClient { return this.serverManager.sendRequest({ method: 'GET', route: `/stats`, - body: {}, }) } } diff --git a/ts/smelter-core/src/index.ts b/ts/smelter-core/src/index.ts index f0401e31f..119220cf9 100644 --- a/ts/smelter-core/src/index.ts +++ b/ts/smelter-core/src/index.ts @@ -2,7 +2,7 @@ import * as Output from './api/output'; import * as Input from './api/input'; export { Output, Input }; -export { ApiClient, ApiRequest, MultipartRequest, RegisterInputResponse } from './api'; +export { ApiClient, ApiRequest, MultipartRequest, RegisterInputResponse, StatsResponse } from './api'; export { Smelter } from './live/compositor'; export { OfflineSmelter } from './offline/compositor'; export { SmelterManager, SetupInstanceOptions } from './smelterManager'; diff --git a/ts/smelter-core/src/offline/compositor.ts b/ts/smelter-core/src/offline/compositor.ts index e452e8c97..aa0cde0d6 100644 --- a/ts/smelter-core/src/offline/compositor.ts +++ b/ts/smelter-core/src/offline/compositor.ts @@ -1,6 +1,6 @@ import type { Renderers } from '@swmansion/smelter'; import { _smelterInternals } from '@swmansion/smelter'; -import type { RegisterInputResponse } from '../api'; +import type { RegisterInputResponse, StatsResponse } from '../api'; import { ApiClient } from '../api'; import type { SmelterManager } from '../smelterManager'; import type { RegisterOutput } from '../api/output'; @@ -147,4 +147,8 @@ export class OfflineSmelter { throw new Error('Render was already started.'); } } + + public async stats(): Promise { + return this.api.stats(); + } } diff --git a/ts/smelter-node/src/api.ts b/ts/smelter-node/src/api.ts index 8586dbbab..80192064a 100644 --- a/ts/smelter-node/src/api.ts +++ b/ts/smelter-node/src/api.ts @@ -44,3 +44,5 @@ export type RegisterWhipServerInputResponse = { bearerToken: string; endpointRoute: string; }; + +export type { StatsResponse } from '@swmansion/smelter-core'; diff --git a/ts/smelter-node/src/live/compositor.ts b/ts/smelter-node/src/live/compositor.ts index 96f396196..003f1915e 100644 --- a/ts/smelter-node/src/live/compositor.ts +++ b/ts/smelter-node/src/live/compositor.ts @@ -3,7 +3,7 @@ import FormData from 'form-data'; import fetch from 'node-fetch'; import type { Renderers } from '@swmansion/smelter'; import type { SmelterManager } from '@swmansion/smelter-core'; -import { StateGuard, Smelter as CoreSmelter } from '@swmansion/smelter-core'; +import { StateGuard, Smelter as CoreSmelter, type StatsResponse } from '@swmansion/smelter-core'; import LocallySpawnedInstance from '../manager/locallySpawnedInstance'; import { createLogger } from '../logger'; @@ -179,4 +179,10 @@ export default class Smelter { await this.coreSmelter.terminate(); }); } + + public async stats(): Promise { + return await this.scheduler.run(async () => { + return this.coreSmelter.stats(); + }); + } } diff --git a/ts/smelter-node/src/offline/compositor.ts b/ts/smelter-node/src/offline/compositor.ts index ec789fc20..de9e394ee 100644 --- a/ts/smelter-node/src/offline/compositor.ts +++ b/ts/smelter-node/src/offline/compositor.ts @@ -2,7 +2,7 @@ import fetch from 'node-fetch'; import FormData from 'form-data'; import type { ReactElement } from 'react'; import type { SmelterManager } from '@swmansion/smelter-core'; -import { OfflineSmelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; +import { OfflineSmelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; import type { Renderers } from '@swmansion/smelter'; import type { @@ -105,4 +105,10 @@ export default class OfflineSmelter { }); }); } + + public async stats(): Promise { + return await this.scheduler.run(async () => { + return this.coreSmelter.stats(); + }); + } } diff --git a/ts/smelter-web-client/src/api.ts b/ts/smelter-web-client/src/api.ts index 1dd37997d..3635bd5ce 100644 --- a/ts/smelter-web-client/src/api.ts +++ b/ts/smelter-web-client/src/api.ts @@ -44,3 +44,5 @@ export type RegisterWhipServerInputResponse = { bearerToken: string; endpointRoute: string; }; + +export type { StatsResponse } from '@swmansion/smelter-core'; diff --git a/ts/smelter-web-client/src/smelter/live.ts b/ts/smelter-web-client/src/smelter/live.ts index 18ac7d112..d1645fe5b 100644 --- a/ts/smelter-web-client/src/smelter/live.ts +++ b/ts/smelter-web-client/src/smelter/live.ts @@ -1,7 +1,7 @@ import type { ReactElement } from 'react'; import { pino } from 'pino'; import type { Renderers } from '@swmansion/smelter'; -import { Smelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; +import { Smelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; import type { RegisterInput, RegisterMp4InputResponse, @@ -183,4 +183,10 @@ export default class Smelter { await this.coreSmelter.terminate(); }); } + + public async stats(): Promise { + return await this.scheduler.run(async () => { + return this.coreSmelter.stats(); + }); + } } diff --git a/ts/smelter-web-client/src/smelter/offline.ts b/ts/smelter-web-client/src/smelter/offline.ts index 72a71fda2..a53c204c3 100644 --- a/ts/smelter-web-client/src/smelter/offline.ts +++ b/ts/smelter-web-client/src/smelter/offline.ts @@ -1,5 +1,5 @@ import type { ReactElement } from 'react'; -import { OfflineSmelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; +import { OfflineSmelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; import type { Renderers } from '@swmansion/smelter'; import { pino } from 'pino'; import type { @@ -109,4 +109,10 @@ export default class OfflineSmelter { }); }); } + + public async stats(): Promise { + return await this.scheduler.run(async () => { + return this.coreSmelter.stats(); + }); + } } diff --git a/ts/smelter-web-wasm/src/compositor/compositor.ts b/ts/smelter-web-wasm/src/compositor/compositor.ts index 1c640a0df..706bbe5da 100644 --- a/ts/smelter-web-wasm/src/compositor/compositor.ts +++ b/ts/smelter-web-wasm/src/compositor/compositor.ts @@ -1,4 +1,4 @@ -import { Smelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; +import { Smelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; import type { ReactElement } from 'react'; import type { Logger } from 'pino'; import { pino } from 'pino'; @@ -168,6 +168,13 @@ export default class Smelter { await this.coreSmelter?.terminate(); }); } + + public async stats(): Promise { + return await this.scheduler.run(async () => { + assert(this.coreSmelter); + return this.coreSmelter.stats(); + }); + } } function resolveFramerate(framerate?: number | Framerate): Framerate { From 6c1fdab4edb32268475aec2822971c301a9e5f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Wed, 18 Mar 2026 17:01:55 +0100 Subject: [PATCH 04/17] Added `JsonSchema` trait to stats reports --- Cargo.lock | 1 + smelter-core/Cargo.toml | 1 + smelter-core/src/stats/input_reports.rs | 27 ++++++++++++------------ smelter-core/src/stats/mod.rs | 3 ++- smelter-core/src/stats/output_reports.rs | 27 ++++++++++++------------ 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc8c7933e..1d16c718b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4924,6 +4924,7 @@ dependencies = [ "reqwest", "rtmp", "rubato", + "schemars", "serde", "serde_json", "smelter-render", diff --git a/smelter-core/Cargo.toml b/smelter-core/Cargo.toml index 728969f0c..517041923 100644 --- a/smelter-core/Cargo.toml +++ b/smelter-core/Cargo.toml @@ -36,6 +36,7 @@ glyphon = { workspace = true } tokio = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } +schemars = { workspace = true } utoipa = { workspace = true } axum = { version = "0.7.7", features = ["macros"] } tower-http = { workspace = true } diff --git a/smelter-core/src/stats/input_reports.rs b/smelter-core/src/stats/input_reports.rs index 5ad865b36..cce1bb795 100644 --- a/smelter-core/src/stats/input_reports.rs +++ b/smelter-core/src/stats/input_reports.rs @@ -1,7 +1,8 @@ +use schemars::JsonSchema; use serde::Serialize; use utoipa::ToSchema; -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] #[serde(rename_all = "snake_case")] pub enum InputStatsReport { Rtp(RtpInputStatsReport), @@ -12,25 +13,25 @@ pub enum InputStatsReport { Mp4(Mp4InputStatsReport), } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtpInputStatsReport { pub video_rtp: RtpJitterBufferStatsReport, pub audio_rtp: RtpJitterBufferStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct WhipInputStatsReport { pub video_rtp: RtpJitterBufferStatsReport, pub audio_rtp: RtpJitterBufferStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct WhepInputStatsReport { pub video_rtp: RtpJitterBufferStatsReport, pub audio_rtp: RtpJitterBufferStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtpJitterBufferStatsReport { pub packets_lost: u64, pub packets_received: u64, @@ -40,7 +41,7 @@ pub struct RtpJitterBufferStatsReport { pub last_10_seconds: RtpJitterBufferSlidingWindowStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtpJitterBufferSlidingWindowStatsReport { pub packets_lost: u64, pub packets_received: u64, @@ -57,31 +58,31 @@ pub struct RtpJitterBufferSlidingWindowStatsReport { pub input_buffer_min_seconds: f64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtmpInputStatsReport { pub video: RtmpInputTrackStatsReport, pub audio: RtmpInputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtmpInputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct Mp4InputStatsReport { pub video: Mp4InputTrackStatsReport, pub audio: Mp4InputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct Mp4InputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct HlsInputStatsReport { pub video: HlsInputTrackStatsReport, pub audio: HlsInputTrackStatsReport, @@ -89,7 +90,7 @@ pub struct HlsInputStatsReport { pub corrupted_packets_received_last_10_seconds: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct HlsInputTrackStatsReport { pub packets_received: u64, pub discontinuities_detected: u32, @@ -99,7 +100,7 @@ pub struct HlsInputTrackStatsReport { pub last_10_seconds: HlsInputTrackSlidingWindowStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct HlsInputTrackSlidingWindowStatsReport { pub packets_received: u64, pub discontinuities_detected: u32, diff --git a/smelter-core/src/stats/mod.rs b/smelter-core/src/stats/mod.rs index eb8273ac7..4c18e7c5b 100644 --- a/smelter-core/src/stats/mod.rs +++ b/smelter-core/src/stats/mod.rs @@ -5,6 +5,7 @@ use std::{ }; use crossbeam_channel::{Receiver, Sender, TrySendError, bounded}; +use schemars::JsonSchema; use serde::Serialize; use tracing::warn; use utoipa::ToSchema; @@ -24,7 +25,7 @@ pub(crate) use input::*; pub(crate) use output::*; pub(crate) use state::StatsEvent; -#[derive(Debug, Serialize, Clone, ToSchema)] +#[derive(Debug, Serialize, Clone, JsonSchema, ToSchema)] pub struct StatsReport { pub inputs: BTreeMap, pub outputs: BTreeMap, diff --git a/smelter-core/src/stats/output_reports.rs b/smelter-core/src/stats/output_reports.rs index 3be4c2c5d..4bd8d4dc8 100644 --- a/smelter-core/src/stats/output_reports.rs +++ b/smelter-core/src/stats/output_reports.rs @@ -1,7 +1,8 @@ +use schemars::JsonSchema; use serde::Serialize; use utoipa::ToSchema; -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] #[serde(rename_all = "snake_case")] pub enum OutputStatsReport { Whep(WhepOutputStatsReport), @@ -12,75 +13,75 @@ pub enum OutputStatsReport { Rtp(RtpOutputStatsReport), } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct WhepOutputStatsReport { pub video: WhepOutputTrackStatsReport, pub audio: WhepOutputTrackStatsReport, pub connected_peers: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct WhepOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct WhipOutputStatsReport { pub video: WhipOutputTrackStatsReport, pub audio: WhipOutputTrackStatsReport, pub is_connected: bool, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct WhipOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct HlsOutputStatsReport { pub video: HlsOutputTrackStatsReport, pub audio: HlsOutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct HlsOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct Mp4OutputStatsReport { pub video: Mp4OutputTrackStatsReport, pub audio: Mp4OutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct Mp4OutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtmpOutputStatsReport { pub video: RtmpOutputTrackStatsReport, pub audio: RtmpOutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtmpOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtpOutputStatsReport { pub video: RtpOutputTrackStatsReport, pub audio: RtpOutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] pub struct RtpOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, From a7d2fb5cb0c809ee0f2856808b3fe8adff2fe279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Wed, 18 Mar 2026 17:18:47 +0100 Subject: [PATCH 05/17] Added `StatsReport` to `api.types` json schema --- smelter-core/src/stats/input_reports.rs | 28 ++++++++++++------------ smelter-core/src/stats/mod.rs | 4 ++-- smelter-core/src/stats/output_reports.rs | 28 ++++++++++++------------ tools/src/bin/generate_from_types.rs | 2 ++ 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/smelter-core/src/stats/input_reports.rs b/smelter-core/src/stats/input_reports.rs index cce1bb795..c7421af8f 100644 --- a/smelter-core/src/stats/input_reports.rs +++ b/smelter-core/src/stats/input_reports.rs @@ -1,8 +1,8 @@ use schemars::JsonSchema; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] #[serde(rename_all = "snake_case")] pub enum InputStatsReport { Rtp(RtpInputStatsReport), @@ -13,25 +13,25 @@ pub enum InputStatsReport { Mp4(Mp4InputStatsReport), } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtpInputStatsReport { pub video_rtp: RtpJitterBufferStatsReport, pub audio_rtp: RtpJitterBufferStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct WhipInputStatsReport { pub video_rtp: RtpJitterBufferStatsReport, pub audio_rtp: RtpJitterBufferStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct WhepInputStatsReport { pub video_rtp: RtpJitterBufferStatsReport, pub audio_rtp: RtpJitterBufferStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtpJitterBufferStatsReport { pub packets_lost: u64, pub packets_received: u64, @@ -41,7 +41,7 @@ pub struct RtpJitterBufferStatsReport { pub last_10_seconds: RtpJitterBufferSlidingWindowStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtpJitterBufferSlidingWindowStatsReport { pub packets_lost: u64, pub packets_received: u64, @@ -58,31 +58,31 @@ pub struct RtpJitterBufferSlidingWindowStatsReport { pub input_buffer_min_seconds: f64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtmpInputStatsReport { pub video: RtmpInputTrackStatsReport, pub audio: RtmpInputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtmpInputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct Mp4InputStatsReport { pub video: Mp4InputTrackStatsReport, pub audio: Mp4InputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct Mp4InputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct HlsInputStatsReport { pub video: HlsInputTrackStatsReport, pub audio: HlsInputTrackStatsReport, @@ -90,7 +90,7 @@ pub struct HlsInputStatsReport { pub corrupted_packets_received_last_10_seconds: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct HlsInputTrackStatsReport { pub packets_received: u64, pub discontinuities_detected: u32, @@ -100,7 +100,7 @@ pub struct HlsInputTrackStatsReport { pub last_10_seconds: HlsInputTrackSlidingWindowStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct HlsInputTrackSlidingWindowStatsReport { pub packets_received: u64, pub discontinuities_detected: u32, diff --git a/smelter-core/src/stats/mod.rs b/smelter-core/src/stats/mod.rs index 4c18e7c5b..1fcc26b6c 100644 --- a/smelter-core/src/stats/mod.rs +++ b/smelter-core/src/stats/mod.rs @@ -6,7 +6,7 @@ use std::{ use crossbeam_channel::{Receiver, Sender, TrySendError, bounded}; use schemars::JsonSchema; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use tracing::warn; use utoipa::ToSchema; @@ -25,7 +25,7 @@ pub(crate) use input::*; pub(crate) use output::*; pub(crate) use state::StatsEvent; -#[derive(Debug, Serialize, Clone, JsonSchema, ToSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, ToSchema)] pub struct StatsReport { pub inputs: BTreeMap, pub outputs: BTreeMap, diff --git a/smelter-core/src/stats/output_reports.rs b/smelter-core/src/stats/output_reports.rs index 4bd8d4dc8..b267f2607 100644 --- a/smelter-core/src/stats/output_reports.rs +++ b/smelter-core/src/stats/output_reports.rs @@ -1,8 +1,8 @@ use schemars::JsonSchema; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] #[serde(rename_all = "snake_case")] pub enum OutputStatsReport { Whep(WhepOutputStatsReport), @@ -13,75 +13,75 @@ pub enum OutputStatsReport { Rtp(RtpOutputStatsReport), } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct WhepOutputStatsReport { pub video: WhepOutputTrackStatsReport, pub audio: WhepOutputTrackStatsReport, pub connected_peers: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct WhepOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct WhipOutputStatsReport { pub video: WhipOutputTrackStatsReport, pub audio: WhipOutputTrackStatsReport, pub is_connected: bool, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct WhipOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct HlsOutputStatsReport { pub video: HlsOutputTrackStatsReport, pub audio: HlsOutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct HlsOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct Mp4OutputStatsReport { pub video: Mp4OutputTrackStatsReport, pub audio: Mp4OutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct Mp4OutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtmpOutputStatsReport { pub video: RtmpOutputTrackStatsReport, pub audio: RtmpOutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtmpOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtpOutputStatsReport { pub video: RtpOutputTrackStatsReport, pub audio: RtpOutputTrackStatsReport, } -#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct RtpOutputTrackStatsReport { pub bitrate_1_second: u64, pub bitrate_1_minute: u64, diff --git a/tools/src/bin/generate_from_types.rs b/tools/src/bin/generate_from_types.rs index 62f5b55e4..9ba2b9a4c 100644 --- a/tools/src/bin/generate_from_types.rs +++ b/tools/src/bin/generate_from_types.rs @@ -30,6 +30,8 @@ enum ApiTypes { RegisterWebRenderer(smelter_api::WebRendererSpec), RegisterShader(smelter_api::ShaderSpec), UpdateOutput(Box), + + StatsReport(smelter_core::stats::StatsReport), } pub fn generate_json_schema(check_flag: bool) { From 23de1d8e3186f8b1c827d69a7bd3fe18304b8598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Wed, 18 Mar 2026 17:38:21 +0100 Subject: [PATCH 06/17] Regenerated ts api types from new schema --- ts/smelter/src/api.generated.ts | 185 +++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 1 deletion(-) diff --git a/ts/smelter/src/api.generated.ts b/ts/smelter/src/api.generated.ts index d4118409a..dfa8a5d6f 100644 --- a/ts/smelter/src/api.generated.ts +++ b/ts/smelter/src/api.generated.ts @@ -8,7 +8,14 @@ /** * This enum is used to generate JSON schema for all API types. This prevents repeating types in generated schema. */ -export type ApiTypes = RegisterInput | RegisterOutput | ImageSpec | WebRendererSpec | ShaderSpec | UpdateOutputRequest; +export type ApiTypes = + | RegisterInput + | RegisterOutput + | ImageSpec + | WebRendererSpec + | ShaderSpec + | UpdateOutputRequest + | StatsReport; export type RegisterInput = | { type: "rtp_stream"; @@ -1285,6 +1292,44 @@ export type WebEmbeddingMethod = | "chromium_embedding" | "native_embedding_over_content" | "native_embedding_under_content"; +export type InputStatsReport = + | { + rtp: RtpInputStatsReport; + } + | { + whip: WhipInputStatsReport; + } + | { + whep: WhepInputStatsReport; + } + | { + hls: HlsInputStatsReport; + } + | { + rtmp: RtmpInputStatsReport; + } + | { + mp4: Mp4InputStatsReport; + }; +export type OutputStatsReport = + | { + whep: WhepOutputStatsReport; + } + | { + whip: WhipOutputStatsReport; + } + | { + hls: HlsOutputStatsReport; + } + | { + mp4: Mp4OutputStatsReport; + } + | { + rtmp: RtmpOutputStatsReport; + } + | { + rtp: RtpOutputStatsReport; + }; export interface InputRtpVideoOptions { decoder: RtpVideoDecoderOptions; @@ -1624,3 +1669,141 @@ export interface UpdateOutputRequest { audio?: AudioScene | null; schedule_time_ms?: number | null; } +export interface StatsReport { + inputs: { + [k: string]: InputStatsReport; + }; + outputs: { + [k: string]: OutputStatsReport; + }; +} +export interface RtpInputStatsReport { + video_rtp: RtpJitterBufferStatsReport; + audio_rtp: RtpJitterBufferStatsReport; +} +export interface RtpJitterBufferStatsReport { + packets_lost: number; + packets_received: number; + bitrate_1_second: number; + bitrate_1_minute: number; + last_10_seconds: RtpJitterBufferSlidingWindowStatsReport; +} +export interface RtpJitterBufferSlidingWindowStatsReport { + packets_lost: number; + packets_received: number; + /** + * Measured when packet leaves jitter buffer. This value represents how much time packet has to reach the queue to be processed. + */ + effective_buffer_avg_seconds: number; + effective_buffer_max_seconds: number; + effective_buffer_min_seconds: number; + /** + * Size of the InputBuffer + */ + input_buffer_avg_seconds: number; + input_buffer_max_seconds: number; + input_buffer_min_seconds: number; +} +export interface WhipInputStatsReport { + video_rtp: RtpJitterBufferStatsReport; + audio_rtp: RtpJitterBufferStatsReport; +} +export interface WhepInputStatsReport { + video_rtp: RtpJitterBufferStatsReport; + audio_rtp: RtpJitterBufferStatsReport; +} +export interface HlsInputStatsReport { + video: HlsInputTrackStatsReport; + audio: HlsInputTrackStatsReport; + corrupted_packets_received: number; + corrupted_packets_received_last_10_seconds: number; +} +export interface HlsInputTrackStatsReport { + packets_received: number; + discontinuities_detected: number; + bitrate_1_second: number; + bitrate_1_minute: number; + last_10_seconds: HlsInputTrackSlidingWindowStatsReport; +} +export interface HlsInputTrackSlidingWindowStatsReport { + packets_received: number; + discontinuities_detected: number; + /** + * Measured when packet leaves jitter buffer. This value represents how much time packet has to reach the queue to be processed. + */ + effective_buffer_avg_seconds: number; + effective_buffer_max_seconds: number; + effective_buffer_min_seconds: number; + /** + * Size of the InputBuffer + */ + input_buffer_avg_seconds: number; + input_buffer_max_seconds: number; + input_buffer_min_seconds: number; +} +export interface RtmpInputStatsReport { + video: RtmpInputTrackStatsReport; + audio: RtmpInputTrackStatsReport; +} +export interface RtmpInputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} +export interface Mp4InputStatsReport { + video: Mp4InputTrackStatsReport; + audio: Mp4InputTrackStatsReport; +} +export interface Mp4InputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} +export interface WhepOutputStatsReport { + video: WhepOutputTrackStatsReport; + audio: WhepOutputTrackStatsReport; + connected_peers: number; +} +export interface WhepOutputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} +export interface WhipOutputStatsReport { + video: WhipOutputTrackStatsReport; + audio: WhipOutputTrackStatsReport; + is_connected: boolean; +} +export interface WhipOutputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} +export interface HlsOutputStatsReport { + video: HlsOutputTrackStatsReport; + audio: HlsOutputTrackStatsReport; +} +export interface HlsOutputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} +export interface Mp4OutputStatsReport { + video: Mp4OutputTrackStatsReport; + audio: Mp4OutputTrackStatsReport; +} +export interface Mp4OutputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} +export interface RtmpOutputStatsReport { + video: RtmpOutputTrackStatsReport; + audio: RtmpOutputTrackStatsReport; +} +export interface RtmpOutputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} +export interface RtpOutputStatsReport { + video: RtpOutputTrackStatsReport; + audio: RtpOutputTrackStatsReport; +} +export interface RtpOutputTrackStatsReport { + bitrate_1_second: number; + bitrate_1_minute: number; +} From b144b7e538abba2b8e84b538f52f9c02102b1e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Wed, 18 Mar 2026 18:17:09 +0100 Subject: [PATCH 07/17] WIP: Adding types to ts-sdk stats --- ts/smelter/src/types/stats.ts | 0 ts/smelter/src/types/stats/input.ts | 89 ++++++++++++++++++++++++++++ ts/smelter/src/types/stats/output.ts | 0 3 files changed, 89 insertions(+) create mode 100644 ts/smelter/src/types/stats.ts create mode 100644 ts/smelter/src/types/stats/input.ts create mode 100644 ts/smelter/src/types/stats/output.ts diff --git a/ts/smelter/src/types/stats.ts b/ts/smelter/src/types/stats.ts new file mode 100644 index 000000000..e69de29bb diff --git a/ts/smelter/src/types/stats/input.ts b/ts/smelter/src/types/stats/input.ts new file mode 100644 index 000000000..a631a52bd --- /dev/null +++ b/ts/smelter/src/types/stats/input.ts @@ -0,0 +1,89 @@ +export type RtpInputStatsReport = { + videoRtp: string; + audioRtp: string; +}; + +export type RtpJitterBufferStatsReport = { + packetsLost: number; + packetsReceived: number; + bitrate1Second: number; + bitrate1Minute: number; + last10Seconds: RtpJitterBufferSlidingWindowStatsReport; +}; + +export type RtpJitterBufferSlidingWindowStatsReport = { + packetsLost: number; + packetsReceived: number; + /** + * Measured when packet leaves jitter buffer. This value represents how much time packet has to reach the queue to be processed. + */ + effectiveBufferAvgSeconds: number; + effectiveBufferMaxSeconds: number; + effectiveBufferMinSeconds: number; + /** + * Size of the InputBuffer + */ + inputBufferAvgSeconds: number; + inputBufferMaxSeconds: number; + inputBufferMinSeconds: number; +} + +export type WhipInputStatsReport = { + videoRtp: RtpJitterBufferStatsReport; + audioRtp: RtpJitterBufferStatsReport; +} +export type WhepInputStatsReport = { + videoRtp: RtpJitterBufferStatsReport; + audioRtp: RtpJitterBufferStatsReport; +} + +export type HlsInputStatsReport = { + video: HlsInputTrackStatsReport; + audio: HlsInputTrackStatsReport; + corruptedPacketsReceived: number; + corruptedPacketsReceivedLast10Seconds: number; +} + +export type HlsInputTrackStatsReport = { + packetsReceived: number; + discontinuitiesDetected: number; + bitrate1Second: number; + bitrate1Minute: number; + last10Seconds: HlsInputTrackSlidingWindowStatsReport; +} + +export type HlsInputTrackSlidingWindowStatsReport = { + packetsReceived: number; + discontinuitiesDetected: number; + /** + * Measured when packet leaves jitter buffer. This value represents how much time packet has to reach the queue to be processed. + */ + effectiveBufferAvgSeconds: number; + effectiveBufferMaxSeconds: number; + effectiveBufferMinSeconds: number; + /** + * Size of the InputBuffer + */ + inputBufferAvgSeconds: number; + inputBufferMaxSeconds: number; + inputBufferMinSeconds: number; +} + +export type RtmpInputStatsReport = { + video: RtmpInputTrackStatsReport; + audio: RtmpInputTrackStatsReport; +} +export type RtmpInputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +} + +export type Mp4InputStatsReport = { + video: Mp4InputTrackStatsReport; + audio: Mp4InputTrackStatsReport; +}; + +export type Mp4InputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +} diff --git a/ts/smelter/src/types/stats/output.ts b/ts/smelter/src/types/stats/output.ts new file mode 100644 index 000000000..e69de29bb From a69167201659ad0521cbba3964179209c88f616f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Thu, 19 Mar 2026 15:06:33 +0100 Subject: [PATCH 08/17] Added types for output stats --- ts/smelter-core/src/api.ts | 2 +- ts/smelter-core/src/index.ts | 8 +- ts/smelter-node/src/live/compositor.ts | 8 +- ts/smelter-node/src/offline/compositor.ts | 8 +- ts/smelter-web-client/src/smelter/offline.ts | 6 +- ts/smelter/src/api.generated.ts | 2278 +++++++++--------- ts/smelter/src/types/stats/input.ts | 18 +- ts/smelter/src/types/stats/output.ts | 50 + 8 files changed, 1223 insertions(+), 1155 deletions(-) diff --git a/ts/smelter-core/src/api.ts b/ts/smelter-core/src/api.ts index f6e7053f7..d3bf7c06b 100644 --- a/ts/smelter-core/src/api.ts +++ b/ts/smelter-core/src/api.ts @@ -160,6 +160,6 @@ export class ApiClient { return this.serverManager.sendRequest({ method: 'GET', route: `/stats`, - }) + }); } } diff --git a/ts/smelter-core/src/index.ts b/ts/smelter-core/src/index.ts index 119220cf9..4b56b9940 100644 --- a/ts/smelter-core/src/index.ts +++ b/ts/smelter-core/src/index.ts @@ -2,7 +2,13 @@ import * as Output from './api/output'; import * as Input from './api/input'; export { Output, Input }; -export { ApiClient, ApiRequest, MultipartRequest, RegisterInputResponse, StatsResponse } from './api'; +export { + ApiClient, + ApiRequest, + MultipartRequest, + RegisterInputResponse, + StatsResponse, +} from './api'; export { Smelter } from './live/compositor'; export { OfflineSmelter } from './offline/compositor'; export { SmelterManager, SetupInstanceOptions } from './smelterManager'; diff --git a/ts/smelter-node/src/live/compositor.ts b/ts/smelter-node/src/live/compositor.ts index 003f1915e..08b7e8553 100644 --- a/ts/smelter-node/src/live/compositor.ts +++ b/ts/smelter-node/src/live/compositor.ts @@ -2,8 +2,12 @@ import type { ReactElement } from 'react'; import FormData from 'form-data'; import fetch from 'node-fetch'; import type { Renderers } from '@swmansion/smelter'; -import type { SmelterManager } from '@swmansion/smelter-core'; -import { StateGuard, Smelter as CoreSmelter, type StatsResponse } from '@swmansion/smelter-core'; +import type { + SmelterManager, + StateGuard, + Smelter as CoreSmelter, + type StatsResponse, +} from '@swmansion/smelter-core'; import LocallySpawnedInstance from '../manager/locallySpawnedInstance'; import { createLogger } from '../logger'; diff --git a/ts/smelter-node/src/offline/compositor.ts b/ts/smelter-node/src/offline/compositor.ts index de9e394ee..56d37d0ec 100644 --- a/ts/smelter-node/src/offline/compositor.ts +++ b/ts/smelter-node/src/offline/compositor.ts @@ -1,8 +1,12 @@ import fetch from 'node-fetch'; import FormData from 'form-data'; import type { ReactElement } from 'react'; -import type { SmelterManager } from '@swmansion/smelter-core'; -import { OfflineSmelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; +import type { + SmelterManager, + OfflineSmelter as CoreSmelter, + StateGuard, + type StatsResponse, +} from '@swmansion/smelter-core'; import type { Renderers } from '@swmansion/smelter'; import type { diff --git a/ts/smelter-web-client/src/smelter/offline.ts b/ts/smelter-web-client/src/smelter/offline.ts index a53c204c3..23666d763 100644 --- a/ts/smelter-web-client/src/smelter/offline.ts +++ b/ts/smelter-web-client/src/smelter/offline.ts @@ -1,5 +1,9 @@ import type { ReactElement } from 'react'; -import { OfflineSmelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; +import { + OfflineSmelter as CoreSmelter, + StateGuard, + type StatsResponse, +} from '@swmansion/smelter-core'; import type { Renderers } from '@swmansion/smelter'; import { pino } from 'pino'; import type { diff --git a/ts/smelter/src/api.generated.ts b/ts/smelter/src/api.generated.ts index dfa8a5d6f..9b0283add 100644 --- a/ts/smelter/src/api.generated.ts +++ b/ts/smelter/src/api.generated.ts @@ -18,239 +18,239 @@ export type ApiTypes = | StatsReport; export type RegisterInput = | { - type: "rtp_stream"; - /** - * UDP port or port range on which the compositor should listen for the stream. - */ - port: PortOrPortRange; - /** - * Transport protocol. - */ - transport_protocol?: TransportProtocol | null; - /** - * Parameters of a video source included in the RTP stream. - */ - video?: InputRtpVideoOptions | null; - /** - * Parameters of an audio source included in the RTP stream. - */ - audio?: InputRtpAudioOptions | null; - /** - * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. - */ - required?: boolean | null; - /** - * Offset in milliseconds relative to the pipeline start (start request). If the offset is not defined then the stream will be synchronized based on the delivery time of the initial frames. - */ - offset_ms?: number | null; - } - | { - type: "rtmp_server"; - /** - * The RTMP application name. This is the first path segment of the RTMP stream URL that Smelter listens on for incoming streams. Format: `rtmp://://://Added in v0.4.0 - */ - loop?: boolean | null; - /** - * (**default=`false`**) If input is required and frames are not processed on time, then Smelter will delay producing output frames. - */ - required?: boolean | null; - /** - * Offset in milliseconds relative to the pipeline start (start request). If offset is not defined then stream is synchronized based on the first frames delivery time. - */ - offset_ms?: number | null; - /** - * Start playing from a specific timestamp in milliseconds. If loop is enabled after first iteration is done it will start from the beginning. - */ - seek_ms?: number | null; - /** - * Assigns which decoder should be used for media encoded with a specific codec. - */ - decoder_map?: { - [k: string]: Mp4VideoDecoderOptions; - } | null; - } - | { - type: "whip_server"; - /** - * Parameters of a video source included in the RTP stream. - */ - video?: InputWhipVideoOptions | null; - /** - * Token used for authentication in WHIP protocol. If not provided, the random value will be generated and returned in the response. - */ - bearer_token?: string | null; - /** - * Internal use only. Overrides whip endpoint id which is used when referencing the input via whip server. If not provided, it defaults to input id. - */ - endpoint_override?: string | null; - /** - * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. - */ - required?: boolean | null; - /** - * Offset in milliseconds relative to the pipeline start (start request). If the offset is not defined then the stream will be synchronized based on the delivery time of the initial frames. - */ - offset_ms?: number | null; - } - | { - type: "whep_client"; - /** - * WHEP server endpoint URL - */ - endpoint_url: string; - /** - * Optional Bearer token for auth - */ - bearer_token?: string | null; - /** - * Parameters of a video source included in the RTP stream. - */ - video?: InputWhepVideoOptions | null; - /** - * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. - */ - required?: boolean | null; - /** - * Offset in milliseconds relative to the pipeline start (start request). - */ - offset_ms?: number | null; - } - | { - type: "hls"; - /** - * URL to HLS playlist - */ - url: string; - /** - * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. - */ - required?: boolean | null; - /** - * Offset in milliseconds relative to the pipeline start (start request). If the offset is not defined then the stream will be synchronized based on the delivery time of the initial frames. - */ - offset_ms?: number | null; - /** - * Assigns which decoder should be used for media encoded with a specific codec. - */ - decoder_map?: { - [k: string]: HlsVideoDecoderOptions; - } | null; - } - | { - type: "v4l2"; - /** - * Path to the V4L2 device. - * - * Typically looks like either of: - `/dev/video[N]`, where `[N]` is the OS-assigned device number - `/dev/v4l/by-id/[ID]`, where `[ID]` is the unique device id - `/dev/v4l/by-path/[PATH]`, where `[PATH]` is the PCI/USB device path - * - * While the numbers assigned in `/dev/video` paths can differ depending on device detection order, the `by-id` paths are always the same for a given device, and the `by-path` paths should be the same for specific ports. - */ - path: string; - /** - * The format that will be negotiated with the device. - */ - format: V4L2InputFormat; - /** - * The requested resolution that will be negotiated with the device. If not provided, the input will use the default resolution for the given format. - */ - resolution?: Resolution | null; - /** - * The framerate that will be negotiated with the device. - * - * Must be either an unsigned integer, or a string in the \"NUM/DEN\" format, where NUM and DEN are both unsigned integers. If not provided, the input will use the default framerate for the given format and resolution. - */ - framerate?: Framerate | null; - /** - * (**default=`false`**) If input is required and frames are not processed on time, then Smelter will delay producing output frames. - */ - required?: boolean | null; - } - | { - type: "decklink"; - /** - * Single DeckLink device can consist of multiple sub-devices. This field defines index of sub-device that should be used. - * - * The input device is selected based on fields `subdevice_index`, `persistent_id` **AND** `display_name`. All of them need to match the device if they are specified. If nothing is matched, the error response will list available devices. - */ - subdevice_index?: number | null; - /** - * Select sub-device to use based on the display name. This is the value you see in e.g. Blackmagic Media Express app. like "DeckLink Quad HDMI Recorder (3)" - * - * The input device is selected based on fields `subdevice_index`, `persistent_id` **AND** `display_name`. All of them need to match the device if they are specified. If nothing is matched, the error response will list available devices. - */ - display_name?: string | null; - /** - * Persistent ID of a device represented by 32-bit hex number. Each DeckLink sub-device has a separate id. - * - * The input device is selected based on fields `subdevice_index`, `persistent_id` **AND** `display_name`. All of them need to match the device if they are specified. If nothing is matched, the error response will list available devices. - */ - persistent_id?: string | null; - /** - * (**default=`true`**) Enable audio support. - */ - enable_audio?: boolean | null; - /** - * (**default=`false`**) If input is required and frames are not processed on time, then Smelter will delay producing output frames. - */ - required?: boolean | null; - }; + type: "rtp_stream"; + /** + * UDP port or port range on which the compositor should listen for the stream. + */ + port: PortOrPortRange; + /** + * Transport protocol. + */ + transport_protocol?: TransportProtocol | null; + /** + * Parameters of a video source included in the RTP stream. + */ + video?: InputRtpVideoOptions | null; + /** + * Parameters of an audio source included in the RTP stream. + */ + audio?: InputRtpAudioOptions | null; + /** + * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. + */ + required?: boolean | null; + /** + * Offset in milliseconds relative to the pipeline start (start request). If the offset is not defined then the stream will be synchronized based on the delivery time of the initial frames. + */ + offset_ms?: number | null; + } + | { + type: "rtmp_server"; + /** + * The RTMP application name. This is the first path segment of the RTMP stream URL that Smelter listens on for incoming streams. Format: `rtmp://://://Added in v0.4.0 + */ + loop?: boolean | null; + /** + * (**default=`false`**) If input is required and frames are not processed on time, then Smelter will delay producing output frames. + */ + required?: boolean | null; + /** + * Offset in milliseconds relative to the pipeline start (start request). If offset is not defined then stream is synchronized based on the first frames delivery time. + */ + offset_ms?: number | null; + /** + * Start playing from a specific timestamp in milliseconds. If loop is enabled after first iteration is done it will start from the beginning. + */ + seek_ms?: number | null; + /** + * Assigns which decoder should be used for media encoded with a specific codec. + */ + decoder_map?: { + [k: string]: Mp4VideoDecoderOptions; + } | null; + } + | { + type: "whip_server"; + /** + * Parameters of a video source included in the RTP stream. + */ + video?: InputWhipVideoOptions | null; + /** + * Token used for authentication in WHIP protocol. If not provided, the random value will be generated and returned in the response. + */ + bearer_token?: string | null; + /** + * Internal use only. Overrides whip endpoint id which is used when referencing the input via whip server. If not provided, it defaults to input id. + */ + endpoint_override?: string | null; + /** + * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. + */ + required?: boolean | null; + /** + * Offset in milliseconds relative to the pipeline start (start request). If the offset is not defined then the stream will be synchronized based on the delivery time of the initial frames. + */ + offset_ms?: number | null; + } + | { + type: "whep_client"; + /** + * WHEP server endpoint URL + */ + endpoint_url: string; + /** + * Optional Bearer token for auth + */ + bearer_token?: string | null; + /** + * Parameters of a video source included in the RTP stream. + */ + video?: InputWhepVideoOptions | null; + /** + * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. + */ + required?: boolean | null; + /** + * Offset in milliseconds relative to the pipeline start (start request). + */ + offset_ms?: number | null; + } + | { + type: "hls"; + /** + * URL to HLS playlist + */ + url: string; + /** + * (**default=`false`**) If input is required and the stream is not delivered on time, then Smelter will delay producing output frames. + */ + required?: boolean | null; + /** + * Offset in milliseconds relative to the pipeline start (start request). If the offset is not defined then the stream will be synchronized based on the delivery time of the initial frames. + */ + offset_ms?: number | null; + /** + * Assigns which decoder should be used for media encoded with a specific codec. + */ + decoder_map?: { + [k: string]: HlsVideoDecoderOptions; + } | null; + } + | { + type: "v4l2"; + /** + * Path to the V4L2 device. + * + * Typically looks like either of: - `/dev/video[N]`, where `[N]` is the OS-assigned device number - `/dev/v4l/by-id/[ID]`, where `[ID]` is the unique device id - `/dev/v4l/by-path/[PATH]`, where `[PATH]` is the PCI/USB device path + * + * While the numbers assigned in `/dev/video` paths can differ depending on device detection order, the `by-id` paths are always the same for a given device, and the `by-path` paths should be the same for specific ports. + */ + path: string; + /** + * The format that will be negotiated with the device. + */ + format: V4L2InputFormat; + /** + * The requested resolution that will be negotiated with the device. If not provided, the input will use the default resolution for the given format. + */ + resolution?: Resolution | null; + /** + * The framerate that will be negotiated with the device. + * + * Must be either an unsigned integer, or a string in the \"NUM/DEN\" format, where NUM and DEN are both unsigned integers. If not provided, the input will use the default framerate for the given format and resolution. + */ + framerate?: Framerate | null; + /** + * (**default=`false`**) If input is required and frames are not processed on time, then Smelter will delay producing output frames. + */ + required?: boolean | null; + } + | { + type: "decklink"; + /** + * Single DeckLink device can consist of multiple sub-devices. This field defines index of sub-device that should be used. + * + * The input device is selected based on fields `subdevice_index`, `persistent_id` **AND** `display_name`. All of them need to match the device if they are specified. If nothing is matched, the error response will list available devices. + */ + subdevice_index?: number | null; + /** + * Select sub-device to use based on the display name. This is the value you see in e.g. Blackmagic Media Express app. like "DeckLink Quad HDMI Recorder (3)" + * + * The input device is selected based on fields `subdevice_index`, `persistent_id` **AND** `display_name`. All of them need to match the device if they are specified. If nothing is matched, the error response will list available devices. + */ + display_name?: string | null; + /** + * Persistent ID of a device represented by 32-bit hex number. Each DeckLink sub-device has a separate id. + * + * The input device is selected based on fields `subdevice_index`, `persistent_id` **AND** `display_name`. All of them need to match the device if they are specified. If nothing is matched, the error response will list available devices. + */ + persistent_id?: string | null; + /** + * (**default=`true`**) Enable audio support. + */ + enable_audio?: boolean | null; + /** + * (**default=`false`**) If input is required and frames are not processed on time, then Smelter will delay producing output frames. + */ + required?: boolean | null; + }; export type PortOrPortRange = string | number; export type TransportProtocol = "udp" | "tcp_server"; export type RtpVideoDecoderOptions = "ffmpeg_h264" | "ffmpeg_vp8" | "ffmpeg_vp9" | "vulkan_h264"; export type InputRtpAudioOptions = | { - decoder: "opus"; - } - | { - decoder: "aac"; - /** - * AudioSpecificConfig as described in MPEG-4 part 3, section 1.6.2.1 The config should be encoded as described in [RFC 3640](https://datatracker.ietf.org/doc/html/rfc3640#section-4.1). - * - * The simplest way to obtain this value when using ffmpeg to stream to the compositor is to pass the additional `-sdp_file FILENAME` option to ffmpeg. This will cause it to write out an sdp file, which will contain this field. Programs which have the ability to stream AAC to the compositor should provide this information. - * - * In MP4 files, the ASC is embedded inside the esds box (note that it is not the whole box, only a part of it). This also applies to fragmented MP4s downloaded over HLS, if the playlist uses MP4s instead of MPEG Transport Streams - * - * In FLV files and the RTMP protocol, the ASC can be found in the `AACAUDIODATA` tag. - */ - audio_specific_config: string; - /** - * (**default=`"high_bitrate"`**) Specifies the [RFC 3640 mode](https://datatracker.ietf.org/doc/html/rfc3640#section-3.3.1) that should be used when depacketizing this stream. - */ - rtp_mode?: AacRtpMode | null; - }; + decoder: "opus"; + } + | { + decoder: "aac"; + /** + * AudioSpecificConfig as described in MPEG-4 part 3, section 1.6.2.1 The config should be encoded as described in [RFC 3640](https://datatracker.ietf.org/doc/html/rfc3640#section-4.1). + * + * The simplest way to obtain this value when using ffmpeg to stream to the compositor is to pass the additional `-sdp_file FILENAME` option to ffmpeg. This will cause it to write out an sdp file, which will contain this field. Programs which have the ability to stream AAC to the compositor should provide this information. + * + * In MP4 files, the ASC is embedded inside the esds box (note that it is not the whole box, only a part of it). This also applies to fragmented MP4s downloaded over HLS, if the playlist uses MP4s instead of MPEG Transport Streams + * + * In FLV files and the RTMP protocol, the ASC can be found in the `AACAUDIODATA` tag. + */ + audio_specific_config: string; + /** + * (**default=`"high_bitrate"`**) Specifies the [RFC 3640 mode](https://datatracker.ietf.org/doc/html/rfc3640#section-3.3.1) that should be used when depacketizing this stream. + */ + rtp_mode?: AacRtpMode | null; + }; export type AacRtpMode = "low_bitrate" | "high_bitrate"; export type RtmpVideoDecoderOptions = "ffmpeg_h264" | "vulkan_h264"; export type Mp4VideoDecoderOptions = "ffmpeg_h264" | "vulkan_h264"; @@ -261,190 +261,190 @@ export type V4L2InputFormat = "yuyv" | "nv12"; export type Framerate = string | number; export type RegisterOutput = | { - type: "rtp_stream"; - /** - * Depends on the value of the `transport_protocol` field: - `udp` - An UDP port number that RTP packets will be sent to. - `tcp_server` - A local TCP port number or a port range that Smelter will listen for incoming connections. - */ - port: PortOrPortRange; - /** - * IP address to which RTP packets should be sent. This field is only valid if `transport_protocol` field is set to `udp`. - */ - ip?: string | null; - /** - * (**default=`"udp"`**) Transport layer protocol that will be used to send RTP packets. - */ - transport_protocol?: TransportProtocol | null; - /** - * Parameters of a video included in the RTP stream. - */ - video?: OutputRtpVideoOptions | null; - /** - * Parameters of an audio included in the RTP stream. - */ - audio?: OutputRtpAudioOptions | null; - } - | { - type: "rtmp_client"; - /** - * RTMP endpoint url. - */ - url: string; - /** - * Video stream configuration. - */ - video?: OutputRtmpClientVideoOptions | null; - /** - * Audio stream configuration. - */ - audio?: OutputRtmpClientAudioOptions | null; - } - | { - type: "mp4"; - /** - * Path to output MP4 file. - */ - path: string; - /** - * Video stream configuration. - */ - video?: OutputMp4VideoOptions | null; - /** - * Audio stream configuration. - */ - audio?: OutputMp4AudioOptions | null; - /** - * Raw FFmpeg muxer options. See [docs](https://ffmpeg.org/ffmpeg-formats.html) for more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "whip_client"; - /** - * WHIP server endpoint - */ - endpoint_url: string; - bearer_token?: string | null; - /** - * Video track configuration. - */ - video?: OutputWhipVideoOptions | null; - /** - * Audio track configuration. - */ - audio?: OutputWhipAudioOptions | null; - } - | { - type: "whep_server"; - /** - * Token used for authentication in WHEP protocol. If not provided, the bearer token is not required to establish the session. - */ - bearer_token?: string | null; - /** - * Video track configuration. - */ - video?: OutputWhepVideoOptions | null; - /** - * Audio track configuration. - */ - audio?: OutputWhepAudioOptions | null; - } - | { - type: "hls"; - /** - * Path to output HLS playlist. - */ - path: string; - /** - * Number of segments kept in the playlist. When the limit is reached the oldest segment is removed. If not specified, no segments will removed. - */ - max_playlist_size?: number | null; - /** - * Video track configuration. - */ - video?: OutputHlsVideoOptions | null; - /** - * Audio track configuration. - */ - audio?: OutputHlsAudioOptions | null; - }; + type: "rtp_stream"; + /** + * Depends on the value of the `transport_protocol` field: - `udp` - An UDP port number that RTP packets will be sent to. - `tcp_server` - A local TCP port number or a port range that Smelter will listen for incoming connections. + */ + port: PortOrPortRange; + /** + * IP address to which RTP packets should be sent. This field is only valid if `transport_protocol` field is set to `udp`. + */ + ip?: string | null; + /** + * (**default=`"udp"`**) Transport layer protocol that will be used to send RTP packets. + */ + transport_protocol?: TransportProtocol | null; + /** + * Parameters of a video included in the RTP stream. + */ + video?: OutputRtpVideoOptions | null; + /** + * Parameters of an audio included in the RTP stream. + */ + audio?: OutputRtpAudioOptions | null; + } + | { + type: "rtmp_client"; + /** + * RTMP endpoint url. + */ + url: string; + /** + * Video stream configuration. + */ + video?: OutputRtmpClientVideoOptions | null; + /** + * Audio stream configuration. + */ + audio?: OutputRtmpClientAudioOptions | null; + } + | { + type: "mp4"; + /** + * Path to output MP4 file. + */ + path: string; + /** + * Video stream configuration. + */ + video?: OutputMp4VideoOptions | null; + /** + * Audio stream configuration. + */ + audio?: OutputMp4AudioOptions | null; + /** + * Raw FFmpeg muxer options. See [docs](https://ffmpeg.org/ffmpeg-formats.html) for more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "whip_client"; + /** + * WHIP server endpoint + */ + endpoint_url: string; + bearer_token?: string | null; + /** + * Video track configuration. + */ + video?: OutputWhipVideoOptions | null; + /** + * Audio track configuration. + */ + audio?: OutputWhipAudioOptions | null; + } + | { + type: "whep_server"; + /** + * Token used for authentication in WHEP protocol. If not provided, the bearer token is not required to establish the session. + */ + bearer_token?: string | null; + /** + * Video track configuration. + */ + video?: OutputWhepVideoOptions | null; + /** + * Audio track configuration. + */ + audio?: OutputWhepAudioOptions | null; + } + | { + type: "hls"; + /** + * Path to output HLS playlist. + */ + path: string; + /** + * Number of segments kept in the playlist. When the limit is reached the oldest segment is removed. If not specified, no segments will removed. + */ + max_playlist_size?: number | null; + /** + * Video track configuration. + */ + video?: OutputHlsVideoOptions | null; + /** + * Audio track configuration. + */ + audio?: OutputHlsAudioOptions | null; + }; export type InputId = string; export type RtpVideoEncoderOptions = | { - type: "ffmpeg_h264"; - /** - * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. - */ - preset?: H264EncoderPreset | null; - /** - * Encoding bitrate. Default value depends on chosen encoder. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format. - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "ffmpeg_vp8"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "ffmpeg_vp9"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format. - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "vulkan_h264"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - }; + type: "ffmpeg_h264"; + /** + * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. + */ + preset?: H264EncoderPreset | null; + /** + * Encoding bitrate. Default value depends on chosen encoder. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format. + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "ffmpeg_vp8"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "ffmpeg_vp9"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format. + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "vulkan_h264"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + }; export type H264EncoderPreset = | "ultrafast" | "superfast" @@ -459,369 +459,369 @@ export type H264EncoderPreset = export type VideoEncoderBitrate = | number | { - /** - * Average bitrate measured in bits/second. Encoder will try to keep the bitrate around the provided average, but may temporarily increase it to the provided max bitrate. - */ - average_bitrate: number; - /** - * Max bitrate measured in bits/second. - */ - max_bitrate: number; - }; + /** + * Average bitrate measured in bits/second. Encoder will try to keep the bitrate around the provided average, but may temporarily increase it to the provided max bitrate. + */ + average_bitrate: number; + /** + * Max bitrate measured in bits/second. + */ + max_bitrate: number; + }; export type PixelFormat = "yuv420p" | "yuv422p" | "yuv444p"; export type Component = | { - type: "input_stream"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * Id of an input. It identifies a stream registered using a [`RegisterInputStream`](../routes.md#register-input) request. - */ - input_id: InputId; - } - | { - type: "view"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * List of component's children. - */ - children?: Component[] | null; - /** - * Width of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. - */ - width?: number | null; - /** - * Height of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. - */ - height?: number | null; - /** - * Direction defines how static children are positioned inside a View component. - */ - direction?: ViewDirection | null; - /** - * Distance in pixels between this component's top edge and its parent's top edge (including a border). If this field is defined, then the component will ignore a layout defined by its parent. - */ - top?: number | null; - /** - * Distance in pixels between this component's left edge and its parent's left edge (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - left?: number | null; - /** - * Distance in pixels between the bottom edge of this component and the bottom edge of its parent (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - bottom?: number | null; - /** - * Distance in pixels between this component's right edge and its parent's right edge. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - right?: number | null; - /** - * Rotation of a component in degrees. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - rotation?: number | null; - /** - * Defines how this component will behave during a scene update. This will only have an effect if the previous scene already contained a `View` component with the same id. - */ - transition?: Transition | null; - /** - * (**default=`"hidden"`**) Controls what happens to content that is too big to fit into an area. - */ - overflow?: Overflow | null; - /** - * (**default=`"#00000000"`**) Background color in a `"#RRGGBBAA"` format. - */ - background_color?: RGBAColor | null; - /** - * (**default=`0.0`**) Radius of a rounded corner. - */ - border_radius?: number | null; - /** - * (**default=`0.0`**) Border width. - */ - border_width?: number | null; - /** - * (**default=`"#00000000"`**) Border color in a `"#RRGGBBAA"` format. - */ - border_color?: RGBAColor | null; - /** - * List of box shadows. - */ - box_shadow?: BoxShadow[] | null; - /** - * (**default=`0.0`**) Padding for all sides of the component. - */ - padding?: number | null; - /** - * (**default=`0.0`**) Padding for the top and bottom of the component. - */ - padding_vertical?: number | null; - /** - * (**default=`0.0`**) Padding for the left and right of the component. - */ - padding_horizontal?: number | null; - /** - * (**default=`0.0`**) Padding on top side in pixels. - */ - padding_top?: number | null; - /** - * (**default=`0.0`**) Padding on right side in pixels. - */ - padding_right?: number | null; - /** - * (**default=`0.0`**) Padding on bottom side in pixels. - */ - padding_bottom?: number | null; - /** - * (**default=`0.0`**) Padding on left side in pixels. - */ - padding_left?: number | null; - } - | { - type: "web_view"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * List of component's children. - */ - children?: Component[] | null; - /** - * Id of a web renderer instance. It identifies an instance registered using a [`register web renderer`](../routes.md#register-web-renderer-instance) request. - * - * :::warning You can only refer to specific instances in one Component at a time. ::: - */ - instance_id: RendererId; - } - | { - type: "shader"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * List of component's children. - */ - children?: Component[] | null; - /** - * Id of a shader. It identifies a shader registered using a [`register shader`](../routes.md#register-shader) request. - */ - shader_id: RendererId; - /** - * Object that will be serialized into a `struct` and passed inside the shader as: - * - * ```wgsl @group(1) @binding(0) var ``` :::note This object's structure must match the structure defined in a shader source code. Currently, we do not handle memory layout automatically. To achieve the correct memory alignment, you might need to pad your data with additional fields. See [WGSL documentation](https://www.w3.org/TR/WGSL/#alignment-and-size) for more details. ::: - */ - shader_param?: ShaderParam | null; - /** - * Resolution of a texture where shader will be executed. - */ - resolution: Resolution; - } - | { - type: "image"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * Id of an image. It identifies an image registered using a [`register image`](../routes.md#register-image) request. - */ - image_id: RendererId; - /** - * Width of the image in pixels. If `height` is not explicitly provided, the image will automatically adjust its height to maintain its original aspect ratio relative to the width. - */ - width?: number | null; - /** - * Height of the image in pixels. If `width` is not explicitly provided, the image will automatically adjust its width to maintain its original aspect ratio relative to the height. - */ - height?: number | null; - } - | { - type: "text"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * Text that will be rendered. - */ - text: string; - /** - * Width of a texture that text will be rendered on. If not provided, the resulting texture will be sized based on the defined text but limited to `max_width` value. - */ - width?: number | null; - /** - * Height of a texture that text will be rendered on. If not provided, the resulting texture will be sized based on the defined text but limited to `max_height` value. It's an error to provide `height` if `width` is not defined. - */ - height?: number | null; - /** - * (**default=`7682`**) Maximal `width`. Limits the width of the texture that the text will be rendered on. Value is ignored if `width` is defined. - */ - max_width?: number | null; - /** - * (**default=`4320`**) Maximal `height`. Limits the height of the texture that the text will be rendered on. Value is ignored if height is defined. - */ - max_height?: number | null; - /** - * Font size in pixels. - */ - font_size: number; - /** - * Distance between lines in pixels. Defaults to the value of the `font_size` property. - */ - line_height?: number | null; - /** - * (**default=`"#FFFFFFFF"`**) Font color in `#RRGGBBAA` format. - */ - color?: RGBAColor | null; - /** - * (**default=`"#00000000"`**) Background color in `#RRGGBBAA` format. - */ - background_color?: RGBAColor | null; - /** - * (**default=`"Verdana"`**) Font family. Provide [family-name](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#family-name-value) for a specific font. "generic-family" values like e.g. "sans-serif" will not work. - */ - font_family?: string | null; - /** - * (**default=`"normal"`**) Font style. The selected font needs to support the specified style. - */ - style?: TextStyle | null; - /** - * (**default=`"left"`**) Text align. - */ - align?: HorizontalAlign | null; - /** - * (**default=`"none"`**) Text wrapping options. - */ - wrap?: TextWrapMode | null; - /** - * (**default=`"normal"`**) Font weight. The selected font needs to support the specified weight. - */ - weight?: TextWeight | null; - } - | { - type: "tiles"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * List of component's children. - */ - children?: Component[] | null; - /** - * Width of a component in pixels. Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. - */ - width?: number | null; - /** - * Height of a component in pixels. Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. - */ - height?: number | null; - /** - * (**default=`"#00000000"`**) Background color in a `"#RRGGBBAA"` format. - */ - background_color?: RGBAColor | null; - /** - * (**default=`"16:9"`**) Aspect ratio of a tile in `"W:H"` format, where W and H are integers. - */ - tile_aspect_ratio?: AspectRatio | null; - /** - * (**default=`0`**) Margin of each tile in pixels. - */ - margin?: number | null; - /** - * (**default=`0`**) Padding on each tile in pixels. - */ - padding?: number | null; - /** - * (**default=`"center"`**) Horizontal alignment of tiles. - */ - horizontal_align?: HorizontalAlign | null; - /** - * (**default=`"center"`**) Vertical alignment of tiles. - */ - vertical_align?: VerticalAlign | null; - /** - * Defines how this component will behave during a scene update. This will only have an effect if the previous scene already contained a `Tiles` component with the same id. - */ - transition?: Transition | null; - } - | { - type: "rescaler"; - /** - * Id of a component. - */ - id?: ComponentId | null; - /** - * List of component's children. - */ - child: Component; - /** - * (**default=`"fit"`**) Resize mode: - */ - mode?: RescaleMode | null; - /** - * (**default=`"center"`**) Horizontal alignment. - */ - horizontal_align?: HorizontalAlign | null; - /** - * (**default=`"center"`**) Vertical alignment. - */ - vertical_align?: VerticalAlign | null; - /** - * Width of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. - */ - width?: number | null; - /** - * Height of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. - */ - height?: number | null; - /** - * Distance in pixels between this component's top edge and its parent's top edge (including a border). If this field is defined, then the component will ignore a layout defined by its parent. - */ - top?: number | null; - /** - * Distance in pixels between this component's left edge and its parent's left edge (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - left?: number | null; - /** - * Distance in pixels between the bottom edge of this component and the bottom edge of its parent (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - bottom?: number | null; - /** - * Distance in pixels between this component's right edge and its parent's right edge. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - right?: number | null; - /** - * Rotation of a component in degrees. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. - */ - rotation?: number | null; - /** - * Defines how this component will behave during a scene update. This will only have an effect if the previous scene already contained a `Rescaler` component with the same id. - */ - transition?: Transition | null; - /** - * (**default=`0.0`**) Radius of a rounded corner. - */ - border_radius?: number | null; - /** - * (**default=`0.0`**) Border width. - */ - border_width?: number | null; - /** - * (**default=`"#00000000"`**) Border color in a `"#RRGGBBAA"` format. - */ - border_color?: RGBAColor | null; - /** - * List of box shadows. - */ - box_shadow?: BoxShadow[] | null; - }; + type: "input_stream"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * Id of an input. It identifies a stream registered using a [`RegisterInputStream`](../routes.md#register-input) request. + */ + input_id: InputId; + } + | { + type: "view"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * List of component's children. + */ + children?: Component[] | null; + /** + * Width of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. + */ + width?: number | null; + /** + * Height of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. + */ + height?: number | null; + /** + * Direction defines how static children are positioned inside a View component. + */ + direction?: ViewDirection | null; + /** + * Distance in pixels between this component's top edge and its parent's top edge (including a border). If this field is defined, then the component will ignore a layout defined by its parent. + */ + top?: number | null; + /** + * Distance in pixels between this component's left edge and its parent's left edge (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + left?: number | null; + /** + * Distance in pixels between the bottom edge of this component and the bottom edge of its parent (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + bottom?: number | null; + /** + * Distance in pixels between this component's right edge and its parent's right edge. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + right?: number | null; + /** + * Rotation of a component in degrees. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + rotation?: number | null; + /** + * Defines how this component will behave during a scene update. This will only have an effect if the previous scene already contained a `View` component with the same id. + */ + transition?: Transition | null; + /** + * (**default=`"hidden"`**) Controls what happens to content that is too big to fit into an area. + */ + overflow?: Overflow | null; + /** + * (**default=`"#00000000"`**) Background color in a `"#RRGGBBAA"` format. + */ + background_color?: RGBAColor | null; + /** + * (**default=`0.0`**) Radius of a rounded corner. + */ + border_radius?: number | null; + /** + * (**default=`0.0`**) Border width. + */ + border_width?: number | null; + /** + * (**default=`"#00000000"`**) Border color in a `"#RRGGBBAA"` format. + */ + border_color?: RGBAColor | null; + /** + * List of box shadows. + */ + box_shadow?: BoxShadow[] | null; + /** + * (**default=`0.0`**) Padding for all sides of the component. + */ + padding?: number | null; + /** + * (**default=`0.0`**) Padding for the top and bottom of the component. + */ + padding_vertical?: number | null; + /** + * (**default=`0.0`**) Padding for the left and right of the component. + */ + padding_horizontal?: number | null; + /** + * (**default=`0.0`**) Padding on top side in pixels. + */ + padding_top?: number | null; + /** + * (**default=`0.0`**) Padding on right side in pixels. + */ + padding_right?: number | null; + /** + * (**default=`0.0`**) Padding on bottom side in pixels. + */ + padding_bottom?: number | null; + /** + * (**default=`0.0`**) Padding on left side in pixels. + */ + padding_left?: number | null; + } + | { + type: "web_view"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * List of component's children. + */ + children?: Component[] | null; + /** + * Id of a web renderer instance. It identifies an instance registered using a [`register web renderer`](../routes.md#register-web-renderer-instance) request. + * + * :::warning You can only refer to specific instances in one Component at a time. ::: + */ + instance_id: RendererId; + } + | { + type: "shader"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * List of component's children. + */ + children?: Component[] | null; + /** + * Id of a shader. It identifies a shader registered using a [`register shader`](../routes.md#register-shader) request. + */ + shader_id: RendererId; + /** + * Object that will be serialized into a `struct` and passed inside the shader as: + * + * ```wgsl @group(1) @binding(0) var ``` :::note This object's structure must match the structure defined in a shader source code. Currently, we do not handle memory layout automatically. To achieve the correct memory alignment, you might need to pad your data with additional fields. See [WGSL documentation](https://www.w3.org/TR/WGSL/#alignment-and-size) for more details. ::: + */ + shader_param?: ShaderParam | null; + /** + * Resolution of a texture where shader will be executed. + */ + resolution: Resolution; + } + | { + type: "image"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * Id of an image. It identifies an image registered using a [`register image`](../routes.md#register-image) request. + */ + image_id: RendererId; + /** + * Width of the image in pixels. If `height` is not explicitly provided, the image will automatically adjust its height to maintain its original aspect ratio relative to the width. + */ + width?: number | null; + /** + * Height of the image in pixels. If `width` is not explicitly provided, the image will automatically adjust its width to maintain its original aspect ratio relative to the height. + */ + height?: number | null; + } + | { + type: "text"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * Text that will be rendered. + */ + text: string; + /** + * Width of a texture that text will be rendered on. If not provided, the resulting texture will be sized based on the defined text but limited to `max_width` value. + */ + width?: number | null; + /** + * Height of a texture that text will be rendered on. If not provided, the resulting texture will be sized based on the defined text but limited to `max_height` value. It's an error to provide `height` if `width` is not defined. + */ + height?: number | null; + /** + * (**default=`7682`**) Maximal `width`. Limits the width of the texture that the text will be rendered on. Value is ignored if `width` is defined. + */ + max_width?: number | null; + /** + * (**default=`4320`**) Maximal `height`. Limits the height of the texture that the text will be rendered on. Value is ignored if height is defined. + */ + max_height?: number | null; + /** + * Font size in pixels. + */ + font_size: number; + /** + * Distance between lines in pixels. Defaults to the value of the `font_size` property. + */ + line_height?: number | null; + /** + * (**default=`"#FFFFFFFF"`**) Font color in `#RRGGBBAA` format. + */ + color?: RGBAColor | null; + /** + * (**default=`"#00000000"`**) Background color in `#RRGGBBAA` format. + */ + background_color?: RGBAColor | null; + /** + * (**default=`"Verdana"`**) Font family. Provide [family-name](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#family-name-value) for a specific font. "generic-family" values like e.g. "sans-serif" will not work. + */ + font_family?: string | null; + /** + * (**default=`"normal"`**) Font style. The selected font needs to support the specified style. + */ + style?: TextStyle | null; + /** + * (**default=`"left"`**) Text align. + */ + align?: HorizontalAlign | null; + /** + * (**default=`"none"`**) Text wrapping options. + */ + wrap?: TextWrapMode | null; + /** + * (**default=`"normal"`**) Font weight. The selected font needs to support the specified weight. + */ + weight?: TextWeight | null; + } + | { + type: "tiles"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * List of component's children. + */ + children?: Component[] | null; + /** + * Width of a component in pixels. Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. + */ + width?: number | null; + /** + * Height of a component in pixels. Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. + */ + height?: number | null; + /** + * (**default=`"#00000000"`**) Background color in a `"#RRGGBBAA"` format. + */ + background_color?: RGBAColor | null; + /** + * (**default=`"16:9"`**) Aspect ratio of a tile in `"W:H"` format, where W and H are integers. + */ + tile_aspect_ratio?: AspectRatio | null; + /** + * (**default=`0`**) Margin of each tile in pixels. + */ + margin?: number | null; + /** + * (**default=`0`**) Padding on each tile in pixels. + */ + padding?: number | null; + /** + * (**default=`"center"`**) Horizontal alignment of tiles. + */ + horizontal_align?: HorizontalAlign | null; + /** + * (**default=`"center"`**) Vertical alignment of tiles. + */ + vertical_align?: VerticalAlign | null; + /** + * Defines how this component will behave during a scene update. This will only have an effect if the previous scene already contained a `Tiles` component with the same id. + */ + transition?: Transition | null; + } + | { + type: "rescaler"; + /** + * Id of a component. + */ + id?: ComponentId | null; + /** + * List of component's children. + */ + child: Component; + /** + * (**default=`"fit"`**) Resize mode: + */ + mode?: RescaleMode | null; + /** + * (**default=`"center"`**) Horizontal alignment. + */ + horizontal_align?: HorizontalAlign | null; + /** + * (**default=`"center"`**) Vertical alignment. + */ + vertical_align?: VerticalAlign | null; + /** + * Width of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. + */ + width?: number | null; + /** + * Height of a component in pixels (without a border). Exact behavior might be different based on the parent component: - If the parent component is a layout, check sections "Absolute positioning" and "Static positioning" of that component. - If the parent component is not a layout, then this field is required. + */ + height?: number | null; + /** + * Distance in pixels between this component's top edge and its parent's top edge (including a border). If this field is defined, then the component will ignore a layout defined by its parent. + */ + top?: number | null; + /** + * Distance in pixels between this component's left edge and its parent's left edge (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + left?: number | null; + /** + * Distance in pixels between the bottom edge of this component and the bottom edge of its parent (including a border). If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + bottom?: number | null; + /** + * Distance in pixels between this component's right edge and its parent's right edge. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + right?: number | null; + /** + * Rotation of a component in degrees. If this field is defined, this element will be absolutely positioned, instead of being laid out by its parent. + */ + rotation?: number | null; + /** + * Defines how this component will behave during a scene update. This will only have an effect if the previous scene already contained a `Rescaler` component with the same id. + */ + transition?: Transition | null; + /** + * (**default=`0.0`**) Radius of a rounded corner. + */ + border_radius?: number | null; + /** + * (**default=`0.0`**) Border width. + */ + border_width?: number | null; + /** + * (**default=`"#00000000"`**) Border color in a `"#RRGGBBAA"` format. + */ + border_color?: RGBAColor | null; + /** + * List of box shadows. + */ + box_shadow?: BoxShadow[] | null; + }; export type ComponentId = string; export type ViewDirection = "row" | "column"; /** @@ -831,72 +831,72 @@ export type ViewDirection = "row" | "column"; */ export type EasingFunction = | { - function_name: "linear"; - } + function_name: "linear"; + } | { - function_name: "bounce"; - } + function_name: "bounce"; + } | { - function_name: "cubic_bezier"; - /** - * @minItems 4 - * @maxItems 4 - */ - points: [number, number, number, number]; - }; + function_name: "cubic_bezier"; + /** + * @minItems 4 + * @maxItems 4 + */ + points: [number, number, number, number]; + }; export type Overflow = "visible" | "hidden" | "fit"; export type RGBAColor = string; export type RendererId = string; export type ShaderParam = | { - type: "f32"; - value: number; - } + type: "f32"; + value: number; + } | { - type: "u32"; - value: number; - } + type: "u32"; + value: number; + } | { - type: "i32"; - value: number; - } + type: "i32"; + value: number; + } | { - type: "list"; - value: ShaderParam[]; - } + type: "list"; + value: ShaderParam[]; + } | { - type: "struct"; - value: ShaderParamStructField[]; - }; + type: "struct"; + value: ShaderParamStructField[]; + }; export type ShaderParamStructField = { field_name: string; } & ShaderParamStructField1; export type ShaderParamStructField1 = | { - type: "f32"; - value: number; - field_name?: string; - } - | { - type: "u32"; - value: number; - field_name?: string; - } - | { - type: "i32"; - value: number; - field_name?: string; - } - | { - type: "list"; - value: ShaderParam[]; - field_name?: string; - } - | { - type: "struct"; - value: ShaderParamStructField[]; - field_name?: string; - }; + type: "f32"; + value: number; + field_name?: string; + } + | { + type: "u32"; + value: number; + field_name?: string; + } + | { + type: "i32"; + value: number; + field_name?: string; + } + | { + type: "list"; + value: ShaderParam[]; + field_name?: string; + } + | { + type: "struct"; + value: ShaderParamStructField[]; + field_name?: string; + }; export type TextStyle = "normal" | "italic" | "oblique"; export type HorizontalAlign = "left" | "right" | "justified" | "center"; export type TextWrapMode = "none" | "glyph" | "word"; @@ -940,41 +940,41 @@ export type OpusEncoderPreset = "quality" | "voip" | "lowest_latency"; export type AudioChannels = "mono" | "stereo"; export type RtmpClientVideoEncoderOptions = | { - type: "ffmpeg_h264"; - /** - * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. - */ - preset?: H264EncoderPreset | null; - /** - * Encoding bitrate. Default value depends on chosen encoder. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "vulkan_h264"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - }; + type: "ffmpeg_h264"; + /** + * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. + */ + preset?: H264EncoderPreset | null; + /** + * Encoding bitrate. Default value depends on chosen encoder. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "vulkan_h264"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + }; export type RtmpClientAudioEncoderOptions = { type: "aac"; /** @@ -984,41 +984,41 @@ export type RtmpClientAudioEncoderOptions = { }; export type Mp4VideoEncoderOptions = | { - type: "ffmpeg_h264"; - /** - * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. - */ - preset?: H264EncoderPreset | null; - /** - * Encoding bitrate. Default value depends on chosen encoder. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format. - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "vulkan_h264"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - }; + type: "ffmpeg_h264"; + /** + * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. + */ + preset?: H264EncoderPreset | null; + /** + * Encoding bitrate. Default value depends on chosen encoder. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format. + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "vulkan_h264"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + }; export type Mp4AudioEncoderOptions = { type: "aac"; /** @@ -1028,176 +1028,176 @@ export type Mp4AudioEncoderOptions = { }; export type WhipVideoEncoderOptions = | { - type: "ffmpeg_h264"; - /** - * (**default=`"fast"`**) Preset for an encoder. See `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. - */ - preset?: H264EncoderPreset | null; - /** - * Encoding bitrate. Default value depends on chosen encoder. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "ffmpeg_vp8"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "ffmpeg_vp9"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "vulkan_h264"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - } - | { - type: "any"; - }; + type: "ffmpeg_h264"; + /** + * (**default=`"fast"`**) Preset for an encoder. See `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. + */ + preset?: H264EncoderPreset | null; + /** + * Encoding bitrate. Default value depends on chosen encoder. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "ffmpeg_vp8"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "ffmpeg_vp9"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "vulkan_h264"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + } + | { + type: "any"; + }; export type WhipAudioEncoderOptions = | { - type: "opus"; - /** - * (**default="voip"**) Specifies preset for audio output encoder. - */ - preset?: OpusEncoderPreset | null; - /** - * (**default=`48000`**) Sample rate. Allowed values: [8000, 16000, 24000, 48000]. - */ - sample_rate?: number | null; - /** - * (**default=`false`**) Specifies if forward error correction (FEC) should be used. - */ - forward_error_correction?: boolean | null; - } - | { - type: "any"; - }; + type: "opus"; + /** + * (**default="voip"**) Specifies preset for audio output encoder. + */ + preset?: OpusEncoderPreset | null; + /** + * (**default=`48000`**) Sample rate. Allowed values: [8000, 16000, 24000, 48000]. + */ + sample_rate?: number | null; + /** + * (**default=`false`**) Specifies if forward error correction (FEC) should be used. + */ + forward_error_correction?: boolean | null; + } + | { + type: "any"; + }; export type WhepVideoEncoderOptions = | { - type: "ffmpeg_h264"; - /** - * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. - */ - preset?: H264EncoderPreset | null; - /** - * Encoding bitrate. Default value depends on chosen encoder. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format. - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "ffmpeg_vp8"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "ffmpeg_vp9"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format. - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "vulkan_h264"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - }; + type: "ffmpeg_h264"; + /** + * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. + */ + preset?: H264EncoderPreset | null; + /** + * Encoding bitrate. Default value depends on chosen encoder. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format. + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "ffmpeg_vp8"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "ffmpeg_vp9"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format. + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. Visit [docs](https://ffmpeg.org/ffmpeg-codecs.html) to learn more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "vulkan_h264"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + }; export type WhepAudioEncoderOptions = { type: "opus"; /** @@ -1219,41 +1219,41 @@ export type WhepAudioEncoderOptions = { }; export type HlsVideoEncoderOptions = | { - type: "ffmpeg_h264"; - /** - * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. - */ - preset?: H264EncoderPreset | null; - /** - * Encoding bitrate. Default value depends on chosen encoder. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - /** - * (**default=`"yuv420p"`**) Encoder pixel format - */ - pixel_format?: PixelFormat | null; - /** - * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. - */ - ffmpeg_options?: { - [k: string]: string; - } | null; - } - | { - type: "vulkan_h264"; - /** - * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. - */ - bitrate?: VideoEncoderBitrate | null; - /** - * (**default=`5000`**) Interval between keyframes, in milliseconds. - */ - keyframe_interval_ms?: number | null; - }; + type: "ffmpeg_h264"; + /** + * (**default=`"fast"`**) Video output encoder preset. Visit `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more. + */ + preset?: H264EncoderPreset | null; + /** + * Encoding bitrate. Default value depends on chosen encoder. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Maximal interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + /** + * (**default=`"yuv420p"`**) Encoder pixel format + */ + pixel_format?: PixelFormat | null; + /** + * Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more. + */ + ffmpeg_options?: { + [k: string]: string; + } | null; + } + | { + type: "vulkan_h264"; + /** + * Encoding bitrate. If not provided, bitrate is calculated based on resolution and framerate. For example at 1080p 30 FPS the average bitrate is 5000 kbit/s and max bitrate is 6250 kbit/s. + */ + bitrate?: VideoEncoderBitrate | null; + /** + * (**default=`5000`**) Interval between keyframes, in milliseconds. + */ + keyframe_interval_ms?: number | null; + }; export type HlsAudioEncoderOptions = { type: "aac"; /** @@ -1263,73 +1263,73 @@ export type HlsAudioEncoderOptions = { }; export type ImageSpec = | { - asset_type: "png"; - url?: string | null; - path?: string | null; - } - | { - asset_type: "jpeg"; - url?: string | null; - path?: string | null; - } - | { - asset_type: "svg"; - url?: string | null; - path?: string | null; - resolution?: Resolution | null; - } - | { - asset_type: "gif"; - url?: string | null; - path?: string | null; - } - | { - asset_type: "auto"; - url?: string | null; - path?: string | null; - }; + asset_type: "png"; + url?: string | null; + path?: string | null; + } + | { + asset_type: "jpeg"; + url?: string | null; + path?: string | null; + } + | { + asset_type: "svg"; + url?: string | null; + path?: string | null; + resolution?: Resolution | null; + } + | { + asset_type: "gif"; + url?: string | null; + path?: string | null; + } + | { + asset_type: "auto"; + url?: string | null; + path?: string | null; + }; export type WebEmbeddingMethod = | "chromium_embedding" | "native_embedding_over_content" | "native_embedding_under_content"; export type InputStatsReport = | { - rtp: RtpInputStatsReport; - } + rtp: RtpInputStatsReport; + } | { - whip: WhipInputStatsReport; - } + whip: WhipInputStatsReport; + } | { - whep: WhepInputStatsReport; - } + whep: WhepInputStatsReport; + } | { - hls: HlsInputStatsReport; - } + hls: HlsInputStatsReport; + } | { - rtmp: RtmpInputStatsReport; - } + rtmp: RtmpInputStatsReport; + } | { - mp4: Mp4InputStatsReport; - }; + mp4: Mp4InputStatsReport; + }; export type OutputStatsReport = | { - whep: WhepOutputStatsReport; - } + whep: WhepOutputStatsReport; + } | { - whip: WhipOutputStatsReport; - } + whip: WhipOutputStatsReport; + } | { - hls: HlsOutputStatsReport; - } + hls: HlsOutputStatsReport; + } | { - mp4: Mp4OutputStatsReport; - } + mp4: Mp4OutputStatsReport; + } | { - rtmp: RtmpOutputStatsReport; - } + rtmp: RtmpOutputStatsReport; + } | { - rtp: RtpOutputStatsReport; - }; + rtp: RtpOutputStatsReport; + }; export interface InputRtpVideoOptions { decoder: RtpVideoDecoderOptions; diff --git a/ts/smelter/src/types/stats/input.ts b/ts/smelter/src/types/stats/input.ts index a631a52bd..410e6e0f3 100644 --- a/ts/smelter/src/types/stats/input.ts +++ b/ts/smelter/src/types/stats/input.ts @@ -26,23 +26,23 @@ export type RtpJitterBufferSlidingWindowStatsReport = { inputBufferAvgSeconds: number; inputBufferMaxSeconds: number; inputBufferMinSeconds: number; -} +}; export type WhipInputStatsReport = { videoRtp: RtpJitterBufferStatsReport; audioRtp: RtpJitterBufferStatsReport; -} +}; export type WhepInputStatsReport = { videoRtp: RtpJitterBufferStatsReport; audioRtp: RtpJitterBufferStatsReport; -} +}; export type HlsInputStatsReport = { video: HlsInputTrackStatsReport; audio: HlsInputTrackStatsReport; corruptedPacketsReceived: number; corruptedPacketsReceivedLast10Seconds: number; -} +}; export type HlsInputTrackStatsReport = { packetsReceived: number; @@ -50,7 +50,7 @@ export type HlsInputTrackStatsReport = { bitrate1Second: number; bitrate1Minute: number; last10Seconds: HlsInputTrackSlidingWindowStatsReport; -} +}; export type HlsInputTrackSlidingWindowStatsReport = { packetsReceived: number; @@ -67,16 +67,16 @@ export type HlsInputTrackSlidingWindowStatsReport = { inputBufferAvgSeconds: number; inputBufferMaxSeconds: number; inputBufferMinSeconds: number; -} +}; export type RtmpInputStatsReport = { video: RtmpInputTrackStatsReport; audio: RtmpInputTrackStatsReport; -} +}; export type RtmpInputTrackStatsReport = { bitrate1Second: number; bitrate1Minute: number; -} +}; export type Mp4InputStatsReport = { video: Mp4InputTrackStatsReport; @@ -86,4 +86,4 @@ export type Mp4InputStatsReport = { export type Mp4InputTrackStatsReport = { bitrate1Second: number; bitrate1Minute: number; -} +}; diff --git a/ts/smelter/src/types/stats/output.ts b/ts/smelter/src/types/stats/output.ts index e69de29bb..1bcf818c1 100644 --- a/ts/smelter/src/types/stats/output.ts +++ b/ts/smelter/src/types/stats/output.ts @@ -0,0 +1,50 @@ +export type WhepOutputStatsReport = { + video: WhepOutputTrackStatsReport; + audio: WhepOutputTrackStatsReport; + connectedPeers: number; +}; +export type WhepOutputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +}; +export type WhipOutputStatsReport = { + video: WhipOutputTrackStatsReport; + audio: WhipOutputTrackStatsReport; + isConnected: boolean; +}; +export type WhipOutputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +}; +export type HlsOutputStatsReport = { + video: HlsOutputTrackStatsReport; + audio: HlsOutputTrackStatsReport; +}; +export type HlsOutputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +}; +export type Mp4OutputStatsReport = { + video: Mp4OutputTrackStatsReport; + audio: Mp4OutputTrackStatsReport; +}; +export type Mp4OutputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +}; +export type RtmpOutputStatsReport = { + video: RtmpOutputTrackStatsReport; + audio: RtmpOutputTrackStatsReport; +}; +export type RtmpOutputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +}; +export type RtpOutputStatsReport = { + video: RtpOutputTrackStatsReport; + audio: RtpOutputTrackStatsReport; +}; +export type RtpOutputTrackStatsReport = { + bitrate1Second: number; + bitrate1Minute: number; +}; From 008bac9a8790a13bb93817bdba304294bfdedc6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Thu, 19 Mar 2026 15:47:25 +0100 Subject: [PATCH 09/17] Correct imports in `smelter-node`, output types added to `stats.ts` --- ts/smelter-node/src/live/compositor.ts | 4 +- ts/smelter-node/src/offline/compositor.ts | 4 +- ts/smelter/src/types/stats.ts | 56 +++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/ts/smelter-node/src/live/compositor.ts b/ts/smelter-node/src/live/compositor.ts index 08b7e8553..15487e3cd 100644 --- a/ts/smelter-node/src/live/compositor.ts +++ b/ts/smelter-node/src/live/compositor.ts @@ -2,8 +2,8 @@ import type { ReactElement } from 'react'; import FormData from 'form-data'; import fetch from 'node-fetch'; import type { Renderers } from '@swmansion/smelter'; -import type { - SmelterManager, +import { + type SmelterManager, StateGuard, Smelter as CoreSmelter, type StatsResponse, diff --git a/ts/smelter-node/src/offline/compositor.ts b/ts/smelter-node/src/offline/compositor.ts index 56d37d0ec..22eda3f9a 100644 --- a/ts/smelter-node/src/offline/compositor.ts +++ b/ts/smelter-node/src/offline/compositor.ts @@ -1,8 +1,8 @@ import fetch from 'node-fetch'; import FormData from 'form-data'; import type { ReactElement } from 'react'; -import type { - SmelterManager, +import { + type SmelterManager, OfflineSmelter as CoreSmelter, StateGuard, type StatsResponse, diff --git a/ts/smelter/src/types/stats.ts b/ts/smelter/src/types/stats.ts index e69de29bb..91f02e091 100644 --- a/ts/smelter/src/types/stats.ts +++ b/ts/smelter/src/types/stats.ts @@ -0,0 +1,56 @@ +import type { + RtpInputStatsReport, + WhepInputStatsReport, + WhipInputStatsReport, + HlsInputStatsReport, + RtmpInputStatsReport, + Mp4InputStatsReport, +} from './stats/input.js'; +import type { + WhepOutputStatsReport, + WhipOutputStatsReport, + HlsOutputStatsReport, + Mp4OutputStatsReport, + RtmpOutputStatsReport, + RtpOutputStatsReport, +} from './stats/output.js'; + +export type InputStatsReport = + | { + rtp: RtpInputStatsReport; + } + | { + whip: WhipInputStatsReport; + } + | { + whep: WhepInputStatsReport; + } + | { + hls: HlsInputStatsReport; + } + | { + rtmp: RtmpInputStatsReport; + } + | { + mp4: Mp4InputStatsReport; + }; + +export type OutputStatsReport = + | { + whep: WhepOutputStatsReport; + } + | { + whip: WhipOutputStatsReport; + } + | { + hls: HlsOutputStatsReport; + } + | { + mp4: Mp4OutputStatsReport; + } + | { + rtmp: RtmpOutputStatsReport; + } + | { + rtp: RtpOutputStatsReport; + }; From 75dd486b28788165a9ce58cc46d121e623237721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Thu, 19 Mar 2026 16:00:38 +0100 Subject: [PATCH 10/17] Added `StatsReport` type --- ts/smelter-core/src/api.ts | 2 +- ts/smelter/src/types/stats.ts | 5 +++++ ts/smelter/src/types/stats/input.ts | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ts/smelter-core/src/api.ts b/ts/smelter-core/src/api.ts index d3bf7c06b..c3a999a89 100644 --- a/ts/smelter-core/src/api.ts +++ b/ts/smelter-core/src/api.ts @@ -32,7 +32,7 @@ export type RegisterOutputResponse = { }; export type StatsResponse = { - stats_json?: string; + stats_report?: string; }; export class ApiClient { diff --git a/ts/smelter/src/types/stats.ts b/ts/smelter/src/types/stats.ts index 91f02e091..a7ffe28f7 100644 --- a/ts/smelter/src/types/stats.ts +++ b/ts/smelter/src/types/stats.ts @@ -15,6 +15,11 @@ import type { RtpOutputStatsReport, } from './stats/output.js'; +export type StatsReport = { + inputs: Record; + outputs: Record; +}; + export type InputStatsReport = | { rtp: RtpInputStatsReport; diff --git a/ts/smelter/src/types/stats/input.ts b/ts/smelter/src/types/stats/input.ts index 410e6e0f3..becaae412 100644 --- a/ts/smelter/src/types/stats/input.ts +++ b/ts/smelter/src/types/stats/input.ts @@ -32,6 +32,7 @@ export type WhipInputStatsReport = { videoRtp: RtpJitterBufferStatsReport; audioRtp: RtpJitterBufferStatsReport; }; + export type WhepInputStatsReport = { videoRtp: RtpJitterBufferStatsReport; audioRtp: RtpJitterBufferStatsReport; From 184a3ac7da17081e0c4fd0b90f98678798690403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Thu, 19 Mar 2026 16:06:55 +0100 Subject: [PATCH 11/17] `StatsResponse` is now typed with `StatsReport` --- ts/examples/node-examples/src/audio.tsx | 11 ----------- ts/smelter-core/src/api.ts | 7 ++----- ts/smelter-core/src/index.ts | 8 +------- ts/smelter-core/src/live/compositor.ts | 6 +++--- ts/smelter-core/src/offline/compositor.ts | 6 +++--- ts/smelter-node/src/api.ts | 2 +- ts/smelter-node/src/live/compositor.ts | 11 +++-------- ts/smelter-node/src/offline/compositor.ts | 5 ++--- ts/smelter-web-client/src/api.ts | 2 +- ts/smelter-web-client/src/smelter/live.ts | 6 +++--- ts/smelter-web-client/src/smelter/offline.ts | 10 +++------- ts/smelter-web-wasm/src/compositor/compositor.ts | 5 +++-- ts/smelter/src/index.ts | 3 +++ ts/smelter/src/types/stats.ts | 4 ++-- 14 files changed, 30 insertions(+), 56 deletions(-) diff --git a/ts/examples/node-examples/src/audio.tsx b/ts/examples/node-examples/src/audio.tsx index 8a3202c88..066f6f1e5 100644 --- a/ts/examples/node-examples/src/audio.tsx +++ b/ts/examples/node-examples/src/audio.tsx @@ -4,10 +4,6 @@ import { downloadAllAssets, ffplayStartRtmpServerAsync } from './utils'; import path from 'path'; import { useState, useEffect } from 'react'; -function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - function ExampleApp() { const [streamWithAudio, setStream] = useState('input_1'); useEffect(() => { @@ -89,12 +85,5 @@ async function run() { }); await smelter.start(); - - while (true) { - await sleep(1000); - console.clear(); - let json_string = await smelter.stats(); - console.dir(json_string, { depth: null }); - } } void run(); diff --git a/ts/smelter-core/src/api.ts b/ts/smelter-core/src/api.ts index c3a999a89..c540812e9 100644 --- a/ts/smelter-core/src/api.ts +++ b/ts/smelter-core/src/api.ts @@ -3,6 +3,7 @@ import type { SmelterManager } from './smelterManager'; import type { RegisterOutputRequest } from './api/output'; import { inputRefIntoRawId, type InputRef, type RegisterInputRequest } from './api/input'; import { imageRefIntoRawId, type ImageRef } from './api/image'; +import { type StatsReport } from '@swmansion/smelter'; export { Api }; @@ -31,10 +32,6 @@ export type RegisterOutputResponse = { endpoint_route?: string; }; -export type StatsResponse = { - stats_report?: string; -}; - export class ApiClient { private serverManager: SmelterManager; @@ -156,7 +153,7 @@ export class ApiClient { }); } - public async stats(): Promise { + public async stats(): Promise { return this.serverManager.sendRequest({ method: 'GET', route: `/stats`, diff --git a/ts/smelter-core/src/index.ts b/ts/smelter-core/src/index.ts index 4b56b9940..f0401e31f 100644 --- a/ts/smelter-core/src/index.ts +++ b/ts/smelter-core/src/index.ts @@ -2,13 +2,7 @@ import * as Output from './api/output'; import * as Input from './api/input'; export { Output, Input }; -export { - ApiClient, - ApiRequest, - MultipartRequest, - RegisterInputResponse, - StatsResponse, -} from './api'; +export { ApiClient, ApiRequest, MultipartRequest, RegisterInputResponse } from './api'; export { Smelter } from './live/compositor'; export { OfflineSmelter } from './offline/compositor'; export { SmelterManager, SetupInstanceOptions } from './smelterManager'; diff --git a/ts/smelter-core/src/live/compositor.ts b/ts/smelter-core/src/live/compositor.ts index 5bf2028c3..7e331c34e 100644 --- a/ts/smelter-core/src/live/compositor.ts +++ b/ts/smelter-core/src/live/compositor.ts @@ -1,6 +1,6 @@ -import type { Renderers } from '@swmansion/smelter'; +import type { Renderers, StatsReport } from '@swmansion/smelter'; import { _smelterInternals } from '@swmansion/smelter'; -import type { RegisterInputResponse, RegisterOutputResponse, StatsResponse } from '../api'; +import type { RegisterInputResponse, RegisterOutputResponse } from '../api'; import { ApiClient } from '../api'; import Output from './output'; import type { SmelterManager } from '../smelterManager'; @@ -173,7 +173,7 @@ export class Smelter { handleEvent(this.store, this.outputs, event); } - public async stats(): Promise { + public async stats(): Promise { return this.api.stats(); } } diff --git a/ts/smelter-core/src/offline/compositor.ts b/ts/smelter-core/src/offline/compositor.ts index aa0cde0d6..f6da0ef14 100644 --- a/ts/smelter-core/src/offline/compositor.ts +++ b/ts/smelter-core/src/offline/compositor.ts @@ -1,6 +1,6 @@ -import type { Renderers } from '@swmansion/smelter'; +import type { Renderers, StatsReport } from '@swmansion/smelter'; import { _smelterInternals } from '@swmansion/smelter'; -import type { RegisterInputResponse, StatsResponse } from '../api'; +import type { RegisterInputResponse } from '../api'; import { ApiClient } from '../api'; import type { SmelterManager } from '../smelterManager'; import type { RegisterOutput } from '../api/output'; @@ -148,7 +148,7 @@ export class OfflineSmelter { } } - public async stats(): Promise { + public async stats(): Promise { return this.api.stats(); } } diff --git a/ts/smelter-node/src/api.ts b/ts/smelter-node/src/api.ts index 80192064a..e53ac9477 100644 --- a/ts/smelter-node/src/api.ts +++ b/ts/smelter-node/src/api.ts @@ -45,4 +45,4 @@ export type RegisterWhipServerInputResponse = { endpointRoute: string; }; -export type { StatsResponse } from '@swmansion/smelter-core'; +export type { StatsReport } from '@swmansion/smelter'; diff --git a/ts/smelter-node/src/live/compositor.ts b/ts/smelter-node/src/live/compositor.ts index 15487e3cd..f15f06b70 100644 --- a/ts/smelter-node/src/live/compositor.ts +++ b/ts/smelter-node/src/live/compositor.ts @@ -1,13 +1,8 @@ import type { ReactElement } from 'react'; import FormData from 'form-data'; import fetch from 'node-fetch'; -import type { Renderers } from '@swmansion/smelter'; -import { - type SmelterManager, - StateGuard, - Smelter as CoreSmelter, - type StatsResponse, -} from '@swmansion/smelter-core'; +import type { Renderers, StatsReport } from '@swmansion/smelter'; +import { type SmelterManager, StateGuard, Smelter as CoreSmelter } from '@swmansion/smelter-core'; import LocallySpawnedInstance from '../manager/locallySpawnedInstance'; import { createLogger } from '../logger'; @@ -184,7 +179,7 @@ export default class Smelter { }); } - public async stats(): Promise { + public async stats(): Promise { return await this.scheduler.run(async () => { return this.coreSmelter.stats(); }); diff --git a/ts/smelter-node/src/offline/compositor.ts b/ts/smelter-node/src/offline/compositor.ts index 22eda3f9a..b4f0f9133 100644 --- a/ts/smelter-node/src/offline/compositor.ts +++ b/ts/smelter-node/src/offline/compositor.ts @@ -5,9 +5,8 @@ import { type SmelterManager, OfflineSmelter as CoreSmelter, StateGuard, - type StatsResponse, } from '@swmansion/smelter-core'; -import type { Renderers } from '@swmansion/smelter'; +import type { Renderers, StatsReport } from '@swmansion/smelter'; import type { RegisterInput, @@ -110,7 +109,7 @@ export default class OfflineSmelter { }); } - public async stats(): Promise { + public async stats(): Promise { return await this.scheduler.run(async () => { return this.coreSmelter.stats(); }); diff --git a/ts/smelter-web-client/src/api.ts b/ts/smelter-web-client/src/api.ts index 3635bd5ce..67f17a394 100644 --- a/ts/smelter-web-client/src/api.ts +++ b/ts/smelter-web-client/src/api.ts @@ -45,4 +45,4 @@ export type RegisterWhipServerInputResponse = { endpointRoute: string; }; -export type { StatsResponse } from '@swmansion/smelter-core'; +export type { StatsReport } from '@swmansion/smelter'; diff --git a/ts/smelter-web-client/src/smelter/live.ts b/ts/smelter-web-client/src/smelter/live.ts index d1645fe5b..aca82aba8 100644 --- a/ts/smelter-web-client/src/smelter/live.ts +++ b/ts/smelter-web-client/src/smelter/live.ts @@ -1,7 +1,7 @@ import type { ReactElement } from 'react'; import { pino } from 'pino'; -import type { Renderers } from '@swmansion/smelter'; -import { Smelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; +import type { Renderers, StatsReport } from '@swmansion/smelter'; +import { Smelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; import type { RegisterInput, RegisterMp4InputResponse, @@ -184,7 +184,7 @@ export default class Smelter { }); } - public async stats(): Promise { + public async stats(): Promise { return await this.scheduler.run(async () => { return this.coreSmelter.stats(); }); diff --git a/ts/smelter-web-client/src/smelter/offline.ts b/ts/smelter-web-client/src/smelter/offline.ts index 23666d763..f8faea8b5 100644 --- a/ts/smelter-web-client/src/smelter/offline.ts +++ b/ts/smelter-web-client/src/smelter/offline.ts @@ -1,10 +1,6 @@ import type { ReactElement } from 'react'; -import { - OfflineSmelter as CoreSmelter, - StateGuard, - type StatsResponse, -} from '@swmansion/smelter-core'; -import type { Renderers } from '@swmansion/smelter'; +import { OfflineSmelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; +import type { Renderers, StatsReport } from '@swmansion/smelter'; import { pino } from 'pino'; import type { RegisterInput, @@ -114,7 +110,7 @@ export default class OfflineSmelter { }); } - public async stats(): Promise { + public async stats(): Promise { return await this.scheduler.run(async () => { return this.coreSmelter.stats(); }); diff --git a/ts/smelter-web-wasm/src/compositor/compositor.ts b/ts/smelter-web-wasm/src/compositor/compositor.ts index 706bbe5da..6a3b3e093 100644 --- a/ts/smelter-web-wasm/src/compositor/compositor.ts +++ b/ts/smelter-web-wasm/src/compositor/compositor.ts @@ -1,4 +1,5 @@ -import { Smelter as CoreSmelter, StateGuard, type StatsResponse } from '@swmansion/smelter-core'; +import { Smelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; +import type { StatsReport } from '@swmansion/smelter'; import type { ReactElement } from 'react'; import type { Logger } from 'pino'; import { pino } from 'pino'; @@ -169,7 +170,7 @@ export default class Smelter { }); } - public async stats(): Promise { + public async stats(): Promise { return await this.scheduler.run(async () => { assert(this.coreSmelter); return this.coreSmelter.stats(); diff --git a/ts/smelter/src/index.ts b/ts/smelter/src/index.ts index 34440cf43..0daba15cc 100644 --- a/ts/smelter/src/index.ts +++ b/ts/smelter/src/index.ts @@ -17,6 +17,7 @@ import { import Show, { ShowProps } from './components/Show.js'; import { SlideShow, Slide, SlideProps, SlideShowProps } from './components/SlideShow.js'; import Mp4, { Mp4Props } from './components/Mp4.js'; +import { StatsReport } from './types/stats.js'; export { RegisterRtpInput, @@ -72,3 +73,5 @@ export { export { useInputStreams, useAudioInput, useBlockingTask, useAfterTimestamp, useCurrentTimestamp }; export { ShaderParam, ShaderParamStructField, EasingFunction, Transition }; + +export { StatsReport }; diff --git a/ts/smelter/src/types/stats.ts b/ts/smelter/src/types/stats.ts index a7ffe28f7..0342f89b5 100644 --- a/ts/smelter/src/types/stats.ts +++ b/ts/smelter/src/types/stats.ts @@ -16,8 +16,8 @@ import type { } from './stats/output.js'; export type StatsReport = { - inputs: Record; - outputs: Record; + inputs?: Record; + outputs?: Record; }; export type InputStatsReport = From b6d2ca5c2452a289922d09b6079e16ddef4ae175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Fri, 20 Mar 2026 09:28:59 +0100 Subject: [PATCH 12/17] Correct type for rtp input fields --- ts/smelter/src/types/stats/input.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/smelter/src/types/stats/input.ts b/ts/smelter/src/types/stats/input.ts index becaae412..6979b686a 100644 --- a/ts/smelter/src/types/stats/input.ts +++ b/ts/smelter/src/types/stats/input.ts @@ -1,6 +1,6 @@ export type RtpInputStatsReport = { - videoRtp: string; - audioRtp: string; + videoRtp: RtpJitterBufferStatsReport; + audioRtp: RtpJitterBufferStatsReport; }; export type RtpJitterBufferStatsReport = { From 84ef2fd47b5fb4fd0f985e3ec9f999f0cbdbea14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Fri, 20 Mar 2026 09:31:10 +0100 Subject: [PATCH 13/17] Removed stats endpoint from wasm --- ts/smelter-web-wasm/src/compositor/compositor.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ts/smelter-web-wasm/src/compositor/compositor.ts b/ts/smelter-web-wasm/src/compositor/compositor.ts index 6a3b3e093..b3d1a8ea5 100644 --- a/ts/smelter-web-wasm/src/compositor/compositor.ts +++ b/ts/smelter-web-wasm/src/compositor/compositor.ts @@ -1,5 +1,4 @@ import { Smelter as CoreSmelter, StateGuard } from '@swmansion/smelter-core'; -import type { StatsReport } from '@swmansion/smelter'; import type { ReactElement } from 'react'; import type { Logger } from 'pino'; import { pino } from 'pino'; @@ -170,12 +169,6 @@ export default class Smelter { }); } - public async stats(): Promise { - return await this.scheduler.run(async () => { - assert(this.coreSmelter); - return this.coreSmelter.stats(); - }); - } } function resolveFramerate(framerate?: number | Framerate): Framerate { From f397a399d8765bb9930277e8b21f572ccd4f056a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Fri, 20 Mar 2026 09:53:04 +0100 Subject: [PATCH 14/17] WIP: test example --- ts/examples/node-examples/src/audio.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ts/examples/node-examples/src/audio.tsx b/ts/examples/node-examples/src/audio.tsx index 066f6f1e5..8879f43f6 100644 --- a/ts/examples/node-examples/src/audio.tsx +++ b/ts/examples/node-examples/src/audio.tsx @@ -4,6 +4,10 @@ import { downloadAllAssets, ffplayStartRtmpServerAsync } from './utils'; import path from 'path'; import { useState, useEffect } from 'react'; +const sleep = (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + function ExampleApp() { const [streamWithAudio, setStream] = useState('input_1'); useEffect(() => { @@ -85,5 +89,13 @@ async function run() { }); await smelter.start(); + + while (true) { + await sleep(1000); + console.clear() + let stats = await smelter.stats(); + let input_1 = stats.inputs?.['global:input_1-1']; + console.dir(input_1, { depth: null }); + } } void run(); From 52fe41a04fc31d85049ea1a1347770db6a51308c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Fri, 20 Mar 2026 10:23:44 +0100 Subject: [PATCH 15/17] Converting stats from api to ts type --- ts/examples/node-examples/src/audio.tsx | 4 +- ts/smelter-core/src/api.ts | 8 +- ts/smelter-core/src/api/stats.ts | 62 +++++++++ ts/smelter-core/src/api/stats/inputs.ts | 121 ++++++++++++++++++ ts/smelter-core/src/api/stats/outputs.ts | 63 +++++++++ .../src/compositor/compositor.ts | 1 - ts/smelter/src/index.ts | 60 ++++++++- 7 files changed, 311 insertions(+), 8 deletions(-) create mode 100644 ts/smelter-core/src/api/stats.ts create mode 100644 ts/smelter-core/src/api/stats/inputs.ts create mode 100644 ts/smelter-core/src/api/stats/outputs.ts diff --git a/ts/examples/node-examples/src/audio.tsx b/ts/examples/node-examples/src/audio.tsx index 8879f43f6..a723d7b8b 100644 --- a/ts/examples/node-examples/src/audio.tsx +++ b/ts/examples/node-examples/src/audio.tsx @@ -5,7 +5,7 @@ import path from 'path'; import { useState, useEffect } from 'react'; const sleep = (ms: number): Promise => { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); }; function ExampleApp() { @@ -92,7 +92,7 @@ async function run() { while (true) { await sleep(1000); - console.clear() + console.clear(); let stats = await smelter.stats(); let input_1 = stats.inputs?.['global:input_1-1']; console.dir(input_1, { depth: null }); diff --git a/ts/smelter-core/src/api.ts b/ts/smelter-core/src/api.ts index c540812e9..318069f9e 100644 --- a/ts/smelter-core/src/api.ts +++ b/ts/smelter-core/src/api.ts @@ -1,9 +1,10 @@ import { Api } from '@swmansion/smelter'; +import type { StatsReport } from '@swmansion/smelter'; import type { SmelterManager } from './smelterManager'; import type { RegisterOutputRequest } from './api/output'; import { inputRefIntoRawId, type InputRef, type RegisterInputRequest } from './api/input'; import { imageRefIntoRawId, type ImageRef } from './api/image'; -import { type StatsReport } from '@swmansion/smelter'; +import { fromApiStatsReport } from './api/stats'; export { Api }; @@ -154,9 +155,10 @@ export class ApiClient { } public async stats(): Promise { - return this.serverManager.sendRequest({ + const response = (await this.serverManager.sendRequest({ method: 'GET', route: `/stats`, - }); + })) as Api.StatsReport; + return fromApiStatsReport(response); } } diff --git a/ts/smelter-core/src/api/stats.ts b/ts/smelter-core/src/api/stats.ts new file mode 100644 index 000000000..3b22db011 --- /dev/null +++ b/ts/smelter-core/src/api/stats.ts @@ -0,0 +1,62 @@ +import type { Api, StatsReport, InputStatsReport, OutputStatsReport } from '@swmansion/smelter'; +import { + fromApiRtpInputStats, + fromApiWhipInputStats, + fromApiWhepInputStats, + fromApiHlsInputStats, + fromApiRtmpInputStats, + fromApiMp4InputStats, +} from './stats/inputs.js'; +import { + fromApiWhepOutputStats, + fromApiWhipOutputStats, + fromApiHlsOutputStats, + fromApiMp4OutputStats, + fromApiRtmpOutputStats, + fromApiRtpOutputStats, +} from './stats/outputs.js'; + +export function fromApiStatsReport(report: Api.StatsReport): StatsReport { + return { + inputs: Object.fromEntries( + Object.entries(report.inputs).map(([id, input]) => [id, fromApiInputStatsReport(input)]) + ), + outputs: Object.fromEntries( + Object.entries(report.outputs).map(([id, output]) => [id, fromApiOutputStatsReport(output)]) + ), + }; +} + +function fromApiInputStatsReport(report: Api.InputStatsReport): InputStatsReport { + if ('rtp' in report) { + return { rtp: fromApiRtpInputStats(report.rtp) }; + } else if ('whip' in report) { + return { whip: fromApiWhipInputStats(report.whip) }; + } else if ('whep' in report) { + return { whep: fromApiWhepInputStats(report.whep) }; + } else if ('hls' in report) { + return { hls: fromApiHlsInputStats(report.hls) }; + } else if ('rtmp' in report) { + return { rtmp: fromApiRtmpInputStats(report.rtmp) }; + } else if ('mp4' in report) { + return { mp4: fromApiMp4InputStats(report.mp4) }; + } + throw new Error(`Unknown input stats report type: ${JSON.stringify(report)}`); +} + +function fromApiOutputStatsReport(report: Api.OutputStatsReport): OutputStatsReport { + if ('whep' in report) { + return { whep: fromApiWhepOutputStats(report.whep) }; + } else if ('whip' in report) { + return { whip: fromApiWhipOutputStats(report.whip) }; + } else if ('hls' in report) { + return { hls: fromApiHlsOutputStats(report.hls) }; + } else if ('mp4' in report) { + return { mp4: fromApiMp4OutputStats(report.mp4) }; + } else if ('rtmp' in report) { + return { rtmp: fromApiRtmpOutputStats(report.rtmp) }; + } else if ('rtp' in report) { + return { rtp: fromApiRtpOutputStats(report.rtp) }; + } + throw new Error(`Unknown output stats report type: ${JSON.stringify(report)}`); +} diff --git a/ts/smelter-core/src/api/stats/inputs.ts b/ts/smelter-core/src/api/stats/inputs.ts new file mode 100644 index 000000000..6f246609e --- /dev/null +++ b/ts/smelter-core/src/api/stats/inputs.ts @@ -0,0 +1,121 @@ +import type { + Api, + RtpInputStatsReport, + RtpJitterBufferStatsReport, + RtpJitterBufferSlidingWindowStatsReport, + WhipInputStatsReport, + WhepInputStatsReport, + HlsInputStatsReport, + HlsInputTrackStatsReport, + HlsInputTrackSlidingWindowStatsReport, + RtmpInputStatsReport, + RtmpInputTrackStatsReport, + Mp4InputStatsReport, + Mp4InputTrackStatsReport, +} from '@swmansion/smelter'; + +export function fromApiRtpInputStats(report: Api.RtpInputStatsReport): RtpInputStatsReport { + return { + videoRtp: fromApiRtpJitterBufferStats(report.video_rtp), + audioRtp: fromApiRtpJitterBufferStats(report.audio_rtp), + }; +} + +export function fromApiWhipInputStats(report: Api.WhipInputStatsReport): WhipInputStatsReport { + return { + videoRtp: fromApiRtpJitterBufferStats(report.video_rtp), + audioRtp: fromApiRtpJitterBufferStats(report.audio_rtp), + }; +} + +export function fromApiWhepInputStats(report: Api.WhepInputStatsReport): WhepInputStatsReport { + return { + videoRtp: fromApiRtpJitterBufferStats(report.video_rtp), + audioRtp: fromApiRtpJitterBufferStats(report.audio_rtp), + }; +} + +export function fromApiHlsInputStats(report: Api.HlsInputStatsReport): HlsInputStatsReport { + return { + video: fromApiHlsInputTrackStats(report.video), + audio: fromApiHlsInputTrackStats(report.audio), + corruptedPacketsReceived: report.corrupted_packets_received, + corruptedPacketsReceivedLast10Seconds: report.corrupted_packets_received_last_10_seconds, + }; +} + +export function fromApiRtmpInputStats(report: Api.RtmpInputStatsReport): RtmpInputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + }; +} + +export function fromApiMp4InputStats(report: Api.Mp4InputStatsReport): Mp4InputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + }; +} + +function fromApiRtpJitterBufferStats( + report: Api.RtpJitterBufferStatsReport +): RtpJitterBufferStatsReport { + return { + packetsLost: report.packets_lost, + packetsReceived: report.packets_received, + bitrate1Second: report.bitrate_1_second, + bitrate1Minute: report.bitrate_1_minute, + last10Seconds: fromApiRtpJitterBufferSlidingWindowStats(report.last_10_seconds), + }; +} + +function fromApiRtpJitterBufferSlidingWindowStats( + report: Api.RtpJitterBufferSlidingWindowStatsReport +): RtpJitterBufferSlidingWindowStatsReport { + return { + packetsLost: report.packets_lost, + packetsReceived: report.packets_received, + effectiveBufferAvgSeconds: report.effective_buffer_avg_seconds, + effectiveBufferMaxSeconds: report.effective_buffer_max_seconds, + effectiveBufferMinSeconds: report.effective_buffer_min_seconds, + inputBufferAvgSeconds: report.input_buffer_avg_seconds, + inputBufferMaxSeconds: report.input_buffer_max_seconds, + inputBufferMinSeconds: report.input_buffer_min_seconds, + }; +} + +function fromApiHlsInputTrackStats(report: Api.HlsInputTrackStatsReport): HlsInputTrackStatsReport { + return { + packetsReceived: report.packets_received, + discontinuitiesDetected: report.discontinuities_detected, + bitrate1Second: report.bitrate_1_second, + bitrate1Minute: report.bitrate_1_minute, + last10Seconds: fromApiHlsInputTrackSlidingWindowStats(report.last_10_seconds), + }; +} + +function fromApiHlsInputTrackSlidingWindowStats( + report: Api.HlsInputTrackSlidingWindowStatsReport +): HlsInputTrackSlidingWindowStatsReport { + return { + packetsReceived: report.packets_received, + discontinuitiesDetected: report.discontinuities_detected, + effectiveBufferAvgSeconds: report.effective_buffer_avg_seconds, + effectiveBufferMaxSeconds: report.effective_buffer_max_seconds, + effectiveBufferMinSeconds: report.effective_buffer_min_seconds, + inputBufferAvgSeconds: report.input_buffer_avg_seconds, + inputBufferMaxSeconds: report.input_buffer_max_seconds, + inputBufferMinSeconds: report.input_buffer_min_seconds, + }; +} + +function fromApiBitrateTrackStats(report: { + bitrate_1_second: number; + bitrate_1_minute: number; +}): RtmpInputTrackStatsReport & Mp4InputTrackStatsReport { + return { + bitrate1Second: report.bitrate_1_second, + bitrate1Minute: report.bitrate_1_minute, + }; +} diff --git a/ts/smelter-core/src/api/stats/outputs.ts b/ts/smelter-core/src/api/stats/outputs.ts new file mode 100644 index 000000000..8fc30a874 --- /dev/null +++ b/ts/smelter-core/src/api/stats/outputs.ts @@ -0,0 +1,63 @@ +import type { + Api, + WhepOutputStatsReport, + WhipOutputStatsReport, + HlsOutputStatsReport, + Mp4OutputStatsReport, + RtmpOutputStatsReport, + RtpOutputStatsReport, +} from '@swmansion/smelter'; + +export function fromApiWhepOutputStats(report: Api.WhepOutputStatsReport): WhepOutputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + connectedPeers: report.connected_peers, + }; +} + +export function fromApiWhipOutputStats(report: Api.WhipOutputStatsReport): WhipOutputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + isConnected: report.is_connected, + }; +} + +export function fromApiHlsOutputStats(report: Api.HlsOutputStatsReport): HlsOutputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + }; +} + +export function fromApiMp4OutputStats(report: Api.Mp4OutputStatsReport): Mp4OutputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + }; +} + +export function fromApiRtmpOutputStats(report: Api.RtmpOutputStatsReport): RtmpOutputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + }; +} + +export function fromApiRtpOutputStats(report: Api.RtpOutputStatsReport): RtpOutputStatsReport { + return { + video: fromApiBitrateTrackStats(report.video), + audio: fromApiBitrateTrackStats(report.audio), + }; +} + +function fromApiBitrateTrackStats(report: { bitrate_1_second: number; bitrate_1_minute: number }): { + bitrate1Second: number; + bitrate1Minute: number; +} { + return { + bitrate1Second: report.bitrate_1_second, + bitrate1Minute: report.bitrate_1_minute, + }; +} diff --git a/ts/smelter-web-wasm/src/compositor/compositor.ts b/ts/smelter-web-wasm/src/compositor/compositor.ts index b3d1a8ea5..1c640a0df 100644 --- a/ts/smelter-web-wasm/src/compositor/compositor.ts +++ b/ts/smelter-web-wasm/src/compositor/compositor.ts @@ -168,7 +168,6 @@ export default class Smelter { await this.coreSmelter?.terminate(); }); } - } function resolveFramerate(framerate?: number | Framerate): Framerate { diff --git a/ts/smelter/src/index.ts b/ts/smelter/src/index.ts index 0daba15cc..c7635dc45 100644 --- a/ts/smelter/src/index.ts +++ b/ts/smelter/src/index.ts @@ -17,7 +17,35 @@ import { import Show, { ShowProps } from './components/Show.js'; import { SlideShow, Slide, SlideProps, SlideShowProps } from './components/SlideShow.js'; import Mp4, { Mp4Props } from './components/Mp4.js'; -import { StatsReport } from './types/stats.js'; +import { StatsReport, InputStatsReport, OutputStatsReport } from './types/stats.js'; +import { + RtpInputStatsReport, + RtpJitterBufferStatsReport, + RtpJitterBufferSlidingWindowStatsReport, + WhipInputStatsReport, + WhepInputStatsReport, + HlsInputStatsReport, + HlsInputTrackStatsReport, + HlsInputTrackSlidingWindowStatsReport, + RtmpInputStatsReport, + RtmpInputTrackStatsReport, + Mp4InputStatsReport, + Mp4InputTrackStatsReport, +} from './types/stats/input.js'; +import { + WhepOutputStatsReport, + WhepOutputTrackStatsReport, + WhipOutputStatsReport, + WhipOutputTrackStatsReport, + HlsOutputStatsReport, + HlsOutputTrackStatsReport, + Mp4OutputStatsReport, + Mp4OutputTrackStatsReport, + RtmpOutputStatsReport, + RtmpOutputTrackStatsReport, + RtpOutputStatsReport, + RtpOutputTrackStatsReport, +} from './types/stats/output.js'; export { RegisterRtpInput, @@ -74,4 +102,32 @@ export { useInputStreams, useAudioInput, useBlockingTask, useAfterTimestamp, use export { ShaderParam, ShaderParamStructField, EasingFunction, Transition }; -export { StatsReport }; +export { + StatsReport, + InputStatsReport, + OutputStatsReport, + RtpInputStatsReport, + RtpJitterBufferStatsReport, + RtpJitterBufferSlidingWindowStatsReport, + WhipInputStatsReport, + WhepInputStatsReport, + HlsInputStatsReport, + HlsInputTrackStatsReport, + HlsInputTrackSlidingWindowStatsReport, + RtmpInputStatsReport, + RtmpInputTrackStatsReport, + Mp4InputStatsReport, + Mp4InputTrackStatsReport, + WhepOutputStatsReport, + WhepOutputTrackStatsReport, + WhipOutputStatsReport, + WhipOutputTrackStatsReport, + HlsOutputStatsReport, + HlsOutputTrackStatsReport, + Mp4OutputStatsReport, + Mp4OutputTrackStatsReport, + RtmpOutputStatsReport, + RtmpOutputTrackStatsReport, + RtpOutputStatsReport, + RtpOutputTrackStatsReport, +}; From 8039dfd32d7cfee0d0f23b06e0877c995b891c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Fri, 20 Mar 2026 10:27:59 +0100 Subject: [PATCH 16/17] Restored examples to match `origin/master` --- ts/examples/node-examples/src/audio.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ts/examples/node-examples/src/audio.tsx b/ts/examples/node-examples/src/audio.tsx index a723d7b8b..066f6f1e5 100644 --- a/ts/examples/node-examples/src/audio.tsx +++ b/ts/examples/node-examples/src/audio.tsx @@ -4,10 +4,6 @@ import { downloadAllAssets, ffplayStartRtmpServerAsync } from './utils'; import path from 'path'; import { useState, useEffect } from 'react'; -const sleep = (ms: number): Promise => { - return new Promise(resolve => setTimeout(resolve, ms)); -}; - function ExampleApp() { const [streamWithAudio, setStream] = useState('input_1'); useEffect(() => { @@ -89,13 +85,5 @@ async function run() { }); await smelter.start(); - - while (true) { - await sleep(1000); - console.clear(); - let stats = await smelter.stats(); - let input_1 = stats.inputs?.['global:input_1-1']; - console.dir(input_1, { depth: null }); - } } void run(); From b68e6690643c85a675ed951e5ac270acf1386ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20R=C4=99kas?= Date: Fri, 20 Mar 2026 10:43:03 +0100 Subject: [PATCH 17/17] Small modification of `.claude` --- .claude/CLAUDE.md | 2 +- .claude/skills/api-change/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 96ef1bcc8..d3a479bc4 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -33,4 +33,4 @@ HTTP API `smelter` crate → `smelter-api` (parse) → `smelter-core` (pipeline: ## API Changes -After modifying types in `smelter-api`, use `/api-change` to run the full generation and validation workflow. +After modifying types in `smelter-api` or types in `smelter-core::stats`, use `/api-change` to run the full generation and validation workflow. diff --git a/.claude/skills/api-change/SKILL.md b/.claude/skills/api-change/SKILL.md index 0921ec184..b505c776e 100644 --- a/.claude/skills/api-change/SKILL.md +++ b/.claude/skills/api-change/SKILL.md @@ -21,7 +21,7 @@ Run the API change workflow. All steps must pass before the change is considered This generates `ts/smelter/src/api.generated.ts`. 4. **Build the TypeScript SDK to verify compatibility** - Run in `./ts`: `pnpm build:all` + Run in `./ts`: `pnpm i && pnpm build:all` 5. **Show a summary of all generated/changed files** so the user can review what was affected.