From a2e1565be8e004ff4d8c92114d11dcbf09b713e1 Mon Sep 17 00:00:00 2001 From: Wojciech Kozyra Date: Tue, 21 Jan 2025 11:02:20 +0100 Subject: [PATCH] [web-wasm] Refactor to Web Worker (add camera) --- ts/@live-compositor/core/src/api/input.ts | 21 +- .../core/src/live/compositor.ts | 2 +- .../core/src/offline/compositor.ts | 12 +- ts/@live-compositor/node/src/utils.ts | 4 +- .../src/{ => compositor}/compositor.ts | 43 +- .../web-wasm/src/compositor/input.ts | 22 + .../web-wasm/src/compositor/output.ts | 24 + .../src/{ => compositor}/renderers.ts | 0 .../web-wasm/src/eventSender.ts | 35 +- ts/@live-compositor/web-wasm/src/index.ts | 5 +- .../web-wasm/src/input/decoder/h264Decoder.ts | 55 -- .../web-wasm/src/input/input.ts | 98 --- .../web-wasm/src/input/inputFrameProducer.ts | 37 - .../web-wasm/src/input/mp4/demuxer.ts | 121 --- .../web-wasm/src/input/mp4/source.ts | 73 -- .../input/producer/decodingFrameProducer.ts | 155 ---- .../web-wasm/src/input/registerInput.ts | 15 - .../web-wasm/src/input/source.ts | 31 - .../web-wasm/src/manager/wasmInstance.ts | 187 ----- .../web-wasm/src/output/registerOutput.ts | 27 - ts/@live-compositor/web-wasm/src/queue.ts | 113 --- ts/@live-compositor/web-wasm/src/utils.ts | 13 +- .../web-wasm/src/wasmInstance.ts | 243 ++++++ .../web-wasm/src/worker/bridge.ts | 115 +++ .../src/worker/input/MediaStreamInput.ts | 94 +++ .../web-wasm/src/worker/input/QueuedInput.ts | 173 ++++ .../web-wasm/src/worker/input/decoder.ts | 95 +++ .../web-wasm/src/{ => worker}/input/frame.ts | 35 +- .../web-wasm/src/worker/input/input.ts | 68 ++ .../src/worker/input/source/Mp4Demuxer.ts | 177 ++++ .../src/worker/input/source/Mp4Source.ts | 51 ++ .../mp4 => worker/input/source}/mp4box.d.ts | 0 .../src/{ => worker}/output/canvas.ts | 9 +- .../src/{ => worker}/output/output.ts | 4 +- .../web-wasm/src/{ => worker}/output/sink.ts | 0 .../web-wasm/src/worker/pipeline.ts | 108 +++ .../web-wasm/src/worker/queue.ts | 120 +++ .../web-wasm/src/worker/runWorker.ts | 49 ++ ts/@live-compositor/web-wasm/src/workerApi.ts | 94 +++ ts/examples/vite-browser-render/src/App.tsx | 30 +- .../src/components/CompositorCanvas.tsx | 11 +- .../src/examples/CameraExample.tsx | 36 + .../src/examples/ComponentMp4Example.tsx | 46 ++ ...mpleMp4Example.tsx => InputMp4Example.tsx} | 4 +- .../src/examples/ScreenCaptureExample.tsx | 36 + ts/package.json | 2 +- ts/pnpm-lock.yaml | 776 ++++++++---------- 47 files changed, 2065 insertions(+), 1404 deletions(-) rename ts/@live-compositor/web-wasm/src/{ => compositor}/compositor.ts (73%) create mode 100644 ts/@live-compositor/web-wasm/src/compositor/input.ts create mode 100644 ts/@live-compositor/web-wasm/src/compositor/output.ts rename ts/@live-compositor/web-wasm/src/{ => compositor}/renderers.ts (100%) delete mode 100644 ts/@live-compositor/web-wasm/src/input/decoder/h264Decoder.ts delete mode 100644 ts/@live-compositor/web-wasm/src/input/input.ts delete mode 100644 ts/@live-compositor/web-wasm/src/input/inputFrameProducer.ts delete mode 100644 ts/@live-compositor/web-wasm/src/input/mp4/demuxer.ts delete mode 100644 ts/@live-compositor/web-wasm/src/input/mp4/source.ts delete mode 100644 ts/@live-compositor/web-wasm/src/input/producer/decodingFrameProducer.ts delete mode 100644 ts/@live-compositor/web-wasm/src/input/registerInput.ts delete mode 100644 ts/@live-compositor/web-wasm/src/input/source.ts delete mode 100644 ts/@live-compositor/web-wasm/src/manager/wasmInstance.ts delete mode 100644 ts/@live-compositor/web-wasm/src/output/registerOutput.ts delete mode 100644 ts/@live-compositor/web-wasm/src/queue.ts create mode 100644 ts/@live-compositor/web-wasm/src/wasmInstance.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/bridge.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/input/MediaStreamInput.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/input/QueuedInput.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/input/decoder.ts rename ts/@live-compositor/web-wasm/src/{ => worker}/input/frame.ts (73%) create mode 100644 ts/@live-compositor/web-wasm/src/worker/input/input.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Demuxer.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Source.ts rename ts/@live-compositor/web-wasm/src/{input/mp4 => worker/input/source}/mp4box.d.ts (100%) rename ts/@live-compositor/web-wasm/src/{ => worker}/output/canvas.ts (56%) rename ts/@live-compositor/web-wasm/src/{ => worker}/output/output.ts (82%) rename ts/@live-compositor/web-wasm/src/{ => worker}/output/sink.ts (100%) create mode 100644 ts/@live-compositor/web-wasm/src/worker/pipeline.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/queue.ts create mode 100644 ts/@live-compositor/web-wasm/src/worker/runWorker.ts create mode 100644 ts/@live-compositor/web-wasm/src/workerApi.ts create mode 100644 ts/examples/vite-browser-render/src/examples/CameraExample.tsx create mode 100644 ts/examples/vite-browser-render/src/examples/ComponentMp4Example.tsx rename ts/examples/vite-browser-render/src/examples/{SimpleMp4Example.tsx => InputMp4Example.tsx} (96%) create mode 100644 ts/examples/vite-browser-render/src/examples/ScreenCaptureExample.tsx diff --git a/ts/@live-compositor/core/src/api/input.ts b/ts/@live-compositor/core/src/api/input.ts index 13c1df70c..1d2a5af9f 100644 --- a/ts/@live-compositor/core/src/api/input.ts +++ b/ts/@live-compositor/core/src/api/input.ts @@ -2,7 +2,14 @@ import type { Api } from '../api.js'; import type { RegisterMp4Input, RegisterRtpInput, Inputs } from 'live-compositor'; import { _liveCompositorInternals } from 'live-compositor'; -export type RegisterInputRequest = Api.RegisterInput; +/** + * It represents HTTP request that can be sent to + * to compositor, but also additional variants that are specific to WASM like camera + */ +export type RegisterInputRequest = + | Api.RegisterInput + | { type: 'camera' } + | { type: 'screen_capture' }; export type InputRef = _liveCompositorInternals.InputRef; export const inputRefIntoRawId = _liveCompositorInternals.inputRefIntoRawId; @@ -10,13 +17,23 @@ export const parseInputRef = _liveCompositorInternals.parseInputRef; export type RegisterInput = | ({ type: 'rtp_stream' } & RegisterRtpInput) - | ({ type: 'mp4' } & RegisterMp4Input); + | ({ type: 'mp4' } & RegisterMp4Input) + | { type: 'camera' } + | { type: 'screen_capture' }; +/** + * Converts object passed by user (or modified by platform specific interface) into + * HTTP request + */ export function intoRegisterInput(input: RegisterInput): RegisterInputRequest { if (input.type === 'mp4') { return intoMp4RegisterInput(input); } else if (input.type === 'rtp_stream') { return intoRtpRegisterInput(input); + } else if (input.type === 'camera') { + return { type: 'camera' }; + } else if (input.type === 'screen_capture') { + return { type: 'screen_capture' }; } else { throw new Error(`Unknown input type ${(input as any).type}`); } diff --git a/ts/@live-compositor/core/src/live/compositor.ts b/ts/@live-compositor/core/src/live/compositor.ts index 144849f9f..a6e2633b2 100644 --- a/ts/@live-compositor/core/src/live/compositor.ts +++ b/ts/@live-compositor/core/src/live/compositor.ts @@ -5,7 +5,6 @@ import Output from './output.js'; import type { CompositorManager } from '../compositorManager.js'; import type { RegisterOutput } from '../api/output.js'; import { intoRegisterOutput } from '../api/output.js'; -import type { RegisterInput } from '../api/input.js'; import { intoRegisterInput } from '../api/input.js'; import { parseEvent } from '../event.js'; import { intoRegisterImage, intoRegisterWebRenderer } from '../api/renderer.js'; @@ -13,6 +12,7 @@ import { handleEvent } from './event.js'; import type { ReactElement } from 'react'; import type { Logger } from 'pino'; import type { ImageRef } from '../api/image.js'; +import type { RegisterInput } from '../index.js'; export class LiveCompositor { private manager: CompositorManager; diff --git a/ts/@live-compositor/core/src/offline/compositor.ts b/ts/@live-compositor/core/src/offline/compositor.ts index 7033e6d0f..ea9703047 100644 --- a/ts/@live-compositor/core/src/offline/compositor.ts +++ b/ts/@live-compositor/core/src/offline/compositor.ts @@ -88,6 +88,8 @@ export class OfflineCompositor { const inputRef = { type: 'global', id: inputId } as const; const result = await this.api.registerInput(inputRef, intoRegisterInput(request)); + const offsetMs = 'offsetMs' in request && request.offsetMs ? request.offsetMs : 0; + if (request.type === 'mp4' && request.loop) { this.store.addInput({ inputId, @@ -98,18 +100,18 @@ export class OfflineCompositor { } else { this.store.addInput({ inputId, - offsetMs: request.offsetMs ?? 0, + offsetMs: offsetMs ?? 0, videoDurationMs: result.video_duration_ms, audioDurationMs: result.audio_duration_ms, }); - if (request.offsetMs) { - this.inputTimestamps.push(request.offsetMs); + if (offsetMs) { + this.inputTimestamps.push(offsetMs); } if (result.video_duration_ms) { - this.inputTimestamps.push((request.offsetMs ?? 0) + result.video_duration_ms); + this.inputTimestamps.push((offsetMs ?? 0) + result.video_duration_ms); } if (result.audio_duration_ms) { - this.inputTimestamps.push((request.offsetMs ?? 0) + result.audio_duration_ms); + this.inputTimestamps.push((offsetMs ?? 0) + result.audio_duration_ms); } } return result; diff --git a/ts/@live-compositor/node/src/utils.ts b/ts/@live-compositor/node/src/utils.ts index cd092bd58..04bf12edf 100644 --- a/ts/@live-compositor/node/src/utils.ts +++ b/ts/@live-compositor/node/src/utils.ts @@ -1,8 +1,8 @@ -export async function sleep(timeout_ms: number): Promise { +export async function sleep(timeoutMs: number): Promise { await new Promise(res => { setTimeout(() => { res(); - }, timeout_ms); + }, timeoutMs); }); } diff --git a/ts/@live-compositor/web-wasm/src/compositor.ts b/ts/@live-compositor/web-wasm/src/compositor/compositor.ts similarity index 73% rename from ts/@live-compositor/web-wasm/src/compositor.ts rename to ts/@live-compositor/web-wasm/src/compositor/compositor.ts index 5f54fb0b1..59043f930 100644 --- a/ts/@live-compositor/web-wasm/src/compositor.ts +++ b/ts/@live-compositor/web-wasm/src/compositor/compositor.ts @@ -1,15 +1,14 @@ -import { loadWasmModule, Renderer } from '@live-compositor/browser-render'; import { LiveCompositor as CoreLiveCompositor } from '@live-compositor/core'; -import WasmInstance from './manager/wasmInstance'; -import type { RegisterOutput } from './output/registerOutput'; -import { intoRegisterOutput } from './output/registerOutput'; -import type { RegisterInput } from './input/registerInput'; -import { intoRegisterInput } from './input/registerInput'; -import type { RegisterImage } from './renderers'; import type { ReactElement } from 'react'; import type { Logger } from 'pino'; import { pino } from 'pino'; -import { assert } from './utils'; +import { assert } from '../utils'; +import type { RegisterOutput } from './output'; +import { intoRegisterOutput } from './output'; +import type { RegisterInput } from './input'; +import { intoRegisterInput } from './input'; +import WasmInstance from '../wasmInstance'; +import type { RegisterImage } from './renderers'; export type LiveCompositorOptions = { framerate?: Framerate; @@ -34,9 +33,8 @@ export function setWasmBundleUrl(url: string) { export default class LiveCompositor { private coreCompositor?: CoreLiveCompositor; private instance?: WasmInstance; - private renderer?: Renderer; private options: LiveCompositorOptions; - private logger: Logger = pino({ level: 'warn' }); + private logger: Logger = pino({ level: 'debug' }); public constructor(options: LiveCompositorOptions) { this.options = options; @@ -47,13 +45,11 @@ export default class LiveCompositor { * Outputs won't produce any results until `start()` is called. */ public async init(): Promise { - await ensureWasmModuleLoaded(); - this.renderer = await Renderer.create({ - streamFallbackTimeoutMs: this.options.streamFallbackTimeoutMs ?? 500, - }); + assert(wasmBundleUrl, 'Location of WASM bundle is not defined, call setWasmBundleUrl() first.'); this.instance = new WasmInstance({ - renderer: this.renderer!, framerate: this.options.framerate ?? { num: 30, den: 1 }, + wasmBundleUrl, + logger: this.logger.child({ element: 'wasmInstance' }), }); this.coreCompositor = new CoreLiveCompositor(this.instance, this.logger); @@ -94,9 +90,9 @@ export default class LiveCompositor { await this.coreCompositor.unregisterImage(imageId); } - public async registerFont(fontUrl: string): Promise { - assert(this.renderer); - await this.renderer.registerFont(fontUrl); + public async registerFont(_fontUrl: string): Promise { + //assert(this.renderer); + //await this.renderer.registerFont(fontUrl); } /** @@ -114,14 +110,3 @@ export default class LiveCompositor { await this.instance?.terminate(); } } - -const ensureWasmModuleLoaded = (() => { - let loadedState: Promise | undefined = undefined; - return async () => { - assert(wasmBundleUrl, 'Location of WASM bundle is not defined, call setWasmBundleUrl() first.'); - if (!loadedState) { - loadedState = loadWasmModule(wasmBundleUrl); - } - await loadedState; - }; -})(); diff --git a/ts/@live-compositor/web-wasm/src/compositor/input.ts b/ts/@live-compositor/web-wasm/src/compositor/input.ts new file mode 100644 index 000000000..a451687d8 --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/compositor/input.ts @@ -0,0 +1,22 @@ +import type { RegisterInput as CoreRegisterInput } from '@live-compositor/core'; + +export type RegisterInput = + | ({ type: 'mp4' } & RegisterMP4Input) + | { type: 'camera' } + | { type: 'screen_capture' }; + +export type RegisterMP4Input = { + url: string; +}; + +export function intoRegisterInput(input: RegisterInput): CoreRegisterInput { + if (input.type === 'mp4') { + return { type: 'mp4', url: input.url }; + } else if (input.type === 'camera') { + return { type: 'camera' }; + } else if (input.type === 'screen_capture') { + return { type: 'screen_capture' }; + } else { + throw new Error(`Unknown input type ${(input as any).type}`); + } +} diff --git a/ts/@live-compositor/web-wasm/src/compositor/output.ts b/ts/@live-compositor/web-wasm/src/compositor/output.ts new file mode 100644 index 000000000..b733d266c --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/compositor/output.ts @@ -0,0 +1,24 @@ +import type { Resolution } from '@live-compositor/browser-render'; +import type { RegisterOutput as CoreRegisterOutput } from '@live-compositor/core'; + +export type RegisterOutput = { + type: 'canvas'; + video: { + canvas: HTMLCanvasElement; + resolution: Resolution; + }; +}; + +export function intoRegisterOutput(output: RegisterOutput): CoreRegisterOutput { + if (output.type === 'canvas') { + return { + type: 'canvas', + video: { + resolution: output.video.resolution, + canvas: output.video.canvas, + }, + }; + } else { + throw new Error(`Unknown output type ${(output as any).type}`); + } +} diff --git a/ts/@live-compositor/web-wasm/src/renderers.ts b/ts/@live-compositor/web-wasm/src/compositor/renderers.ts similarity index 100% rename from ts/@live-compositor/web-wasm/src/renderers.ts rename to ts/@live-compositor/web-wasm/src/compositor/renderers.ts diff --git a/ts/@live-compositor/web-wasm/src/eventSender.ts b/ts/@live-compositor/web-wasm/src/eventSender.ts index 4758fa35c..066b0d15f 100644 --- a/ts/@live-compositor/web-wasm/src/eventSender.ts +++ b/ts/@live-compositor/web-wasm/src/eventSender.ts @@ -1,26 +1,31 @@ import { _liveCompositorInternals } from 'live-compositor'; +import type { WorkerEvent } from './workerApi'; export const CompositorEventType = _liveCompositorInternals.CompositorEventType; export const inputRefIntoRawId = _liveCompositorInternals.inputRefIntoRawId; export class EventSender { - private eventCallback?: (event: object) => void; + private eventCallbacks: Set<(event: object) => void> = new Set(); - public setEventCallback(eventCallback: (event: object) => void) { - this.eventCallback = eventCallback; + /** + * Check if this is event that should be passed to core + */ + public static isExternalEvent(event: WorkerEvent): event is ExternalWorkerEvent { + return Object.values(CompositorEventType).includes(event?.type); } - public sendEvent(event: WasmCompositorEvent) { - if (!this.eventCallback) { - console.warn(`Failed to send event: ${event}`); - return; - } + public registerEventCallback(eventCallback: (event: object) => void) { + this.eventCallbacks?.add(eventCallback); + } - this.eventCallback!(toWebSocketMessage(event)); + public sendEvent(event: ExternalWorkerEvent) { + for (const cb of this.eventCallbacks) { + cb(toWebSocketMessage(event)); + } } } -function toWebSocketMessage(event: WasmCompositorEvent): WebSocketMessage { +function toWebSocketMessage(event: ExternalWorkerEvent): WebSocketMessage { if (event.type == CompositorEventType.OUTPUT_DONE) { return { type: event.type, @@ -34,7 +39,10 @@ function toWebSocketMessage(event: WasmCompositorEvent): WebSocketMessage { }; } -export type WasmCompositorEvent = +/** + * Subset of WorkerEvents that should be passed outside (to the core code) + */ +export type ExternalWorkerEvent = | { type: | _liveCompositorInternals.CompositorEventType.AUDIO_INPUT_DELIVERED @@ -49,6 +57,11 @@ export type WasmCompositorEvent = type: _liveCompositorInternals.CompositorEventType.OUTPUT_DONE; outputId: string; }; + +/** + * Actual format that in non-WASM compositor would be sent via WebSocket. Here it's only used to match the format + * so the core package can handle both WASM and non-WASM instances. + */ export type WebSocketMessage = | { type: diff --git a/ts/@live-compositor/web-wasm/src/index.ts b/ts/@live-compositor/web-wasm/src/index.ts index 2357b7bf7..2f335a978 100644 --- a/ts/@live-compositor/web-wasm/src/index.ts +++ b/ts/@live-compositor/web-wasm/src/index.ts @@ -1,4 +1,3 @@ -import WasmInstance from './manager/wasmInstance'; -import LiveCompositor, { setWasmBundleUrl } from './compositor'; +import LiveCompositor, { setWasmBundleUrl } from './compositor/compositor'; -export { WasmInstance, LiveCompositor, setWasmBundleUrl }; +export { LiveCompositor, setWasmBundleUrl }; diff --git a/ts/@live-compositor/web-wasm/src/input/decoder/h264Decoder.ts b/ts/@live-compositor/web-wasm/src/input/decoder/h264Decoder.ts deleted file mode 100644 index 43deb6761..000000000 --- a/ts/@live-compositor/web-wasm/src/input/decoder/h264Decoder.ts +++ /dev/null @@ -1,55 +0,0 @@ -export type FrameWithPts = { - frame: Omit; - ptsMs: number; -}; - -export type H264DecoderProps = { - onFrame: (frame: FrameWithPts) => void; -}; - -export class H264Decoder { - private decoder: VideoDecoder; - private ptsOffset?: number; - - public constructor(props: H264DecoderProps) { - // TODO(noituri): Use web workers - this.decoder = new VideoDecoder({ - output: videoFrame => props.onFrame(this.intoFrameWithPts(videoFrame)), - error: error => { - console.error(`H264Decoder error: ${error}`); - }, - }); - } - - public configure(config: VideoDecoderConfig) { - this.decoder.configure(config); - } - - public decode(chunk: EncodedVideoChunk) { - this.decoder.decode(chunk); - } - - public decodeQueueSize(): number { - return this.decoder.decodeQueueSize; - } - - private intoFrameWithPts(videoFrame: VideoFrame): FrameWithPts { - if (this.ptsOffset === undefined) { - this.ptsOffset = -videoFrame.timestamp; - } - - return { - frame: videoFrame, - // TODO(noituri): Handle pts roller - ptsMs: (this.ptsOffset + videoFrame.timestamp) / 1000, - }; - } - - public async flush(): Promise { - await this.decoder.flush(); - } - - public close() { - this.decoder.close(); - } -} diff --git a/ts/@live-compositor/web-wasm/src/input/input.ts b/ts/@live-compositor/web-wasm/src/input/input.ts deleted file mode 100644 index 1efd4c7b5..000000000 --- a/ts/@live-compositor/web-wasm/src/input/input.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { InputId } from '@live-compositor/browser-render'; -import { CompositorEventType, type EventSender } from '../eventSender'; -import type { FrameRef } from './frame'; -import { assert } from '../utils'; -import type InputFrameProducer from './inputFrameProducer'; - -export type InputState = 'waiting_for_start' | 'buffering' | 'playing' | 'finished'; - -export type InputStartInfo = { - videoDurationMs?: number; -}; - -export class Input { - private id: InputId; - private state: InputState; - private frameProducer: InputFrameProducer; - private eventSender: EventSender; - /** - * Queue PTS of the first frame - */ - private startPtsMs?: number; - - public constructor(id: InputId, frameProducer: InputFrameProducer, eventSender: EventSender) { - this.id = id; - this.state = 'waiting_for_start'; - this.frameProducer = frameProducer; - this.eventSender = eventSender; - - this.frameProducer.registerCallbacks({ - onReady: () => { - this.state = 'playing'; - this.eventSender.sendEvent({ - type: CompositorEventType.VIDEO_INPUT_PLAYING, - inputId: this.id, - }); - }, - }); - } - - public async start(): Promise { - if (this.state !== 'waiting_for_start') { - console.warn(`Tried to start an already started input "${this.id}"`); - return; - } - - const startInfo = await this.frameProducer.start(); - this.state = 'buffering'; - this.eventSender.sendEvent({ - type: CompositorEventType.VIDEO_INPUT_DELIVERED, - inputId: this.id, - }); - - return startInfo; - } - - /** - * Called on every queue tick. Produces frames for given `currentQueuePts` & handles EOS. - */ - public async onQueueTick(currentQueuePts: number): Promise { - let targetPts: number | undefined; - if (this.startPtsMs !== undefined) { - targetPts = this.queuePtsToInputPts(currentQueuePts); - } - - await this.frameProducer.produce(targetPts); - - if (this.state === 'playing' && this.frameProducer.isFinished()) { - // EOS received and no more frames will be produced. - this.state = 'finished'; - this.eventSender.sendEvent({ - type: CompositorEventType.VIDEO_INPUT_EOS, - inputId: this.id, - }); - - this.frameProducer.close(); - } - } - - /** - * Retrieves reference of a frame closest to the provided `currentQueuePts`. - */ - public getFrameRef(currentQueuePts: number): FrameRef | undefined { - if (this.state !== 'playing') { - return; - } - if (this.startPtsMs === undefined) { - this.startPtsMs = currentQueuePts; - } - - const framePts = this.queuePtsToInputPts(currentQueuePts); - return this.frameProducer.getFrameRef(framePts); - } - - private queuePtsToInputPts(queuePts: number): number { - assert(this.startPtsMs !== undefined); - return queuePts - this.startPtsMs; - } -} diff --git a/ts/@live-compositor/web-wasm/src/input/inputFrameProducer.ts b/ts/@live-compositor/web-wasm/src/input/inputFrameProducer.ts deleted file mode 100644 index edc0cf257..000000000 --- a/ts/@live-compositor/web-wasm/src/input/inputFrameProducer.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { RegisterInputRequest } from '@live-compositor/core'; -import type { FrameRef } from './frame'; -import DecodingFrameProducer from './producer/decodingFrameProducer'; -import MP4Source from './mp4/source'; -import type { InputStartInfo } from './input'; - -export type InputFrameProducerCallbacks = { - onReady(): void; -}; - -export default interface InputFrameProducer { - init(): Promise; - /** - * Starts resources required for producing frames. `init()` has to be called beforehand. - */ - start(): Promise; - registerCallbacks(callbacks: InputFrameProducerCallbacks): void; - /** - * Produce next frame. - * @param framePts - Desired PTS of the frame in milliseconds. - */ - produce(framePts?: number): Promise; - getFrameRef(framePts: number): FrameRef | undefined; - /** - * if `true` no more frames will be produced. - */ - isFinished(): boolean; - close(): void; -} - -export function producerFromRequest(request: RegisterInputRequest): InputFrameProducer { - if (request.type === 'mp4') { - return new DecodingFrameProducer(new MP4Source(request.url!)); - } else { - throw new Error(`Unknown input type ${(request as any).type}`); - } -} diff --git a/ts/@live-compositor/web-wasm/src/input/mp4/demuxer.ts b/ts/@live-compositor/web-wasm/src/input/mp4/demuxer.ts deleted file mode 100644 index 63727d1d3..000000000 --- a/ts/@live-compositor/web-wasm/src/input/mp4/demuxer.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { MP4ArrayBuffer, MP4File, MP4Info, Sample } from 'mp4box'; -import MP4Box, { DataStream } from 'mp4box'; -import type { SourcePayload } from '../source'; -import { assert } from '../../utils'; -import type { Framerate } from '../../compositor'; - -export type Mp4ReadyData = { - decoderConfig: VideoDecoderConfig; - framerate: Framerate; - videoDurationMs: number; -}; - -export type MP4DemuxerCallbacks = { - onReady: (data: Mp4ReadyData) => void; - onPayload: (payload: SourcePayload) => void; -}; - -export class MP4Demuxer { - private file: MP4File; - private fileOffset: number; - private callbacks: MP4DemuxerCallbacks; - private samplesCount?: number; - private ptsOffset?: number; - - public constructor(callbacks: MP4DemuxerCallbacks) { - this.file = MP4Box.createFile(); - this.file.onReady = info => this.onReady(info); - this.file.onSamples = (_id, _user, samples) => this.onSamples(samples); - this.file.onError = (error: string) => { - console.error(`MP4Demuxer error: ${error}`); - }; - this.fileOffset = 0; - - this.callbacks = callbacks; - } - - public demux(data: ArrayBuffer) { - const mp4Data = data as MP4ArrayBuffer; - mp4Data.fileStart = this.fileOffset; - this.fileOffset += mp4Data.byteLength; - - this.file.appendBuffer(mp4Data); - } - - public flush() { - this.file.flush(); - } - - private onReady(info: MP4Info) { - if (info.videoTracks.length == 0) { - throw new Error('No video tracks'); - } - - const videoTrack = info.videoTracks[0]; - const videoDurationMs = (videoTrack.movie_duration / videoTrack.movie_timescale) * 1000; - const codecDescription = this.getCodecDescription(videoTrack.id); - this.samplesCount = videoTrack.nb_samples; - - const decoderConfig = { - codec: videoTrack.codec, - codedWidth: videoTrack.video.width, - codedHeight: videoTrack.video.height, - description: codecDescription, - }; - const framerate = { - num: videoTrack.timescale, - den: 1000, - }; - - this.callbacks.onReady({ - decoderConfig, - framerate, - videoDurationMs, - }); - - this.file.setExtractionOptions(videoTrack.id); - this.file.start(); - } - - private onSamples(samples: Sample[]) { - assert(this.samplesCount !== undefined); - - for (const sample of samples) { - const pts = (sample.cts * 1_000_000) / sample.timescale; - if (this.ptsOffset === undefined) { - this.ptsOffset = -pts; - } - - const chunk = new EncodedVideoChunk({ - type: sample.is_sync ? 'key' : 'delta', - timestamp: pts + this.ptsOffset, - duration: (sample.duration * 1_000_000) / sample.timescale, - data: sample.data, - }); - - this.callbacks.onPayload({ type: 'chunk', chunk: chunk }); - - if (sample.number === this.samplesCount - 1) { - this.callbacks.onPayload({ type: 'eos' }); - } - } - } - - private getCodecDescription(trackId: number): Uint8Array { - const track = this.file.getTrackById(trackId); - if (!track) { - throw new Error('Track does not exist'); - } - - for (const entry of track.mdia.minf.stbl.stsd.entries) { - const box = entry.avcC || entry.hvcC || entry.vpcC || entry.av1C; - if (box) { - const stream = new DataStream(undefined, 0, DataStream.BIG_ENDIAN); - box.write(stream); - return new Uint8Array(stream.buffer, 8); - } - } - - throw new Error('Codec description not found'); - } -} diff --git a/ts/@live-compositor/web-wasm/src/input/mp4/source.ts b/ts/@live-compositor/web-wasm/src/input/mp4/source.ts deleted file mode 100644 index 2075d2028..000000000 --- a/ts/@live-compositor/web-wasm/src/input/mp4/source.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { MP4Demuxer } from './demuxer'; -import type InputSource from '../source'; -import type { InputSourceCallbacks, SourceMetadata, SourcePayload } from '../source'; -import { Queue } from '@datastructures-js/queue'; - -export default class MP4Source implements InputSource { - private fileUrl: string; - private fileData?: ArrayBuffer; - private demuxer?: MP4Demuxer; - private callbacks?: InputSourceCallbacks; - private chunks: Queue; - private eosReceived: boolean = false; - private metadata: SourceMetadata = {}; - - public constructor(fileUrl: string) { - this.fileUrl = fileUrl; - this.chunks = new Queue(); - } - - public async init(): Promise { - const resp = await fetch(this.fileUrl); - this.fileData = await resp.arrayBuffer(); - } - - public async start(): Promise { - await new Promise(resolve => { - if (!this.fileData) { - throw new Error('MP4Source has to be initialized first before processing can be started'); - } - - this.demuxer = new MP4Demuxer({ - onReady: data => { - this.callbacks?.onDecoderConfig(data.decoderConfig); - this.metadata.framerate = data.framerate; - this.metadata.videoDurationMs = data.videoDurationMs; - resolve(); - }, - onPayload: payload => this.handlePayload(payload), - }); - - this.demuxer.demux(this.fileData); - this.demuxer.flush(); - }); - } - - public registerCallbacks(callbacks: InputSourceCallbacks): void { - this.callbacks = callbacks; - } - - public isFinished(): boolean { - return this.eosReceived && this.chunks.isEmpty(); - } - - public getMetadata(): SourceMetadata { - return this.metadata; - } - - public nextChunk(): EncodedVideoChunk | undefined { - return this.chunks.pop(); - } - - public peekChunk(): EncodedVideoChunk | undefined { - return this.chunks.front(); - } - - private handlePayload(payload: SourcePayload) { - if (payload.type === 'chunk') { - this.chunks.push(payload.chunk); - } else if (payload.type === 'eos') { - this.eosReceived = true; - } - } -} diff --git a/ts/@live-compositor/web-wasm/src/input/producer/decodingFrameProducer.ts b/ts/@live-compositor/web-wasm/src/input/producer/decodingFrameProducer.ts deleted file mode 100644 index 6457c2b3d..000000000 --- a/ts/@live-compositor/web-wasm/src/input/producer/decodingFrameProducer.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Queue } from '@datastructures-js/queue'; -import { assert, framerateToDurationMs } from '../../utils'; -import { H264Decoder } from '../decoder/h264Decoder'; -import { FrameRef } from '../frame'; -import type { InputFrameProducerCallbacks } from '../inputFrameProducer'; -import type InputFrameProducer from '../inputFrameProducer'; -import type InputSource from '../source'; -import type { InputStartInfo } from '../input'; - -const MAX_BUFFERING_SIZE = 3; - -export default class DecodingFrameProducer implements InputFrameProducer { - private source: InputSource; - private decoder: H264Decoder; - private frames: Queue; - private onReadySent: boolean; - private callbacks?: InputFrameProducerCallbacks; - - public constructor(source: InputSource) { - this.onReadySent = false; - this.source = source; - this.decoder = new H264Decoder({ - onFrame: frame => this.frames.push(new FrameRef(frame)), - }); - this.frames = new Queue(); - - this.source.registerCallbacks({ - onDecoderConfig: config => this.decoder.configure(config), - }); - } - - public async init(): Promise { - await this.source.init(); - } - - public async start(): Promise { - await this.source.start(); - - const metadata = this.source.getMetadata(); - return { - videoDurationMs: metadata.videoDurationMs, - }; - } - - public registerCallbacks(callbacks: InputFrameProducerCallbacks): void { - this.callbacks = callbacks; - } - - public async produce(framePts?: number): Promise { - if (!this.onReadySent && this.frames.size() >= MAX_BUFFERING_SIZE) { - this.callbacks?.onReady(); - this.onReadySent = true; - } - - if (this.source.isFinished()) { - // No more chunks will be produced. Flush all the remaining frames from the decoder - if (this.decoder.decodeQueueSize() !== 0) { - await this.decoder.flush(); - } - return; - } - - if (framePts) { - this.enqueueChunksForPts(framePts); - } else { - this.tryEnqueueChunk(); - } - } - - /** - * Retrieves frame with PTS closest to `framePts`. - * Frames older than the closest frame are dropped. - */ - public getFrameRef(framePts: number): FrameRef | undefined { - this.dropOldFrames(framePts); - - if ( - this.source.isFinished() && - this.decoder.decodeQueueSize() === 0 && - this.frames.size() == 1 - ) { - return this.frames.pop(); - } else { - return this.cloneLatestFrame(); - } - } - - /** - * Retrieves latest frame and increments its reference count - */ - private cloneLatestFrame(): FrameRef | undefined { - const frame = this.frames.front(); - if (frame) { - frame.incrementRefCount(); - return frame; - } - - return undefined; - } - - public isFinished(): boolean { - return ( - this.source.isFinished() && this.decoder.decodeQueueSize() === 0 && this.frames.isEmpty() - ); - } - - public close(): void { - this.decoder.close(); - } - - /** - * Finds frame with PTS closest to `framePts` and removes frames older than it - */ - private dropOldFrames(framePts: number): void { - if (this.frames.isEmpty()) { - return; - } - - const frames = this.frames.toArray(); - const targetFrame = frames.reduce((prevFrame, frame) => { - const prevPtsDiff = Math.abs(prevFrame.getPtsMs() - framePts); - const currPtsDiff = Math.abs(frame.getPtsMs() - framePts); - return prevPtsDiff < currPtsDiff ? prevFrame : frame; - }); - - for (const frame of frames) { - if (frame.getPtsMs() < targetFrame.getPtsMs()) { - frame.decrementRefCount(); - this.frames.pop(); - } - } - } - - private tryEnqueueChunk() { - const chunk = this.source.nextChunk(); - if (chunk) { - this.decoder.decode(chunk); - } - } - - private enqueueChunksForPts(framePts: number) { - const metadata = this.source.getMetadata(); - assert(metadata.framerate); - - const frameDuration = framerateToDurationMs(metadata.framerate); - const targetPtsUs = (framePts + frameDuration * MAX_BUFFERING_SIZE) * 1000; - - let chunk = this.source.peekChunk(); - while (chunk && chunk.timestamp <= targetPtsUs) { - this.decoder.decode(chunk); - this.source.nextChunk(); - chunk = this.source.peekChunk(); - } - } -} diff --git a/ts/@live-compositor/web-wasm/src/input/registerInput.ts b/ts/@live-compositor/web-wasm/src/input/registerInput.ts deleted file mode 100644 index cae44cd9a..000000000 --- a/ts/@live-compositor/web-wasm/src/input/registerInput.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { RegisterInput as InternalRegisterInput } from '@live-compositor/core'; - -export type RegisterInput = { type: 'mp4' } & RegisterMP4Input; - -export type RegisterMP4Input = { - url: string; -}; - -export function intoRegisterInput(input: RegisterInput): InternalRegisterInput { - if (input.type === 'mp4') { - return { type: 'mp4', url: input.url }; - } else { - throw new Error(`Unknown input type ${(input as any).type}`); - } -} diff --git a/ts/@live-compositor/web-wasm/src/input/source.ts b/ts/@live-compositor/web-wasm/src/input/source.ts deleted file mode 100644 index fee0e84c1..000000000 --- a/ts/@live-compositor/web-wasm/src/input/source.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Framerate } from '../compositor'; - -export type SourcePayload = { type: 'chunk'; chunk: EncodedVideoChunk } | { type: 'eos' }; - -export type SourceMetadata = { - framerate?: Framerate; - videoDurationMs?: number; -}; - -export type InputSourceCallbacks = { - onDecoderConfig: (config: VideoDecoderConfig) => void; -}; - -/** - * `InputSource` produces encoded video chunks required for decoding. - */ -export default interface InputSource { - init(): Promise; - /** - * Starts producing chunks. `init()` has to be called beforehand. - */ - start(): Promise; - registerCallbacks(callbacks: InputSourceCallbacks): void; - /** - * if `true` InputSource won't produce more chunks anymore. - */ - isFinished(): boolean; - getMetadata(): SourceMetadata; - nextChunk(): EncodedVideoChunk | undefined; - peekChunk(): EncodedVideoChunk | undefined; -} diff --git a/ts/@live-compositor/web-wasm/src/manager/wasmInstance.ts b/ts/@live-compositor/web-wasm/src/manager/wasmInstance.ts deleted file mode 100644 index e2425d98a..000000000 --- a/ts/@live-compositor/web-wasm/src/manager/wasmInstance.ts +++ /dev/null @@ -1,187 +0,0 @@ -import type { - ApiRequest, - CompositorManager, - RegisterInputRequest, - RegisterInputResponse, - RegisterOutputRequest, -} from '@live-compositor/core'; -import type { Renderer, Component, ImageSpec } from '@live-compositor/browser-render'; -import type { Api } from 'live-compositor'; -import { Path } from 'path-parser'; -import type { StopQueueFn } from '../queue'; -import { Queue } from '../queue'; -import { Input } from '../input/input'; -import { EventSender } from '../eventSender'; -import type { Framerate } from '../compositor'; -import { Output } from '../output/output'; -import { producerFromRequest } from '../input/inputFrameProducer'; - -export type OnRegisterCallback = (event: object) => void; - -const apiPath = new Path('/api/:type/:id/:operation'); -const apiStartPath = new Path('/api/start'); - -class WasmInstance implements CompositorManager { - private renderer: Renderer; - private queue: Queue; - private eventSender: EventSender; - private stopQueue?: StopQueueFn; - - public constructor(props: { renderer: Renderer; framerate: Framerate }) { - this.renderer = props.renderer; - this.queue = new Queue(props.framerate, props.renderer); - this.eventSender = new EventSender(); - } - - public async setupInstance(): Promise {} - - public async sendRequest(request: ApiRequest): Promise { - const route = apiPath.test(request.route); - if (!route) { - if (apiStartPath.test(request.route)) { - this.start(); - } - return {}; - } - - if (route.type == 'input') { - return await this.handleInputRequest(route.id, route.operation, request.body); - } else if (route.type === 'output') { - return this.handleOutputRequest(route.id, route.operation, request.body); - } else if (route.type === 'image') { - return await this.handleImageRequest(route.id, route.operation, request.body); - } else if (route.type === 'shader') { - throw new Error('Shaders are not supported'); - } else if (route.type === 'web-renderer') { - throw new Error('Web renderers are not supported'); - } else { - return {}; - } - } - - public registerEventListener(cb: (event: unknown) => void): void { - this.eventSender.setEventCallback(cb); - } - - public async terminate(): Promise { - // TODO(noituri): Clean all remaining `InputFrame`s & stop input processing - if (this.stopQueue) { - this.stopQueue(); - this.stopQueue = undefined; - } - } - - private start() { - if (this.stopQueue) { - throw new Error('Compositor is already running'); - } - this.stopQueue = this.queue.start(); - } - - private async handleInputRequest( - inputId: string, - operation: string, - body?: object - ): Promise { - if (operation === 'register') { - return await this.registerInput(inputId, body! as RegisterInputRequest); - } else if (operation === 'unregister') { - return this.unregisterInput(inputId); - } else { - return {}; - } - } - - private handleOutputRequest(outputId: string, operation: string, body?: object): object { - if (operation === 'register') { - return this.registerOutput(outputId, body! as RegisterOutputRequest); - } else if (operation === 'unregister') { - return this.unregisterOutput(outputId); - } else if (operation === 'update') { - return this.updateScene(outputId, body! as Api.UpdateOutputRequest); - } else { - return {}; - } - } - - private async handleImageRequest( - imageId: string, - operation: string, - body?: object - ): Promise { - if (operation === 'register') { - await this.renderer.registerImage(imageId, body as ImageSpec); - } else if (operation === 'unregister') { - this.renderer.unregisterImage(imageId); - } - - return {}; - } - - private async registerInput( - inputId: string, - request: RegisterInputRequest - ): Promise { - const frameProducer = producerFromRequest(request); - await frameProducer.init(); - - const input = new Input(inputId, frameProducer, this.eventSender); - // `addInput` will throw an exception if input already exists - this.queue.addInput(inputId, input); - this.renderer.registerInput(inputId); - - const startInfo = await input.start(); - return { - video_duration_ms: startInfo?.videoDurationMs, - }; - } - - private unregisterInput(inputId: string): object { - this.queue.removeInput(inputId); - this.renderer.unregisterInput(inputId); - return {}; - } - - private registerOutput(outputId: string, request: RegisterOutputRequest): object { - if (request.video) { - const output = new Output(request); - this.queue.addOutput(outputId, output); - try { - // `updateScene` implicitly registers the output. - // In case of an error, the output has to be manually cleaned up from the renderer. - this.renderer.updateScene( - outputId, - request.video.resolution, - request.video.initial.root as Component - ); - } catch (e) { - this.queue.removeOutput(outputId); - this.renderer.unregisterOutput(outputId); - throw e; - } - } - - return {}; - } - - private unregisterOutput(outputId: string): object { - this.queue.removeOutput(outputId); - this.renderer.unregisterOutput(outputId); - return {}; - } - - private updateScene(outputId: string, request: Api.UpdateOutputRequest): object { - if (!request.video) { - return {}; - } - const output = this.queue.getOutput(outputId); - if (!output) { - throw `Unknown output "${outputId}"`; - } - this.renderer.updateScene(outputId, output.resolution, request.video.root as Component); - - return {}; - } -} - -export default WasmInstance; diff --git a/ts/@live-compositor/web-wasm/src/output/registerOutput.ts b/ts/@live-compositor/web-wasm/src/output/registerOutput.ts deleted file mode 100644 index e505089e1..000000000 --- a/ts/@live-compositor/web-wasm/src/output/registerOutput.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Resolution } from '@live-compositor/browser-render'; -import type { RegisterOutput as InternalRegisterOutput } from '@live-compositor/core'; - -export type RegisterOutput = { type: 'canvas' } & RegisterCanvasOutput; - -export type RegisterCanvasOutput = { - resolution: Resolution; - canvas: HTMLCanvasElement; -}; - -export function intoRegisterOutput(output: RegisterOutput): InternalRegisterOutput { - if (output.type === 'canvas') { - return fromRegisterCanvasOutput(output); - } else { - throw new Error(`Unknown output type ${(output as any).type}`); - } -} - -function fromRegisterCanvasOutput(output: RegisterCanvasOutput): InternalRegisterOutput { - return { - type: 'canvas', - video: { - resolution: output.resolution, - canvas: output.canvas, - }, - }; -} diff --git a/ts/@live-compositor/web-wasm/src/queue.ts b/ts/@live-compositor/web-wasm/src/queue.ts deleted file mode 100644 index b3b1be8e6..000000000 --- a/ts/@live-compositor/web-wasm/src/queue.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { FrameSet, InputId, OutputId, Renderer } from '@live-compositor/browser-render'; -import type { Framerate } from './compositor'; -import type { Input } from './input/input'; -import type { Output } from './output/output'; -import { framerateToDurationMs } from './utils'; -import type { FrameRef } from './input/frame'; - -export type StopQueueFn = () => void; - -export class Queue { - private inputs: Record = {}; - private outputs: Record = {}; - private renderer: Renderer; - private framerate: Framerate; - private currentPts: number; - - public constructor(framerate: Framerate, renderer: Renderer) { - this.renderer = renderer; - this.framerate = framerate; - this.currentPts = 0; - } - - public start(): StopQueueFn { - const tickDuration = framerateToDurationMs(this.framerate); - const queueInterval = setInterval(async () => { - await this.onTick(); - this.currentPts += tickDuration; - }, tickDuration); - - return () => clearInterval(queueInterval); - } - - public addInput(inputId: InputId, input: Input) { - if (this.inputs[inputId]) { - throw `Input "${inputId}" already exists`; - } - this.inputs[inputId] = input; - } - - public removeInput(inputId: InputId) { - delete this.inputs[inputId]; - } - - public getInput(inputId: InputId): Input | undefined { - return this.inputs[inputId]; - } - - public addOutput(outputId: OutputId, output: Output) { - if (this.outputs[outputId]) { - throw `Output "${outputId}" already exists`; - } - this.outputs[outputId] = output; - } - - public removeOutput(outputId: OutputId) { - delete this.outputs[outputId]; - } - - public getOutput(outputId: OutputId): Output | undefined { - return this.outputs[outputId]; - } - - private async onTick() { - await this.tickInputs(); - await this.produceOutputs(); - } - - private async tickInputs(): Promise { - const pending = Object.values(this.inputs).map( - async input => await input.onQueueTick(this.currentPts) - ); - await Promise.all(pending); - } - - private async produceOutputs(): Promise { - const inputs = this.getInputFrames(); - const pendingFrames = Object.entries(inputs).map(async ([inputId, input]) => [ - inputId, - await input.getFrame(), - ]); - const frames = Object.fromEntries(await Promise.all(pendingFrames)); - - const outputs = this.renderer.render({ - ptsMs: this.currentPts, - frames: frames, - }); - this.sendOutputs(outputs); - - for (const input of Object.values(inputs)) { - input.decrementRefCount(); - } - } - - private getInputFrames(): Record { - const frames = Object.entries(this.inputs).map(([inputId, input]) => [ - inputId, - input.getFrameRef(this.currentPts), - ]); - - return Object.fromEntries(frames.filter(([_inputId, frame]) => !!frame)); - } - - private sendOutputs(outputs: FrameSet) { - for (const [outputId, frame] of Object.entries(outputs.frames)) { - const output = this.outputs[outputId]; - if (!output) { - console.warn(`Output "${outputId}" not found`); - continue; - } - void output.send(frame); - } - } -} diff --git a/ts/@live-compositor/web-wasm/src/utils.ts b/ts/@live-compositor/web-wasm/src/utils.ts index 5831b1472..2a188c851 100644 --- a/ts/@live-compositor/web-wasm/src/utils.ts +++ b/ts/@live-compositor/web-wasm/src/utils.ts @@ -1,4 +1,7 @@ -import type { Framerate } from './compositor'; +import type { Framerate } from './compositor/compositor'; + +export type Interval = ReturnType; +export type Timeout = ReturnType; export function assert(value: T, msg?: string): asserts value { if (!value) { @@ -10,6 +13,14 @@ export function assert(value: T, msg?: string): asserts value { } } +export async function sleep(timeoutMs: number): Promise { + await new Promise(res => { + setTimeout(() => { + res(); + }, timeoutMs); + }); +} + export function framerateToDurationMs(framerate: Framerate): number { return (1000 * framerate.den) / framerate.num; } diff --git a/ts/@live-compositor/web-wasm/src/wasmInstance.ts b/ts/@live-compositor/web-wasm/src/wasmInstance.ts new file mode 100644 index 000000000..b5768afec --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/wasmInstance.ts @@ -0,0 +1,243 @@ +import type { + ApiRequest, + CompositorManager, + RegisterInputRequest, + RegisterOutputRequest, +} from '@live-compositor/core'; +import type { Framerate } from './compositor/compositor'; +import type { WorkerEvent, WorkerMessage, WorkerResponse } from './workerApi'; +import { EventSender } from './eventSender'; +import { Path } from 'path-parser'; +import { assert } from './utils'; +import type { ImageSpec } from '@live-compositor/browser-render'; +import type { Api } from 'live-compositor'; +import type { Logger } from 'pino'; +import { AsyncWorker } from './worker/bridge'; + +const apiPath = new Path('/api/:type/:id/:operation'); +const apiStartPath = new Path('/api/start'); + +class WasmInstance implements CompositorManager { + private eventSender: EventSender = new EventSender(); + private worker: AsyncWorker; + private logger: Logger; + private framerate: Framerate; + private wasmBundleUrl: string; + + public constructor(options: { framerate: Framerate; wasmBundleUrl: string; logger: Logger }) { + this.logger = options.logger; + this.framerate = options.framerate; + this.wasmBundleUrl = options.wasmBundleUrl; + + const worker = new Worker(new URL('./worker/runWorker.js', import.meta.url), { + type: 'module', + }); + const onEvent = (event: WorkerEvent) => { + if (EventSender.isExternalEvent(event)) { + this.eventSender.sendEvent(event); + return; + } + throw new Error(`Unknown event received. ${JSON.stringify(event)}`); + }; + this.worker = new AsyncWorker(worker, onEvent, this.logger); + } + + public async setupInstance(): Promise { + await this.worker.postMessage({ + type: 'init', + framerate: this.framerate, + wasmBundleUrl: this.wasmBundleUrl, + loggerLevel: this.logger.level, + }); + this.logger.debug('WASM instance initialized'); + } + + public async sendRequest(request: ApiRequest): Promise { + const [msg, transferable] = await handleRequest(request); + const response = await this.worker.postMessage(msg, transferable); + console.log('response', response); + return handleResponse(response); + } + + public registerEventListener(cb: (event: unknown) => void): void { + this.eventSender.registerEventCallback(cb); + } + + public async terminate(): Promise { + await this.worker.postMessage({ type: 'terminate' }); + this.worker.terminate(); + } +} + +function handleResponse(response: WorkerResponse): object { + if (!response) { + return {}; + } + if (response.type === 'registerInput') { + return response.body; + } + throw new Error('Unknown response type.'); +} + +async function handleRequest(request: ApiRequest): Promise<[WorkerMessage, Transferable[]]> { + const route = apiPath.test(request.route); + if (!route) { + if (apiStartPath.test(request.route)) { + return [{ type: 'start' }, []]; + } + throw new Error('Unknown route'); + } + + if (route.type == 'input') { + if (route.operation === 'register') { + assert(request.body); + return handleRegisterInputRequest(route.id, request.body as RegisterInputRequest); + } else if (route.operation === 'unregister') { + return [ + { + type: 'unregisterInput', + inputId: route.id, + }, + [], + ]; + } + } else if (route.type === 'output') { + if (route.operation === 'register') { + assert(request.body); + return handleRegisterOutputRequest(route.id, request.body as RegisterOutputRequest); + } else if (route.operation === 'unregister') { + return [ + { + type: 'unregisterOutput', + outputId: route.id, + }, + [], + ]; + } else if (route.operation === 'update') { + return [ + { + type: 'updateScene', + outputId: route.id, + output: request.body as Api.UpdateOutputRequest, + }, + [], + ]; + } + } else if (route.type === 'image') { + if (route.operation === 'register') { + assert(request.body); + return [ + { + type: 'registerImage', + imageId: route.id, + image: request.body as ImageSpec, + }, + [], + ]; + } else if (route.operation === 'unregister') { + return [ + { + type: 'unregisterImage', + imageId: route.id, + }, + [], + ]; + } + } else if (route.type === 'shader') { + throw new Error('Shaders are not supported'); + } else if (route.type === 'web-renderer') { + throw new Error('Web renderers are not supported'); + } + + throw new Error('Unknown request'); +} + +async function handleRegisterOutputRequest( + outputId: string, + body: RegisterOutputRequest +): Promise<[WorkerMessage, Transferable[]]> { + if (body.type === 'canvas') { + const canvas = (body.video.canvas as HTMLCanvasElement).transferControlToOffscreen(); + return [ + { + type: 'registerOutput', + outputId: outputId, + output: { + type: 'canvas', + video: { + resolution: body.video.resolution, + canvas, + initial: body.video.initial, + }, + }, + }, + [canvas], + ]; + } + throw new Error(`Unknown output type ${body.type}`); +} + +async function handleRegisterInputRequest( + inputId: string, + body: RegisterInputRequest +): Promise<[WorkerMessage, Transferable[]]> { + if (body.type === 'mp4') { + assert(body.url); + return [ + { + type: 'registerInput', + inputId, + input: { + type: 'mp4', + url: body.url, + }, + }, + [], + ]; + } else if (body.type === 'camera') { + const mediaStream = await navigator.mediaDevices.getUserMedia({ + audio: false, + video: true, + }); + const videoTrack = mediaStream.getVideoTracks()[0]; + // @ts-ignore + const trackProcessor = new MediaStreamTrackProcessor({ track: videoTrack }); + return [ + { + type: 'registerInput', + inputId, + input: { + type: 'camera', + stream: trackProcessor.readable, + }, + }, + [trackProcessor.readable], + ]; + } else if (body.type === 'screen_capture') { + const mediaStream = await navigator.mediaDevices.getDisplayMedia({ + audio: false, + video: { + width: { max: 2000 }, + height: { max: 2000 }, + }, + }); + const videoTrack = mediaStream.getVideoTracks()[0]; + assert(videoTrack); + // @ts-ignore + const trackProcessor = new MediaStreamTrackProcessor({ track: videoTrack }); + return [ + { + type: 'registerInput', + inputId, + input: { + type: 'screen_capture', + stream: trackProcessor.readable, + }, + }, + [trackProcessor.readable], + ]; + } + throw new Error(`Unknown output type ${body.type}`); +} + +export default WasmInstance; diff --git a/ts/@live-compositor/web-wasm/src/worker/bridge.ts b/ts/@live-compositor/web-wasm/src/worker/bridge.ts new file mode 100644 index 000000000..ebe1d1e86 --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/bridge.ts @@ -0,0 +1,115 @@ +import type { Logger } from 'pino'; + +type RequestMessage = { + id: string; + request: Request; +}; + +type ResponseMessage = { + type: 'workerResponse'; + id: string; + response?: Response; + error?: Error; +}; + +type EventMessage = { + type: 'workerEvent'; + event: Event; +}; + +type PendingMessage = { + res: (response: Response) => void; + rej: (err: Error) => void; +}; + +let requestCounter = 1; + +export function registerWorkerEntrypoint( + onMessage: (request: Request) => Promise +) { + self.onmessage = async (event: MessageEvent>) => { + try { + const response = await onMessage(event.data.request); + self.postMessage({ + type: 'workerResponse', + id: event.data.id, + response, + } as ResponseMessage); + } catch (error: any) { + self.postMessage({ + type: 'workerResponse', + id: event.data.id, + error, + } as ResponseMessage); + } + }; +} + +export function workerPostEvent(event: Event) { + self.postMessage({ type: 'workerEvent', event }); +} + +export class AsyncWorker { + private worker: Worker; + private pendingMessages: Record> = {}; + private onEvent: (event: Event) => void; + private logger: Logger; + + constructor(worker: Worker, onEvent: (event: Event) => void, logger: Logger) { + this.logger = logger; + this.worker = worker; + this.worker.onmessage = ( + event: MessageEvent | EventMessage> + ) => { + if (event.data.type === 'workerEvent') { + this.handleEvent(event.data.event); + } else if (event.data.type === 'workerResponse') { + this.handleResponse(event.data); + } + }; + this.onEvent = onEvent; + } + + public async postMessage(request: Request, transferable?: Transferable[]): Promise { + const requestId = String(requestCounter); + requestCounter += 1; + + const pendingMessage: PendingMessage = {} as any; + const responsePromise = new Promise((res, rej) => { + pendingMessage.res = res; + pendingMessage.rej = rej; + }); + this.pendingMessages[requestId] = pendingMessage; + + if (transferable) { + this.worker.postMessage({ id: requestId, request }, transferable); + } else { + this.worker.postMessage({ id: requestId, request }); + } + return responsePromise; + } + + public terminate() { + this.worker.terminate(); + } + + private handleEvent(event: Event) { + this.onEvent(event); + } + + private handleResponse(msg: ResponseMessage) { + const pendingMessage = this.pendingMessages[msg.id]; + if (!pendingMessage) { + this.logger.error(`Unknown response from Web Worker received. ${JSON.stringify(msg)}`); + return; + } + delete this.pendingMessages[msg.id]; + if (msg.error) { + pendingMessage.rej(msg.error); + } else { + // Response will likely include just "void", so falsy value + // still should mean that it is resolved + pendingMessage.res(msg.response!); + } + } +} diff --git a/ts/@live-compositor/web-wasm/src/worker/input/MediaStreamInput.ts b/ts/@live-compositor/web-wasm/src/worker/input/MediaStreamInput.ts new file mode 100644 index 000000000..82674324e --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/input/MediaStreamInput.ts @@ -0,0 +1,94 @@ +import type { Frame, InputId } from '@live-compositor/browser-render'; +import type { Input, InputStartResult } from './input'; +import { InputVideoFrameRef } from './frame'; +import type { Interval } from '../../utils'; +import { CompositorEventType } from '../../eventSender'; +import { workerPostEvent } from '../bridge'; + +export type InputState = 'started' | 'playing' | 'finished'; + +export class MediaStreamInput implements Input { + private inputId: InputId; + + private frameRef?: InputVideoFrameRef; + private reader: ReadableStreamDefaultReader; + private readInterval?: Interval; + + private receivedEos: boolean = false; + private sentEos: boolean = false; + private sentFirstFrame: boolean = false; + + public constructor(inputId: InputId, source: ReadableStream) { + this.reader = source.getReader(); + this.inputId = inputId; + } + + public start(): InputStartResult { + let readPromise: Promise> | undefined; + this.readInterval = setInterval(async () => { + if (readPromise) { + return; + } + readPromise = this.reader.read(); + const readResult = await readPromise; + if (readResult.value) { + if (this.frameRef) { + this.frameRef.decrementRefCount(); + } + this.frameRef = new InputVideoFrameRef({ + frame: readResult.value, + ptsMs: 0, // pts does not matter here + }); + } + + if (readResult.done) { + this.close(); + this.receivedEos = true; + } + readPromise = undefined; + }, 30); + workerPostEvent({ + type: CompositorEventType.VIDEO_INPUT_DELIVERED, + inputId: this.inputId, + }); + return {}; + } + + public close() { + if (this.readInterval) { + clearInterval(this.readInterval); + } + } + + public updateQueueStartTime(_queueStartTimeMs: number) {} + + public async getFrame(_currentQueuePts: number): Promise { + if (this.receivedEos) { + if (!this.sentEos) { + this.sentEos = true; + workerPostEvent({ + type: CompositorEventType.VIDEO_INPUT_EOS, + inputId: this.inputId, + }); + } + return; + } + const frameRef = this.frameRef; + if (frameRef) { + if (!this.sentFirstFrame) { + this.sentFirstFrame = true; + workerPostEvent({ + type: CompositorEventType.VIDEO_INPUT_PLAYING, + inputId: this.inputId, + }); + } + // using Ref just to cache downloading frames if the same frame is used more than once + frameRef.incrementRefCount(); + const frame = await frameRef.getFrame(); + frameRef.decrementRefCount(); + + return frame; + } + return frameRef; + } +} diff --git a/ts/@live-compositor/web-wasm/src/worker/input/QueuedInput.ts b/ts/@live-compositor/web-wasm/src/worker/input/QueuedInput.ts new file mode 100644 index 000000000..b92e260c6 --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/input/QueuedInput.ts @@ -0,0 +1,173 @@ +import type { Frame, InputId } from '@live-compositor/browser-render'; +import type { Logger } from 'pino'; +import { Queue } from '@datastructures-js/queue'; +import { workerPostEvent } from '../../worker/pipeline'; +import { CompositorEventType } from '../../eventSender'; +import type { Interval } from '../../utils'; +import { assert } from '../../utils'; +import type { Input, InputStartResult, InputVideoFrameSource } from './input'; +import type { InputVideoFrame } from './frame'; +import { InputVideoFrameRef } from './frame'; + +export type InputState = 'started' | 'playing' | 'finished'; + +const MAX_BUFFER_FRAME_COUNT = 10; +const ENQUEUE_INTERVAL_MS = 50; + +export class QueuedInput implements Input { + private inputId: InputId; + private source: InputVideoFrameSource; + private logger: Logger; + /** + * frames PTS start from 0, where 0 represents first frame + */ + private frames: Queue; + + private enqueueInterval?: Interval; + private enqueuePromise?: Promise; + + /** + * Queue PTS of the first frame + */ + private firstFrameTimeMs?: number; + /** + * Timestamp from first frame; + * TODO: maybe consider always zeroing them earlier + */ + private firstFramePtsMs?: number; + /** + * Start time of the queue + */ + private queueStartTimeMs: number = 0; + + private receivedEos: boolean = false; + private sentFirstFrame: boolean = false; + + public constructor(inputId: InputId, source: InputVideoFrameSource, logger: Logger) { + this.inputId = inputId; + this.source = source; + this.logger = logger; + this.frames = new Queue(); + } + + public start(): InputStartResult { + this.enqueueInterval = setInterval(async () => { + if (this.enqueuePromise) { + return; + } + this.enqueuePromise = this.tryEnqueue(); + await this.enqueuePromise; + this.enqueuePromise = undefined; + }, ENQUEUE_INTERVAL_MS); + + workerPostEvent({ + type: CompositorEventType.VIDEO_INPUT_DELIVERED, + inputId: this.inputId, + }); + return this.source.getMetadata(); + } + + public close() { + if (this.enqueueInterval) { + clearInterval(this.enqueueInterval); + } + } + + public updateQueueStartTime(queueStartTimeMs: number) { + this.queueStartTimeMs = queueStartTimeMs; + } + + /** + * Retrieves reference of a frame closest to the provided `currentQueuePts`. + */ + public async getFrame(currentQueuePts: number): Promise { + this.dropOldFrames(currentQueuePts); + const frameRef = this.frames.front(); + if (frameRef) { + frameRef.incrementRefCount(); + const frame = await frameRef.getFrame(); + frameRef.decrementRefCount(); + + if (!this.sentFirstFrame) { + this.sentFirstFrame = true; + this.logger.debug('Input started'); + workerPostEvent({ + type: CompositorEventType.VIDEO_INPUT_PLAYING, + inputId: this.inputId, + }); + } + + if (this.frames.size() === 1 && this.receivedEos) { + this.frames.pop().decrementRefCount(); + this.logger.debug('Input finished'); + workerPostEvent({ + type: CompositorEventType.VIDEO_INPUT_EOS, + inputId: this.inputId, + }); + } + + return frame; + } + return; + } + + private async tryEnqueue() { + // TODO: try dropping old frames that will not be used + if (this.frames.size() >= MAX_BUFFER_FRAME_COUNT) { + return; + } + const nextFrame = this.source.nextFrame(); + if (!nextFrame) { + return; + } else if (nextFrame.type === 'frame') { + this.frames.push(this.newFrameRef(nextFrame.frame)); + } else if (nextFrame.type === 'eos') { + this.receivedEos = true; + if (this.enqueueInterval) { + clearInterval(this.enqueueInterval); + } + } + } + + private newFrameRef(frame: InputVideoFrame): InputVideoFrameRef { + if (!this.firstFrameTimeMs) { + this.firstFrameTimeMs = Date.now(); + } + if (!this.firstFramePtsMs) { + this.firstFramePtsMs = frame.ptsMs; + } + frame.ptsMs = frame.ptsMs - this.firstFramePtsMs; + return new InputVideoFrameRef(frame); + } + + /** + * Finds frame with PTS closest to `framePts` and removes frames older than it + */ + private dropOldFrames(queuePts: number): void { + if (this.frames.isEmpty()) { + return; + } + const inputPts = this.queuePtsToInputPts(queuePts); + + const frames = this.frames.toArray(); + const targetFrame = frames.reduce((prevFrame, frame) => { + const prevPtsDiff = Math.abs(prevFrame.ptsMs - inputPts); + const currPtsDiff = Math.abs(frame.ptsMs - inputPts); + return prevPtsDiff < currPtsDiff ? prevFrame : frame; + }); + + for (const frame of frames) { + if (frame.ptsMs < targetFrame.ptsMs) { + frame.decrementRefCount(); + this.frames.pop(); + } + } + } + + private queuePtsToInputPts(queuePts: number): number { + assert(this.firstFrameTimeMs); + // TODO: handle before start + const offsetMs = this.firstFrameTimeMs - this.queueStartTimeMs; + return queuePts - offsetMs; + } +} diff --git a/ts/@live-compositor/web-wasm/src/worker/input/decoder.ts b/ts/@live-compositor/web-wasm/src/worker/input/decoder.ts new file mode 100644 index 000000000..31bf79ca2 --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/input/decoder.ts @@ -0,0 +1,95 @@ +import { Queue } from '@datastructures-js/queue'; +import type { InputVideoFrame } from './frame'; +import type { InputVideoFrameSource, EncodedVideoSource, VideoFramePayload } from './input'; +import type { Logger } from 'pino'; +import { sleep } from '../../utils'; + +const MAX_DECODED_FRAMES = 10; + +export class Decoder implements InputVideoFrameSource { + private source: EncodedVideoSource; + private decoder: VideoDecoder; + private offsetMs?: number; + private frames: Queue; + private receivedEos: boolean = false; + private firstFramePromise: Promise; + + public constructor(source: EncodedVideoSource, logger: Logger) { + this.source = source; + this.frames = new Queue(); + + let onFirstFrame: (() => void) | undefined; + let onDecoderError: ((err: Error) => void) | undefined; + this.firstFramePromise = new Promise((res, rej) => { + onFirstFrame = res; + onDecoderError = rej; + }); + + this.decoder = new VideoDecoder({ + output: videoFrame => { + onFirstFrame?.(); + this.onFrameDecoded(videoFrame); + }, + error: error => { + onDecoderError?.(error); + logger.error(`H264Decoder error: ${error}`); + }, + }); + } + + public async init(): Promise { + const metadata = this.source.getMetadata(); + this.decoder.configure(metadata.video.decoderConfig); + // + while (!this.trySchedulingDecoding()) { + await sleep(100); + } + await this.firstFramePromise; + } + + public nextFrame(): VideoFramePayload | undefined { + const frame = this.frames.pop(); + this.trySchedulingDecoding(); + if (frame) { + return { type: 'frame', frame: frame }; + } else if (this.receivedEos && this.decoder.decodeQueueSize === 0) { + return { type: 'eos' }; + } + return; + } + + public close() { + this.decoder.close(); + this.source.close(); + } + + private onFrameDecoded(videoFrame: VideoFrame) { + const frameTimeMs = videoFrame.timestamp / 1000; + if (this.offsetMs === undefined) { + this.offsetMs = -frameTimeMs; + } + + this.frames.push({ + frame: videoFrame, + ptsMs: this.offsetMs + frameTimeMs, + }); + } + + private trySchedulingDecoding(): boolean { + if (this.receivedEos) { + return true; + } + while (this.frames.size() + this.decoder.decodeQueueSize < MAX_DECODED_FRAMES) { + const payload = this.source.nextChunk(); + if (!payload) { + return false; + } else if (payload.type === 'eos') { + this.receivedEos = true; + return true; + } else if (payload.type === 'chunk') { + this.decoder.decode(payload.chunk); + } + } + return true; + } +} diff --git a/ts/@live-compositor/web-wasm/src/input/frame.ts b/ts/@live-compositor/web-wasm/src/worker/input/frame.ts similarity index 73% rename from ts/@live-compositor/web-wasm/src/input/frame.ts rename to ts/@live-compositor/web-wasm/src/worker/input/frame.ts index 7eeed00c8..4f2aa8577 100644 --- a/ts/@live-compositor/web-wasm/src/input/frame.ts +++ b/ts/@live-compositor/web-wasm/src/worker/input/frame.ts @@ -1,7 +1,11 @@ import type { Frame } from '@live-compositor/browser-render'; import { FrameFormat } from '@live-compositor/browser-render'; -import type { FrameWithPts } from './decoder/h264Decoder'; -import { assert } from '../utils'; +import { assert } from '../../utils'; + +export type InputVideoFrame = { + frame: Omit; + ptsMs: number; +}; /** * Represents frame produced by decoder. @@ -9,16 +13,20 @@ import { assert } from '../utils'; * `Input` manages memory in `getFrameRef()` * `Queue` on tick pulls `FrameRef` for each input and once render finishes, decrements the ref count */ -export class FrameRef { - private frame: FrameWithPts; +export class InputVideoFrameRef { + private frame: InputVideoFrame; private refCount: number; private downloadedFrame?: Frame; - public constructor(frame: FrameWithPts) { + public constructor(frame: InputVideoFrame) { this.frame = frame; this.refCount = 1; } + public get ptsMs(): number { + return this.frame.ptsMs; + } + /** * Increments reference count. Should be called every time the reference is copied. */ @@ -52,21 +60,28 @@ export class FrameRef { } return this.downloadedFrame; } +} - public getPtsMs(): number { - return this.frame.ptsMs; +export class NonCopyableFrameRef extends InputVideoFrameRef { + public constructor(frame: InputVideoFrame) { + super(frame); + } + + public incrementRefCount(): void { + throw new Error('Reference count of `NonCopyableFrameRef` cannot be incremented'); } } -async function downloadFrame(frameWithPts: FrameWithPts): Promise { +async function downloadFrame(inputFrame: InputVideoFrame): Promise { // Safari does not support conversion to RGBA // Chrome does not support conversion to YUV - const isSafari = !!(window as any).safari; + // const isSafari = !!(window as any).safari; + const isSafari = false; const options = { format: isSafari ? 'I420' : 'RGBA', }; - const frame = frameWithPts.frame; + const frame = inputFrame.frame; const buffer = new Uint8ClampedArray(frame.allocationSize(options as VideoFrameCopyToOptions)); await frame.copyTo(buffer, options as VideoFrameCopyToOptions); diff --git a/ts/@live-compositor/web-wasm/src/worker/input/input.ts b/ts/@live-compositor/web-wasm/src/worker/input/input.ts new file mode 100644 index 000000000..bbf97a3ed --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/input/input.ts @@ -0,0 +1,68 @@ +import Mp4Source from './source/Mp4Source'; +import { QueuedInput } from './QueuedInput'; +import type { InputVideoFrame } from './frame'; +import type { Frame } from '@live-compositor/browser-render'; +import { MediaStreamInput } from './MediaStreamInput'; +import type { RegisterInput } from '../../workerApi'; +import type { Logger } from 'pino'; + +export type InputStartResult = { + videoDurationMs?: number; + audioDurationMs?: number; +}; + +export type ContainerInfo = { + video: { + durationMs?: number; + decoderConfig: VideoDecoderConfig; + }; +}; + +export interface Input { + start(): InputStartResult; + updateQueueStartTime(queueStartTimeMs: number): void; + getFrame(currentQueuePts: number): Promise; + close(): void; +} + +export type VideoFramePayload = { type: 'frame'; frame: InputVideoFrame } | { type: 'eos' }; + +export interface InputVideoFrameSource { + init(): Promise; + getMetadata(): InputStartResult; + nextFrame(): VideoFramePayload | undefined; + close(): void; +} + +export type EncodedVideoPayload = { type: 'chunk'; chunk: EncodedVideoChunk } | { type: 'eos' }; + +/** + * `EncodedVideoSource` produces encoded video chunks required for decoding. + */ +export interface EncodedVideoSource { + init(): Promise; + getMetadata(): ContainerInfo; + nextChunk(): EncodedVideoPayload | undefined; + close(): void; +} + +export async function createInput( + inputId: string, + request: RegisterInput, + logger: Logger +): Promise { + const inputLogger = logger.child({ inputId }); + if (request.type === 'mp4') { + if (!request.url) { + throw new Error('Mp4 url is required'); + } + const source = new Mp4Source(request.url, inputLogger); + await source.init(); + return new QueuedInput(inputId, source, inputLogger); + } else if (request.type === 'camera') { + return new MediaStreamInput(inputId, request.stream); + } else if (request.type === 'screen_capture') { + return new MediaStreamInput(inputId, request.stream); + } + throw new Error(`Unknown input type ${(request as any).type}`); +} diff --git a/ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Demuxer.ts b/ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Demuxer.ts new file mode 100644 index 000000000..d6aecea7f --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Demuxer.ts @@ -0,0 +1,177 @@ +import type { MP4ArrayBuffer, MP4File, MP4Info, Sample } from 'mp4box'; +import MP4Box, { DataStream } from 'mp4box'; +import type { ContainerInfo, EncodedVideoPayload, EncodedVideoSource } from '../input'; +import type { Logger } from 'pino'; +import { Queue } from '@datastructures-js/queue'; +import type { Framerate } from '../../../compositor/compositor'; +import { assert } from '../../../utils'; + +export type Mp4Metadata = { + video: { + decoderConfig: VideoDecoderConfig; + framerate: Framerate; + trackId: number; + frameCount: number; + durationMs: number; + }; +}; + +export class Mp4Demuxer implements EncodedVideoSource { + private file: MP4File; + private logger: Logger; + private ptsOffset?: number; + + private videoChunks: Queue; + private videoTrackFinished: boolean = false; + + private readyPromise: Promise; + private firstVideoChunkPromise: Promise; + private mp4Metadata?: Mp4Metadata; + + public constructor(data: ArrayBuffer, logger: Logger) { + this.logger = logger; + this.videoChunks = new Queue(); + + this.file = MP4Box.createFile(); + this.readyPromise = new Promise((res, rej) => { + this.file.onReady = info => { + try { + res(this.parseMp4Info(info)); + } catch (err: any) { + rej(err); + } + }; + this.file.onError = (error: string) => { + this.logger.error(`MP4Demuxer error: ${error}`); + rej(new Error(error)); + }; + }); + + let firstVideoChunkCb: (() => void) | undefined; + this.firstVideoChunkPromise = new Promise((res, _rej) => { + firstVideoChunkCb = res; + }); + + this.file.onSamples = (id, _user, samples) => { + this.onSamples(samples); + if (id === this.mp4Metadata?.video.trackId) { + firstVideoChunkCb?.(); + } + }; + + const mp4Data = data as MP4ArrayBuffer; + mp4Data.fileStart = 0; + + this.file.appendBuffer(mp4Data); + } + + public async init(): Promise { + this.mp4Metadata = await this.readyPromise; + this.file.setExtractionOptions(this.mp4Metadata.video.trackId); + this.file.start(); + + // by flushing we are signaling that there won't be any new + // chunks added + this.file.flush(); + + await this.firstVideoChunkPromise; + } + + public getMetadata(): ContainerInfo { + assert(this.mp4Metadata, 'Mp4 metadata not available, call `init` first.'); + return { + video: { + durationMs: this.mp4Metadata.video.durationMs, + decoderConfig: this.mp4Metadata.video.decoderConfig, + }, + }; + } + + public nextChunk(): EncodedVideoPayload | undefined { + const chunk = this.videoChunks.pop(); + if (chunk) { + return { type: 'chunk', chunk }; + } else if (this.videoTrackFinished) { + return { type: 'eos' }; + } + return; + } + + public close(): void { + this.file.stop(); + } + + private parseMp4Info(info: MP4Info): Mp4Metadata { + if (info.videoTracks.length == 0) { + throw new Error('No video tracks'); + } + + const videoTrack = info.videoTracks[0]; + const videoDurationMs = (videoTrack.movie_duration / videoTrack.movie_timescale) * 1000; + const codecDescription = this.getCodecDescription(videoTrack.id); + const frameCount = videoTrack.nb_samples; + + const decoderConfig = { + codec: videoTrack.codec, + codedWidth: videoTrack.video.width, + codedHeight: videoTrack.video.height, + description: codecDescription, + }; + const framerate = { + num: videoTrack.timescale, + den: 1000, + }; + + return { + video: { + decoderConfig, + framerate, + trackId: videoTrack.id, + frameCount, + durationMs: videoDurationMs, + }, + }; + } + + private onSamples(samples: Sample[]) { + assert(this.mp4Metadata); + + for (const sample of samples) { + const pts = (sample.cts * 1_000_000) / sample.timescale; + if (this.ptsOffset === undefined) { + this.ptsOffset = -pts; + } + + const chunk = new EncodedVideoChunk({ + type: sample.is_sync ? 'key' : 'delta', + timestamp: pts + this.ptsOffset, + duration: (sample.duration * 1_000_000) / sample.timescale, + data: sample.data, + }); + + this.videoChunks.push(chunk); + + if (sample.number === this.mp4Metadata.video.frameCount - 1) { + this.videoTrackFinished = true; + } + } + } + + private getCodecDescription(trackId: number): Uint8Array { + const track = this.file.getTrackById(trackId); + if (!track) { + throw new Error('Track does not exist'); + } + + for (const entry of track.mdia.minf.stbl.stsd.entries) { + const box = entry.avcC || entry.hvcC || entry.vpcC || entry.av1C; + if (box) { + const stream = new DataStream(undefined, 0, DataStream.BIG_ENDIAN); + box.write(stream); + return new Uint8Array(stream.buffer, 8); + } + } + + throw new Error('Codec description not found'); + } +} diff --git a/ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Source.ts b/ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Source.ts new file mode 100644 index 000000000..80e2a8053 --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/input/source/Mp4Source.ts @@ -0,0 +1,51 @@ +import { Mp4Demuxer } from './Mp4Demuxer'; +import type { + ContainerInfo, + InputStartResult, + InputVideoFrameSource, + VideoFramePayload, +} from '../input'; +import { Decoder } from '../decoder'; +import type { Logger } from 'pino'; +import { assert } from '../../../utils'; + +export default class Mp4Source implements InputVideoFrameSource { + private fileUrl: string; + private logger: Logger; + private decoder?: Decoder; + private metadata?: ContainerInfo; + + public constructor(fileUrl: string, logger: Logger) { + this.fileUrl = fileUrl; + this.logger = logger; + } + + public async init(): Promise { + const resp = await fetch(this.fileUrl); + const fileData = await resp.arrayBuffer(); + + const demuxer = new Mp4Demuxer(fileData, this.logger); + await demuxer.init(); + + this.decoder = new Decoder(demuxer, this.logger); + await this.decoder.init(); + + this.metadata = demuxer.getMetadata(); + } + + public nextFrame(): VideoFramePayload | undefined { + assert(this.decoder, 'Decoder was not initialized, call init() first.'); + return this.decoder.nextFrame(); + } + + public getMetadata(): InputStartResult { + return { + videoDurationMs: this.metadata?.video.durationMs, + }; + } + + public close(): void { + assert(this.decoder, 'Decoder was not initialized, call init() first.'); + this.decoder.close(); + } +} diff --git a/ts/@live-compositor/web-wasm/src/input/mp4/mp4box.d.ts b/ts/@live-compositor/web-wasm/src/worker/input/source/mp4box.d.ts similarity index 100% rename from ts/@live-compositor/web-wasm/src/input/mp4/mp4box.d.ts rename to ts/@live-compositor/web-wasm/src/worker/input/source/mp4box.d.ts diff --git a/ts/@live-compositor/web-wasm/src/output/canvas.ts b/ts/@live-compositor/web-wasm/src/worker/output/canvas.ts similarity index 56% rename from ts/@live-compositor/web-wasm/src/output/canvas.ts rename to ts/@live-compositor/web-wasm/src/worker/output/canvas.ts index 5c7c0f7c1..aabdb4c47 100644 --- a/ts/@live-compositor/web-wasm/src/output/canvas.ts +++ b/ts/@live-compositor/web-wasm/src/worker/output/canvas.ts @@ -1,11 +1,14 @@ import type { Frame } from '@live-compositor/browser-render'; import type { OutputSink } from './sink'; +import { assert } from '../../utils'; export default class CanvasSink implements OutputSink { - private ctx: CanvasRenderingContext2D; + private ctx: OffscreenCanvasRenderingContext2D; - public constructor(canvas: HTMLCanvasElement) { - this.ctx = canvas.getContext('2d')!; + public constructor(canvas: OffscreenCanvas) { + const ctx = canvas.getContext('2d', { desynchronized: false }); + assert(ctx, 'Failed to instantiate a context.'); + this.ctx = ctx; } public async send(frame: Frame): Promise { diff --git a/ts/@live-compositor/web-wasm/src/output/output.ts b/ts/@live-compositor/web-wasm/src/worker/output/output.ts similarity index 82% rename from ts/@live-compositor/web-wasm/src/output/output.ts rename to ts/@live-compositor/web-wasm/src/worker/output/output.ts index 236106c75..1a93b0ed5 100644 --- a/ts/@live-compositor/web-wasm/src/output/output.ts +++ b/ts/@live-compositor/web-wasm/src/worker/output/output.ts @@ -1,13 +1,13 @@ import type { Frame, Resolution } from '@live-compositor/browser-render'; import type { OutputSink } from './sink'; import CanvasSink from './canvas'; -import type { RegisterOutputRequest } from '@live-compositor/core'; +import type { RegisterOutput } from '../../workerApi'; export class Output { private sink: OutputSink; public readonly resolution: Resolution; - public constructor(request: RegisterOutputRequest) { + public constructor(request: RegisterOutput) { if (request.type === 'canvas') { this.sink = new CanvasSink(request.video.canvas); } else { diff --git a/ts/@live-compositor/web-wasm/src/output/sink.ts b/ts/@live-compositor/web-wasm/src/worker/output/sink.ts similarity index 100% rename from ts/@live-compositor/web-wasm/src/output/sink.ts rename to ts/@live-compositor/web-wasm/src/worker/output/sink.ts diff --git a/ts/@live-compositor/web-wasm/src/worker/pipeline.ts b/ts/@live-compositor/web-wasm/src/worker/pipeline.ts new file mode 100644 index 000000000..bb90c0267 --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/pipeline.ts @@ -0,0 +1,108 @@ +import type { Component, ImageSpec, Renderer } from '@live-compositor/browser-render'; +import type { Framerate } from '../compositor/compositor'; +import type { Logger } from 'pino'; +import type { Api } from 'live-compositor'; +import { createInput } from './input/input'; +import { Output } from './output/output'; +import { Queue } from './queue'; +import type { RegisterInput, RegisterOutput, WorkerEvent, WorkerResponse } from '../workerApi'; +import { workerPostEvent as genericWorkerPostEvent } from './bridge'; +import { CompositorEventType } from '../eventSender'; + +export const workerPostEvent = genericWorkerPostEvent; + +export class Pipeline { + private renderer: Renderer; + private queue: Queue; + private logger: Logger; + private started = false; + + public constructor(options: { renderer: Renderer; framerate: Framerate; logger: Logger }) { + this.renderer = options.renderer; + this.logger = options.logger.child({ element: 'pipeline' }); + this.queue = new Queue(options.framerate, options.renderer, options.logger); + } + + public start() { + if (this.started) { + throw new Error('Compositor was already started'); + } + this.started = true; + this.queue.start(); + } + + public async terminate(): Promise { + // TODO(noituri): Clean all remaining `InputFrame`s & stop input processing + this.queue.stop(); + } + + public async registerInput(inputId: string, request: RegisterInput): Promise { + const input = await createInput(inputId, request, this.logger); + // `addInput` will throw an exception if input already exists + this.queue.addInput(inputId, input); + this.renderer.registerInput(inputId); + const result = input.start(); + return { + type: 'registerInput', + body: { + video_duration_ms: result.videoDurationMs, + audio_duration_ms: result.audioDurationMs, + }, + }; + } + + public async unregisterInput(inputId: string): Promise { + this.queue.removeInput(inputId); + this.renderer.unregisterInput(inputId); + } + + public registerOutput(outputId: string, request: RegisterOutput) { + if (request.video) { + const output = new Output(request); + this.queue.addOutput(outputId, output); + try { + // `updateScene` implicitly registers the output. + // In case of an error, the output has to be manually cleaned up from the renderer. + this.renderer.updateScene( + outputId, + request.video.resolution, + request.video.initial.root as Component + ); + } catch (e) { + this.queue.removeOutput(outputId); + this.renderer.unregisterOutput(outputId); + throw e; + } + } + } + + public async unregisterOutput(outputId: string): Promise { + this.queue.removeOutput(outputId); + this.renderer.unregisterOutput(outputId); + // If we add outputs that can end early or require flushing + // then this needs to be change + workerPostEvent({ + type: CompositorEventType.OUTPUT_DONE, + outputId, + }); + } + + public updateScene(outputId: string, request: Api.UpdateOutputRequest) { + if (!request.video) { + return; + } + const output = this.queue.getOutput(outputId); + if (!output) { + throw new Error(`Unknown output "${outputId}"`); + } + this.renderer.updateScene(outputId, output.resolution, request.video.root as Component); + } + + public async registerImage(imageId: string, request: ImageSpec) { + await this.renderer.registerImage(imageId, request); + } + + public unregisterImage(imageId: string) { + this.renderer.unregisterImage(imageId); + } +} diff --git a/ts/@live-compositor/web-wasm/src/worker/queue.ts b/ts/@live-compositor/web-wasm/src/worker/queue.ts new file mode 100644 index 000000000..c7d31d1bd --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/queue.ts @@ -0,0 +1,120 @@ +import type { Frame, FrameSet, InputId, OutputId, Renderer } from '@live-compositor/browser-render'; +import type { Framerate } from '../compositor/compositor'; +import type { Input } from './input/input'; +import type { Output } from './output/output'; +import type { Interval } from '../utils'; +import { framerateToDurationMs } from '../utils'; +import type { Logger } from 'pino'; + +export type StopQueueFn = () => void; + +export class Queue { + private inputs: Record = {}; + private outputs: Record = {}; + private renderer: Renderer; + private framerate: Framerate; + private currentPts: number; + private startTimeMs?: number; + private queueInterval?: Interval; + private logger: Logger; + + public constructor(framerate: Framerate, renderer: Renderer, logger: Logger) { + this.renderer = renderer; + this.framerate = framerate; + this.currentPts = 0; + this.logger = logger; + } + + public start() { + this.logger.debug('Start queue'); + if (this.queueInterval) { + throw new Error('Queue was already started.'); + } + const tickDuration = framerateToDurationMs(this.framerate); + // TODO: setInterval can drift, this implementation needs to be replaced + this.queueInterval = setInterval(async () => { + await this.onTick(); + this.currentPts += tickDuration; + }, tickDuration); + this.startTimeMs = Date.now(); + for (const input of Object.values(this.inputs)) { + input.updateQueueStartTime(this.startTimeMs); + } + } + + public stop() { + if (this.queueInterval) { + clearTimeout(this.queueInterval); + } + for (const input of Object.values(this.inputs)) { + input.close(); + } + } + + public addInput(inputId: InputId, input: Input) { + if (this.inputs[inputId]) { + throw new Error(`Input "${inputId}" already exists`); + } + if (this.startTimeMs) { + input.updateQueueStartTime(this.startTimeMs); + } + this.inputs[inputId] = input; + } + + public removeInput(inputId: InputId) { + delete this.inputs[inputId]; + } + + public getInput(inputId: InputId): Input | undefined { + return this.inputs[inputId]; + } + + public addOutput(outputId: OutputId, output: Output) { + if (this.outputs[outputId]) { + throw new Error(`Output "${outputId}" already exists`); + } + this.outputs[outputId] = output; + } + + public removeOutput(outputId: OutputId) { + delete this.outputs[outputId]; + } + + public getOutput(outputId: OutputId): Output | undefined { + return this.outputs[outputId]; + } + + private async onTick(): Promise { + const frames = await this.getInputFrames(); + this.logger.trace({ frames }, 'onQueueTick'); + + const outputs = this.renderer.render({ + ptsMs: this.currentPts, + frames, + }); + this.sendOutputs(outputs); + } + + private async getInputFrames(): Promise> { + const frames: Array<[InputId, Frame | undefined]> = await Promise.all( + Object.entries(this.inputs).map(async ([inputId, input]) => [ + inputId, + await input.getFrame(this.currentPts), + ]) + ); + const validFrames = frames.filter((entry): entry is [string, Frame] => !!entry[1]); + + return Object.fromEntries(validFrames); + } + + private sendOutputs(outputs: FrameSet) { + for (const [outputId, frame] of Object.entries(outputs.frames)) { + const output = this.outputs[outputId]; + if (!output) { + this.logger.warn(`Output "${outputId}" not found`); + continue; + } + void output.send(frame); + } + } +} diff --git a/ts/@live-compositor/web-wasm/src/worker/runWorker.ts b/ts/@live-compositor/web-wasm/src/worker/runWorker.ts new file mode 100644 index 000000000..02da4bc84 --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/worker/runWorker.ts @@ -0,0 +1,49 @@ +import { loadWasmModule, Renderer } from '@live-compositor/browser-render'; +import { Pipeline } from './pipeline'; +import { pino, type Logger } from 'pino'; +import type { InitOptions, WorkerMessage, WorkerResponse } from '../workerApi'; +import { registerWorkerEntrypoint } from './bridge'; +import { assert } from '../utils'; + +let instance: Pipeline | undefined; +let onMessageLogger: Logger = pino({ level: 'warn' }); + +async function initInstance(options: InitOptions) { + await loadWasmModule(options.wasmBundleUrl); + const renderer = await Renderer.create({ + streamFallbackTimeoutMs: 500, + }); + const logger = pino({ level: options.loggerLevel }).child({ runtime: 'worker' }); + onMessageLogger = logger.child({ element: 'onMessage' }); + instance = new Pipeline({ renderer, framerate: options.framerate, logger }); +} + +registerWorkerEntrypoint( + async (request: WorkerMessage): Promise => { + if (request.type === 'init') { + return await initInstance(request); + } + assert(instance); + if (request.type === 'registerInput') { + return await instance.registerInput(request.inputId, request.input); + } else if (request.type === 'registerOutput') { + return instance.registerOutput(request.outputId, request.output); + } else if (request.type === 'registerImage') { + return await instance.registerImage(request.imageId, request.image); + } else if (request.type === 'unregisterInput') { + return await instance.unregisterInput(request.inputId); + } else if (request.type === 'unregisterOutput') { + return await instance.unregisterOutput(request.outputId); + } else if (request.type === 'unregisterImage') { + return instance.unregisterImage(request.imageId); + } else if (request.type === 'updateScene') { + return instance.updateScene(request.outputId, request.output); + } else if (request.type === 'start') { + return instance.start(); + } else if (request.type === 'terminate') { + return await instance.terminate(); + } else { + onMessageLogger.warn(request, 'Web worker received unknown message.'); + } + } +); diff --git a/ts/@live-compositor/web-wasm/src/workerApi.ts b/ts/@live-compositor/web-wasm/src/workerApi.ts new file mode 100644 index 000000000..9b032db9d --- /dev/null +++ b/ts/@live-compositor/web-wasm/src/workerApi.ts @@ -0,0 +1,94 @@ +import type { _liveCompositorInternals, Api } from 'live-compositor'; +import type { ImageSpec, Resolution } from '@live-compositor/browser-render'; +import type { Framerate } from './compositor/compositor'; + +export type RegisterInput = + | { + type: 'mp4'; + url: string; + } + | { + type: 'camera'; + stream: ReadableStream; + } + | { + type: 'screen_capture'; + stream: ReadableStream; + }; + +export type RegisterOutput = { + type: 'canvas'; + video: { + canvas: OffscreenCanvas; + resolution: Resolution; + initial: Api.Video; + }; +}; + +export type InitOptions = { + framerate: Framerate; + wasmBundleUrl: string; + loggerLevel: string; +}; + +export type WorkerMessage = + | ({ type: 'init' } & InitOptions) + | { + type: 'start'; + } + | { + type: 'registerInput'; + inputId: string; + input: RegisterInput; + } + | { + type: 'unregisterInput'; + inputId: string; + } + | { + type: 'registerOutput'; + outputId: string; + output: RegisterOutput; + } + | { + type: 'updateScene'; + outputId: string; + output: Api.UpdateOutputRequest; + } + | { + type: 'unregisterOutput'; + outputId: string; + } + | { + type: 'registerImage'; + imageId: string; + image: ImageSpec; + } + | { + type: 'unregisterImage'; + imageId: string; + } + | { + type: 'terminate'; + }; + +export type WorkerResponse = void | { + type: 'registerInput'; + body: { video_duration_ms?: number; audio_duration_ms?: number }; +}; + +export type WorkerEvent = + | { + type: + | _liveCompositorInternals.CompositorEventType.AUDIO_INPUT_DELIVERED + | _liveCompositorInternals.CompositorEventType.VIDEO_INPUT_DELIVERED + | _liveCompositorInternals.CompositorEventType.AUDIO_INPUT_PLAYING + | _liveCompositorInternals.CompositorEventType.VIDEO_INPUT_PLAYING + | _liveCompositorInternals.CompositorEventType.AUDIO_INPUT_EOS + | _liveCompositorInternals.CompositorEventType.VIDEO_INPUT_EOS; + inputId: string; + } + | { + type: _liveCompositorInternals.CompositorEventType.OUTPUT_DONE; + outputId: string; + }; diff --git a/ts/examples/vite-browser-render/src/App.tsx b/ts/examples/vite-browser-render/src/App.tsx index 1763b79cc..ddbb0f624 100644 --- a/ts/examples/vite-browser-render/src/App.tsx +++ b/ts/examples/vite-browser-render/src/App.tsx @@ -1,17 +1,23 @@ import { useState } from 'react'; import './App.css'; import Counter from './examples/Counter'; -import SimpleMp4Example from './examples/SimpleMp4Example'; +import InputMp4Example from './examples/InputMp4Example'; +import ComponentMp4Example from './examples/ComponentMp4Example'; import MultipleCompositors from './examples/MultipleCompositors'; +import Camera from './examples/CameraExample'; +import ScreenCapture from './examples/ScreenCaptureExample'; import { setWasmBundleUrl } from '@live-compositor/web-wasm'; -setWasmBundleUrl('assets/live-compositor.wasm'); +setWasmBundleUrl('/assets/live-compositor.wasm'); function App() { const EXAMPLES = { counter: , - simpleMp4: , + inputMp4: , + componentMp4: , multipleCompositors: , + camera: , + screenCapture: , home: , }; const [currentExample, setCurrentExample] = useState('home'); @@ -21,10 +27,13 @@ function App() {

Examples

- + + + +
{EXAMPLES[currentExample]}
@@ -40,12 +49,23 @@ function Home() { @live-compositor/web-wasm - LiveCompositor in the browser
  • - Simple Mp4 - Take MP4 file as an input and render output on canvas + Input Stream Mp4 - Register MP4 file as an input stream and render output on + canvas. +
  • +
  • + Component Mp4 - Add 2 MP4 component (one after the other) to the scene and + render output on canvas.
  • Multiple LiveCompositor instances - Runs multiple LiveCompositor instances at the same time.
  • +
  • + Camera - Use webcam as an input and render output on canvas. +
  • +
  • + Screen Capture - Use screen capture as an input and render output on canvas. +
  • @live-compositor/browser-render - Rendering engine from LiveCompositor

    diff --git a/ts/examples/vite-browser-render/src/components/CompositorCanvas.tsx b/ts/examples/vite-browser-render/src/components/CompositorCanvas.tsx index 37234dd73..4d48c5c9f 100644 --- a/ts/examples/vite-browser-render/src/components/CompositorCanvas.tsx +++ b/ts/examples/vite-browser-render/src/components/CompositorCanvas.tsx @@ -21,6 +21,7 @@ export default function CompositorCanvas(props: CompositorCanvasProps) { if (!canvas) { return; } + const compositor = new LiveCompositor({}); await compositor.init(); @@ -31,10 +32,12 @@ export default function CompositorCanvas(props: CompositorCanvasProps) { await compositor.registerOutput('output', children, { type: 'canvas', - canvas: canvas, - resolution: { - width: Number(canvasProps.width ?? canvas.width), - height: Number(canvasProps.height ?? canvas.height), + video: { + canvas: canvas as any, + resolution: { + width: Number(canvasProps.width ?? canvas.width), + height: Number(canvasProps.height ?? canvas.height), + }, }, }); diff --git a/ts/examples/vite-browser-render/src/examples/CameraExample.tsx b/ts/examples/vite-browser-render/src/examples/CameraExample.tsx new file mode 100644 index 000000000..e527e560a --- /dev/null +++ b/ts/examples/vite-browser-render/src/examples/CameraExample.tsx @@ -0,0 +1,36 @@ +import { useCallback } from 'react'; +import type { LiveCompositor } from '@live-compositor/web-wasm'; +import { InputStream, Rescaler, Text, View } from 'live-compositor'; +import CompositorCanvas from '../components/CompositorCanvas'; + +function ScreenCapture() { + const onCanvasCreate = useCallback(async (compositor: LiveCompositor) => { + await compositor.registerFont( + 'https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9a6Vc.ttf' + ); + await compositor.registerInput('camera', { type: 'camera' }); + }, []); + + return ( +
    + + + +
    + ); +} + +function Scene() { + return ( + + + + + + Camera input + + + ); +} + +export default ScreenCapture; diff --git a/ts/examples/vite-browser-render/src/examples/ComponentMp4Example.tsx b/ts/examples/vite-browser-render/src/examples/ComponentMp4Example.tsx new file mode 100644 index 000000000..ed8ba4eeb --- /dev/null +++ b/ts/examples/vite-browser-render/src/examples/ComponentMp4Example.tsx @@ -0,0 +1,46 @@ +import { useCallback } from 'react'; +import type { LiveCompositor } from '@live-compositor/web-wasm'; +import { Mp4, Slide, SlideShow, Text, View } from 'live-compositor'; +import CompositorCanvas from '../components/CompositorCanvas'; + +const FIRST_MP4_URL = + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'; + +const SECOND_MP4_URL = + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4'; + +function InputMp4Example() { + const onCanvasCreate = useCallback(async (compositor: LiveCompositor) => { + await compositor.registerFont( + 'https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9a6Vc.ttf' + ); + }, []); + + return ( +
    + + + +
    + ); +} + +function Scene() { + return ( + + + + + + + + + + + Playing MP4 file + + + ); +} + +export default InputMp4Example; diff --git a/ts/examples/vite-browser-render/src/examples/SimpleMp4Example.tsx b/ts/examples/vite-browser-render/src/examples/InputMp4Example.tsx similarity index 96% rename from ts/examples/vite-browser-render/src/examples/SimpleMp4Example.tsx rename to ts/examples/vite-browser-render/src/examples/InputMp4Example.tsx index 44b3eb752..27e612e36 100644 --- a/ts/examples/vite-browser-render/src/examples/SimpleMp4Example.tsx +++ b/ts/examples/vite-browser-render/src/examples/InputMp4Example.tsx @@ -6,7 +6,7 @@ import CompositorCanvas from '../components/CompositorCanvas'; const MP4_URL = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'; -function SimpleMp4Example() { +function InputMp4Example() { const onCanvasCreate = useCallback(async (compositor: LiveCompositor) => { await compositor.registerFont( 'https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9a6Vc.ttf' @@ -57,4 +57,4 @@ function Scene() { ); } -export default SimpleMp4Example; +export default InputMp4Example; diff --git a/ts/examples/vite-browser-render/src/examples/ScreenCaptureExample.tsx b/ts/examples/vite-browser-render/src/examples/ScreenCaptureExample.tsx new file mode 100644 index 000000000..06393c4df --- /dev/null +++ b/ts/examples/vite-browser-render/src/examples/ScreenCaptureExample.tsx @@ -0,0 +1,36 @@ +import { useCallback } from 'react'; +import type { LiveCompositor } from '@live-compositor/web-wasm'; +import { InputStream, Rescaler, Text, View } from 'live-compositor'; +import CompositorCanvas from '../components/CompositorCanvas'; + +function CameraExample() { + const onCanvasCreate = useCallback(async (compositor: LiveCompositor) => { + await compositor.registerFont( + 'https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9a6Vc.ttf' + ); + await compositor.registerInput('screen', { type: 'screen_capture' }); + }, []); + + return ( +
    + + + +
    + ); +} + +function Scene() { + return ( + + + + + + Camera input + + + ); +} + +export default CameraExample; diff --git a/ts/package.json b/ts/package.json index aa76dedae..dc8fb3396 100644 --- a/ts/package.json +++ b/ts/package.json @@ -30,7 +30,7 @@ "json-schema-to-typescript": "^15.0.1", "prettier": "^3.3.3", "rimraf": "^6.0.1", - "typescript": "5.5.3" + "typescript": "5.7.2" }, "overrides": { "rollup-plugin-copy": { diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 0b674af93..2108af620 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -13,13 +13,13 @@ importers: version: 0.2.4 '@typescript-eslint/eslint-plugin': specifier: ^8.8.1 - version: 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint@9.17.0)(typescript@5.5.3) + version: 8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint@9.17.0)(typescript@5.7.2) '@typescript-eslint/parser': specifier: ^8.8.1 - version: 8.19.1(eslint@9.17.0)(typescript@5.5.3) + version: 8.18.1(eslint@9.17.0)(typescript@5.7.2) concurrently: specifier: ^9.0.1 - version: 9.1.2 + version: 9.1.0 eslint: specifier: ^9.12.0 version: 9.17.0 @@ -31,7 +31,7 @@ importers: version: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.17.0) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0) + version: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0) eslint-plugin-prettier: specifier: ^5.2.1 version: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.17.0))(eslint@9.17.0)(prettier@3.4.2) @@ -54,8 +54,8 @@ importers: specifier: ^6.0.1 version: 6.0.1 typescript: - specifier: 5.5.3 - version: 5.5.3 + specifier: 5.7.2 + version: 5.7.2 '@live-compositor/browser-render': dependencies: @@ -65,16 +65,16 @@ importers: devDependencies: '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.30.1)(tslib@2.8.1)(typescript@5.5.3) + version: 11.1.6(rollup@4.28.1)(tslib@2.8.1)(typescript@5.7.2) rollup: specifier: ^4.21.2 - version: 4.30.1 + version: 4.28.1 rollup-plugin-copy: specifier: ^3.5.0 version: 3.5.0 rollup-plugin-dts: specifier: ^6.1.1 - version: 6.1.1(rollup@4.30.1)(typescript@5.5.3) + version: 6.1.1(rollup@4.28.1)(typescript@5.7.2) wasm-pack: specifier: ^0.13.0 version: 0.13.1 @@ -86,7 +86,7 @@ importers: version: link:../../live-compositor pino: specifier: ^9.5.0 - version: 9.6.0 + version: 9.5.0 react: specifier: '*' version: 18.3.1 @@ -96,7 +96,7 @@ importers: devDependencies: '@types/react': specifier: ^18.3.3 - version: 18.3.18 + version: 18.3.17 '@types/react-reconciler': specifier: 0.28.8 version: 0.28.8 @@ -114,7 +114,7 @@ importers: version: 2.7.0 pino: specifier: ^9.5.0 - version: 9.6.0 + version: 9.5.0 pino-pretty: specifier: ^13.0.0 version: 13.0.0 @@ -133,7 +133,7 @@ importers: version: 11.0.4 '@types/node': specifier: ^20.14.10 - version: 20.17.12 + version: 20.17.10 '@types/node-fetch': specifier: ^2.6.11 version: 2.6.12 @@ -166,11 +166,11 @@ importers: version: 6.1.0 pino: specifier: ^9.5.0 - version: 9.6.0 + version: 9.5.0 devDependencies: '@types/react': specifier: ^18.3.3 - version: 18.3.18 + version: 18.3.17 create-live-compositor: dependencies: @@ -207,17 +207,17 @@ importers: version: 18.3.1 zustand: specifier: 4.5.5 - version: 4.5.5(@types/react@18.3.18)(react@18.3.1) + version: 4.5.5(@types/react@18.3.17)(react@18.3.1) devDependencies: '@types/express': specifier: ^4.17.21 version: 4.17.21 '@types/node': specifier: ^20.14.10 - version: 20.17.12 + version: 20.17.10 '@types/react': specifier: ^18.3.3 - version: 18.3.18 + version: 18.3.17 create-live-compositor/templates/node-minimal: dependencies: @@ -233,13 +233,13 @@ importers: devDependencies: '@types/node': specifier: ^20.14.10 - version: 20.17.12 + version: 20.17.10 '@types/react': specifier: ^18.3.3 - version: 18.3.18 + version: 18.3.17 typescript: specifier: ^5.5.3 - version: 5.5.3 + version: 5.7.2 examples/node-examples: dependencies: @@ -260,20 +260,20 @@ importers: version: 18.3.1 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.17.12)(typescript@5.5.3) + version: 10.9.2(@types/node@20.17.10)(typescript@5.7.2) devDependencies: '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 '@types/node': specifier: ^20.14.10 - version: 20.17.12 + version: 20.17.10 '@types/node-fetch': specifier: ^2.6.11 version: 2.6.12 '@types/react': specifier: ^18.3.3 - version: 18.3.18 + version: 18.3.17 examples/vite-browser-render: dependencies: @@ -298,25 +298,25 @@ importers: devDependencies: '@types/react': specifier: ^18.3.3 - version: 18.3.18 + version: 18.3.17 '@types/react-dom': specifier: ^18.3.0 - version: 18.3.5(@types/react@18.3.18) + version: 18.3.5(@types/react@18.3.17) '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.4(vite@5.4.11(@types/node@20.17.12)) + version: 4.3.4(vite@5.4.11(@types/node@20.17.10)) typescript: specifier: ^5.5.3 - version: 5.5.3 + version: 5.7.2 typescript-eslint: specifier: ^8.0.1 - version: 8.19.1(eslint@9.17.0)(typescript@5.5.3) + version: 8.18.1(eslint@9.17.0)(typescript@5.7.2) vite: specifier: ^5.4.1 - version: 5.4.11(@types/node@20.17.12) + version: 5.4.11(@types/node@20.17.10) vite-plugin-static-copy: specifier: ^1.0.6 - version: 1.0.6(vite@5.4.11(@types/node@20.17.12)) + version: 1.0.6(vite@5.4.11(@types/node@20.17.10)) live-compositor: dependencies: @@ -326,7 +326,7 @@ importers: devDependencies: '@types/react': specifier: ^18.3.3 - version: 18.3.18 + version: 18.3.17 packages: @@ -694,98 +694,98 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.30.1': - resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} + '@rollup/rollup-android-arm-eabi@4.28.1': + resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.30.1': - resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==} + '@rollup/rollup-android-arm64@4.28.1': + resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.30.1': - resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==} + '@rollup/rollup-darwin-arm64@4.28.1': + resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.30.1': - resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==} + '@rollup/rollup-darwin-x64@4.28.1': + resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.30.1': - resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==} + '@rollup/rollup-freebsd-arm64@4.28.1': + resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.30.1': - resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==} + '@rollup/rollup-freebsd-x64@4.28.1': + resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': - resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==} + '@rollup/rollup-linux-arm-gnueabihf@4.28.1': + resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.30.1': - resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==} + '@rollup/rollup-linux-arm-musleabihf@4.28.1': + resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.30.1': - resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==} + '@rollup/rollup-linux-arm64-gnu@4.28.1': + resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.30.1': - resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==} + '@rollup/rollup-linux-arm64-musl@4.28.1': + resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': - resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': + resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': - resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': + resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.30.1': - resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==} + '@rollup/rollup-linux-riscv64-gnu@4.28.1': + resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.30.1': - resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==} + '@rollup/rollup-linux-s390x-gnu@4.28.1': + resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.30.1': - resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==} + '@rollup/rollup-linux-x64-gnu@4.28.1': + resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.30.1': - resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==} + '@rollup/rollup-linux-x64-musl@4.28.1': + resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.30.1': - resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} + '@rollup/rollup-win32-arm64-msvc@4.28.1': + resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.30.1': - resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==} + '@rollup/rollup-win32-ia32-msvc@4.28.1': + resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.30.1': - resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} + '@rollup/rollup-win32-x64-msvc@4.28.1': + resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} cpu: [x64] os: [win32] @@ -852,8 +852,8 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - '@types/lodash@4.17.14': - resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} + '@types/lodash@4.17.13': + resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -864,8 +864,8 @@ packages: '@types/node-fetch@2.6.12': resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} - '@types/node@20.17.12': - resolution: {integrity: sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==} + '@types/node@20.17.10': + resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} '@types/prompts@2.4.9': resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} @@ -887,8 +887,8 @@ packages: '@types/react-reconciler@0.28.8': resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==} - '@types/react@18.3.18': - resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/react@18.3.17': + resolution: {integrity: sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw==} '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} @@ -902,51 +902,51 @@ packages: '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} - '@typescript-eslint/eslint-plugin@8.19.1': - resolution: {integrity: sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==} + '@typescript-eslint/eslint-plugin@8.18.1': + resolution: {integrity: sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.19.1': - resolution: {integrity: sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==} + '@typescript-eslint/parser@8.18.1': + resolution: {integrity: sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@8.19.1': - resolution: {integrity: sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==} + '@typescript-eslint/scope-manager@8.18.1': + resolution: {integrity: sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.19.1': - resolution: {integrity: sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==} + '@typescript-eslint/type-utils@8.18.1': + resolution: {integrity: sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@8.19.1': - resolution: {integrity: sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==} + '@typescript-eslint/types@8.18.1': + resolution: {integrity: sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.19.1': - resolution: {integrity: sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==} + '@typescript-eslint/typescript-estree@8.18.1': + resolution: {integrity: sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.19.1': - resolution: {integrity: sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==} + '@typescript-eslint/utils@8.18.1': + resolution: {integrity: sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/visitor-keys@8.19.1': - resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} + '@typescript-eslint/visitor-keys@8.18.1': + resolution: {integrity: sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-react@4.3.4': @@ -1002,8 +1002,8 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} array-flatten@1.1.1: @@ -1072,8 +1072,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + browserslist@4.24.3: + resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1097,8 +1097,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001692: - resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1140,8 +1140,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@9.1.2: - resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} + concurrently@9.1.0: + resolution: {integrity: sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==} engines: {node: '>=18'} hasBin: true @@ -1173,12 +1173,12 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} engines: {node: '>= 0.4'} data-view-byte-offset@1.0.1: @@ -1258,8 +1258,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.79: - resolution: {integrity: sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==} + electron-to-chromium@1.5.74: + resolution: {integrity: sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1278,12 +1278,12 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enhanced-resolve@5.18.0: - resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} + enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} - es-abstract@1.23.9: - resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + es-abstract@1.23.6: + resolution: {integrity: sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -1298,8 +1298,8 @@ packages: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} es-shim-unscopables@1.0.2: @@ -1465,8 +1465,8 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: @@ -1482,8 +1482,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} fdir@6.4.2: resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} @@ -1567,8 +1567,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + function.prototype.name@1.1.7: + resolution: {integrity: sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -1582,12 +1582,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.7: - resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + get-intrinsic@1.2.6: + resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} engines: {node: '>= 0.4'} get-symbol-description@1.1.0: @@ -1717,8 +1713,8 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} - is-async-function@2.1.0: - resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} is-bigint@1.1.0: @@ -1740,8 +1736,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.0: + resolution: {integrity: sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -1764,8 +1760,8 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} is-glob@4.0.3: @@ -1776,6 +1772,10 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -2079,10 +2079,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2156,8 +2152,8 @@ packages: pino-std-serializers@7.0.0: resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - pino@9.6.0: - resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==} + pino@9.5.0: + resolution: {integrity: sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==} hasBin: true possible-typed-array-names@1.0.0: @@ -2181,8 +2177,8 @@ packages: engines: {node: '>=14'} hasBin: true - process-warning@4.0.1: - resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + process-warning@4.0.0: + resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} @@ -2244,12 +2240,12 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + reflect.getprototypeof@1.0.9: + resolution: {integrity: sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==} engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + regexp.prototype.flags@1.5.3: + resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} require-directory@2.1.1: @@ -2263,9 +2259,8 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} + resolve@1.22.9: + resolution: {integrity: sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==} hasBin: true reusify@1.0.4: @@ -2297,8 +2292,8 @@ packages: rollup: ^3.29.4 || ^4 typescript: ^4.5 || ^5.0 - rollup@4.30.1: - resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==} + rollup@4.28.1: + resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2315,10 +2310,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} @@ -2364,10 +2355,6 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -2514,11 +2501,11 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-api-utils@2.0.0: - resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} - engines: {node: '>=18.12'} + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} peerDependencies: - typescript: '>=4.8.4' + typescript: '>=4.2.0' ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} @@ -2567,15 +2554,15 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.19.1: - resolution: {integrity: sha512-LKPUQpdEMVOeKluHi8md7rwLcoXHhwvWp3x+sJkMuq3gGm9yaYJtPo8sRZSblMFJ5pcOGCAak/scKf1mvZDlQw==} + typescript-eslint@8.18.1: + resolution: {integrity: sha512-Mlaw6yxuaDEPQvb/2Qwu3/TfgeBHy9iTJ3mTwe7OvpPmF6KPQjVOfGyEJpPv6Ez2C34OODChhXrzYw/9phI0MQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - typescript@5.5.3: - resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true @@ -2598,8 +2585,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2820,7 +2807,7 @@ snapshots: dependencies: '@babel/compat-data': 7.26.3 '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 + browserslist: 4.24.3 lru-cache: 5.1.1 semver: 6.3.1 @@ -3066,7 +3053,7 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + fastq: 1.17.1 '@nolyfill/is-core-module@1.0.39': {} @@ -3075,78 +3062,78 @@ snapshots: '@pkgr/core@0.1.1': {} - '@rollup/plugin-typescript@11.1.6(rollup@4.30.1)(tslib@2.8.1)(typescript@5.5.3)': + '@rollup/plugin-typescript@11.1.6(rollup@4.28.1)(tslib@2.8.1)(typescript@5.7.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) - resolve: 1.22.10 - typescript: 5.5.3 + '@rollup/pluginutils': 5.1.4(rollup@4.28.1) + resolve: 1.22.9 + typescript: 5.7.2 optionalDependencies: - rollup: 4.30.1 + rollup: 4.28.1 tslib: 2.8.1 - '@rollup/pluginutils@5.1.4(rollup@4.30.1)': + '@rollup/pluginutils@5.1.4(rollup@4.28.1)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.30.1 + rollup: 4.28.1 - '@rollup/rollup-android-arm-eabi@4.30.1': + '@rollup/rollup-android-arm-eabi@4.28.1': optional: true - '@rollup/rollup-android-arm64@4.30.1': + '@rollup/rollup-android-arm64@4.28.1': optional: true - '@rollup/rollup-darwin-arm64@4.30.1': + '@rollup/rollup-darwin-arm64@4.28.1': optional: true - '@rollup/rollup-darwin-x64@4.30.1': + '@rollup/rollup-darwin-x64@4.28.1': optional: true - '@rollup/rollup-freebsd-arm64@4.30.1': + '@rollup/rollup-freebsd-arm64@4.28.1': optional: true - '@rollup/rollup-freebsd-x64@4.30.1': + '@rollup/rollup-freebsd-x64@4.28.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + '@rollup/rollup-linux-arm-gnueabihf@4.28.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.30.1': + '@rollup/rollup-linux-arm-musleabihf@4.28.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.30.1': + '@rollup/rollup-linux-arm64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.30.1': + '@rollup/rollup-linux-arm64-musl@4.28.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.30.1': + '@rollup/rollup-linux-riscv64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.30.1': + '@rollup/rollup-linux-s390x-gnu@4.28.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.30.1': + '@rollup/rollup-linux-x64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-x64-musl@4.30.1': + '@rollup/rollup-linux-x64-musl@4.28.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.30.1': + '@rollup/rollup-win32-arm64-msvc@4.28.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.30.1': + '@rollup/rollup-win32-ia32-msvc@4.28.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.30.1': + '@rollup/rollup-win32-x64-msvc@4.28.1': optional: true '@rtsao/scc@1.1.0': {} @@ -3183,17 +3170,17 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/connect@3.4.38': dependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/estree@1.0.6': {} '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -3208,16 +3195,16 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/fs-extra@8.1.5': dependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/http-errors@2.0.4': {} @@ -3227,9 +3214,9 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 - '@types/lodash@4.17.14': {} + '@types/lodash@4.17.13': {} '@types/mime@1.3.5': {} @@ -3237,16 +3224,16 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 form-data: 4.0.1 - '@types/node@20.17.12': + '@types/node@20.17.10': dependencies: undici-types: 6.19.8 '@types/prompts@2.4.9': dependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 kleur: 3.0.3 '@types/prop-types@15.7.14': {} @@ -3255,15 +3242,15 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/react-dom@18.3.5(@types/react@18.3.18)': + '@types/react-dom@18.3.5(@types/react@18.3.17)': dependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.17 '@types/react-reconciler@0.28.8': dependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.17 - '@types/react@18.3.18': + '@types/react@18.3.17': dependencies: '@types/prop-types': 15.7.14 csstype: 3.1.3 @@ -3271,105 +3258,105 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.17.12 + '@types/node': 20.17.10 '@types/send': 0.17.4 '@types/uuid@10.0.0': {} '@types/ws@8.5.13': dependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 - '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint@9.17.0)(typescript@5.5.3)': + '@typescript-eslint/eslint-plugin@8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint@9.17.0)(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0)(typescript@5.5.3) - '@typescript-eslint/scope-manager': 8.19.1 - '@typescript-eslint/type-utils': 8.19.1(eslint@9.17.0)(typescript@5.5.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0)(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 8.19.1 + '@typescript-eslint/parser': 8.18.1(eslint@9.17.0)(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.18.1 + '@typescript-eslint/type-utils': 8.18.1(eslint@9.17.0)(typescript@5.7.2) + '@typescript-eslint/utils': 8.18.1(eslint@9.17.0)(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.18.1 eslint: 9.17.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 2.0.0(typescript@5.5.3) - typescript: 5.5.3 + ts-api-utils: 1.4.3(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3)': + '@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2)': dependencies: - '@typescript-eslint/scope-manager': 8.19.1 - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 8.19.1 + '@typescript-eslint/scope-manager': 8.18.1 + '@typescript-eslint/types': 8.18.1 + '@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.18.1 debug: 4.4.0 eslint: 9.17.0 - typescript: 5.5.3 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.19.1': + '@typescript-eslint/scope-manager@8.18.1': dependencies: - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/visitor-keys': 8.19.1 + '@typescript-eslint/types': 8.18.1 + '@typescript-eslint/visitor-keys': 8.18.1 - '@typescript-eslint/type-utils@8.19.1(eslint@9.17.0)(typescript@5.5.3)': + '@typescript-eslint/type-utils@8.18.1(eslint@9.17.0)(typescript@5.7.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.5.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0)(typescript@5.5.3) + '@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2) + '@typescript-eslint/utils': 8.18.1(eslint@9.17.0)(typescript@5.7.2) debug: 4.4.0 eslint: 9.17.0 - ts-api-utils: 2.0.0(typescript@5.5.3) - typescript: 5.5.3 + ts-api-utils: 1.4.3(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.19.1': {} + '@typescript-eslint/types@8.18.1': {} - '@typescript-eslint/typescript-estree@8.19.1(typescript@5.5.3)': + '@typescript-eslint/typescript-estree@8.18.1(typescript@5.7.2)': dependencies: - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/visitor-keys': 8.19.1 + '@typescript-eslint/types': 8.18.1 + '@typescript-eslint/visitor-keys': 8.18.1 debug: 4.4.0 - fast-glob: 3.3.3 + fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 2.0.0(typescript@5.5.3) - typescript: 5.5.3 + ts-api-utils: 1.4.3(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.19.1(eslint@9.17.0)(typescript@5.5.3)': + '@typescript-eslint/utils@8.18.1(eslint@9.17.0)(typescript@5.7.2)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) - '@typescript-eslint/scope-manager': 8.19.1 - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.5.3) + '@typescript-eslint/scope-manager': 8.18.1 + '@typescript-eslint/types': 8.18.1 + '@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2) eslint: 9.17.0 - typescript: 5.5.3 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.19.1': + '@typescript-eslint/visitor-keys@8.18.1': dependencies: - '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/types': 8.18.1 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-react@4.3.4(vite@5.4.11(@types/node@20.17.12))': + '@vitejs/plugin-react@4.3.4(vite@5.4.11(@types/node@20.17.10))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.11(@types/node@20.17.12) + vite: 5.4.11(@types/node@20.17.10) transitivePeerDependencies: - supports-color @@ -3414,9 +3401,9 @@ snapshots: argparse@2.0.1: {} - array-buffer-byte-length@1.0.2: + array-buffer-byte-length@1.0.1: dependencies: - call-bound: 1.0.3 + call-bind: 1.0.8 is-array-buffer: 3.0.5 array-flatten@1.1.1: {} @@ -3425,9 +3412,9 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 es-object-atoms: 1.0.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 is-string: 1.1.1 array-union@2.1.0: {} @@ -3436,7 +3423,7 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 es-errors: 1.3.0 es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 @@ -3445,24 +3432,24 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 es-shim-unscopables: 1.0.2 array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 es-shim-unscopables: 1.0.2 arraybuffer.prototype.slice@1.0.4: dependencies: - array-buffer-byte-length: 1.0.2 + array-buffer-byte-length: 1.0.1 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 is-array-buffer: 3.0.5 asynckit@0.4.0: {} @@ -3521,12 +3508,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.4: + browserslist@4.24.3: dependencies: - caniuse-lite: 1.0.30001692 - electron-to-chromium: 1.5.79 + caniuse-lite: 1.0.30001690 + electron-to-chromium: 1.5.74 node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + update-browserslist-db: 1.1.1(browserslist@4.24.3) bytes@3.1.2: {} @@ -3539,17 +3526,17 @@ snapshots: dependencies: call-bind-apply-helpers: 1.0.1 es-define-property: 1.0.1 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 set-function-length: 1.2.2 call-bound@1.0.3: dependencies: call-bind-apply-helpers: 1.0.1 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 callsites@3.1.0: {} - caniuse-lite@1.0.30001692: {} + caniuse-lite@1.0.30001690: {} chalk@4.1.2: dependencies: @@ -3594,7 +3581,7 @@ snapshots: concat-map@0.0.1: {} - concurrently@9.1.2: + concurrently@9.1.0: dependencies: chalk: 4.1.2 lodash: 4.17.21 @@ -3626,15 +3613,15 @@ snapshots: csstype@3.1.3: {} - data-view-buffer@1.0.2: + data-view-buffer@1.0.1: dependencies: - call-bound: 1.0.3 + call-bind: 1.0.8 es-errors: 1.3.0 is-data-view: 1.0.2 - data-view-byte-length@1.0.2: + data-view-byte-length@1.0.1: dependencies: - call-bound: 1.0.3 + call-bind: 1.0.8 es-errors: 1.3.0 is-data-view: 1.0.2 @@ -3698,7 +3685,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.79: {} + electron-to-chromium@1.5.74: {} emoji-regex@8.0.0: {} @@ -3712,29 +3699,28 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.18.0: + enhanced-resolve@5.17.1: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 - es-abstract@1.23.9: + es-abstract@1.23.6: dependencies: - array-buffer-byte-length: 1.0.2 + array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 call-bind: 1.0.8 call-bound: 1.0.3 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.0.0 - es-set-tostringtag: 2.1.0 + es-set-tostringtag: 2.0.3 es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.2.7 - get-proto: 1.0.1 + function.prototype.name: 1.1.7 + get-intrinsic: 1.2.6 get-symbol-description: 1.1.0 globalthis: 1.0.4 gopd: 1.2.0 @@ -3746,6 +3732,7 @@ snapshots: is-array-buffer: 3.0.5 is-callable: 1.2.7 is-data-view: 1.0.2 + is-negative-zero: 2.0.3 is-regex: 1.2.1 is-shared-array-buffer: 1.0.4 is-string: 1.1.1 @@ -3755,12 +3742,9 @@ snapshots: object-inspect: 1.13.3 object-keys: 1.1.1 object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 + regexp.prototype.flags: 1.5.3 safe-array-concat: 1.1.3 - safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 - set-proto: 1.0.0 string.prototype.trim: 1.2.10 string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 @@ -3779,10 +3763,9 @@ snapshots: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.1.0: + es-set-tostringtag@2.0.3: dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -3835,8 +3818,8 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 - is-core-module: 2.16.1 - resolve: 1.22.10 + is-core-module: 2.16.0 + resolve: 1.22.9 transitivePeerDependencies: - supports-color @@ -3844,30 +3827,30 @@ snapshots: dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 - enhanced-resolve: 5.18.0 + enhanced-resolve: 5.17.1 eslint: 9.17.0 - fast-glob: 3.3.3 + fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.3.0 is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0)(typescript@5.5.3) + '@typescript-eslint/parser': 8.18.1(eslint@9.17.0)(typescript@5.7.2) eslint: 9.17.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.17.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -3878,9 +3861,9 @@ snapshots: doctrine: 2.1.0 eslint: 9.17.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0) hasown: 2.0.2 - is-core-module: 2.16.1 + is-core-module: 2.16.0 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 @@ -3890,7 +3873,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0)(typescript@5.5.3) + '@typescript-eslint/parser': 8.18.1(eslint@9.17.0)(typescript@5.7.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4025,7 +4008,7 @@ snapshots: fast-diff@1.3.0: {} - fast-glob@3.3.3: + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -4041,7 +4024,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fastq@1.18.0: + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -4125,10 +4108,9 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.8: + function.prototype.name@1.1.7: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 define-properties: 1.2.1 functions-have-names: 1.2.3 hasown: 2.0.2 @@ -4140,29 +4122,24 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.2.7: + get-intrinsic@1.2.6: dependencies: call-bind-apply-helpers: 1.0.1 + dunder-proto: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.0.0 function-bind: 1.1.2 - get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 math-intrinsics: 1.1.0 - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.0.0 - get-symbol-description@1.1.0: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 get-tsconfig@4.8.1: dependencies: @@ -4219,7 +4196,7 @@ snapshots: '@types/glob': 7.2.0 array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.3 + fast-glob: 3.3.2 glob: 7.2.3 ignore: 5.3.2 merge2: 1.4.1 @@ -4295,14 +4272,11 @@ snapshots: dependencies: call-bind: 1.0.8 call-bound: 1.0.3 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 - is-async-function@2.1.0: + is-async-function@2.0.0: dependencies: - call-bound: 1.0.3 - get-proto: 1.0.1 has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 is-bigint@1.1.0: dependencies: @@ -4323,14 +4297,14 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.16.1: + is-core-module@2.16.0: dependencies: hasown: 2.0.2 is-data-view@1.0.2: dependencies: call-bound: 1.0.3 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 is-typed-array: 1.1.15 is-date-object@1.1.0: @@ -4346,12 +4320,9 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.0: + is-generator-function@1.0.10: dependencies: - call-bound: 1.0.3 - get-proto: 1.0.1 has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 is-glob@4.0.3: dependencies: @@ -4359,6 +4330,8 @@ snapshots: is-map@2.0.3: {} + is-negative-zero@2.0.3: {} + is-number-object@1.1.1: dependencies: call-bound: 1.0.3 @@ -4405,7 +4378,7 @@ snapshots: is-weakset@2.0.4: dependencies: call-bound: 1.0.3 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 isarray@2.0.5: {} @@ -4437,7 +4410,7 @@ snapshots: dependencies: '@apidevtools/json-schema-ref-parser': 11.7.3 '@types/json-schema': 7.0.15 - '@types/lodash': 4.17.14 + '@types/lodash': 4.17.13 is-glob: 4.0.3 js-yaml: 4.1.0 lodash: 4.17.21 @@ -4598,14 +4571,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 es-object-atoms: 1.0.0 object.groupby@1.0.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 object.values@1.2.1: dependencies: @@ -4633,12 +4606,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.2.7 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -4710,14 +4677,14 @@ snapshots: pino-std-serializers@7.0.0: {} - pino@9.6.0: + pino@9.5.0: dependencies: atomic-sleep: 1.0.0 fast-redact: 3.5.0 on-exit-leak-free: 2.1.2 pino-abstract-transport: 2.0.0 pino-std-serializers: 7.0.0 - process-warning: 4.0.1 + process-warning: 4.0.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 safe-stable-stringify: 2.5.0 @@ -4740,7 +4707,7 @@ snapshots: prettier@3.4.2: {} - process-warning@4.0.1: {} + process-warning@4.0.0: {} prompts@2.4.2: dependencies: @@ -4800,24 +4767,22 @@ snapshots: real-require@0.2.0: {} - reflect.getprototypeof@1.0.10: + reflect.getprototypeof@1.0.9: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + dunder-proto: 1.0.1 + es-abstract: 1.23.6 es-errors: 1.3.0 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.7 - get-proto: 1.0.1 + get-intrinsic: 1.2.6 + gopd: 1.2.0 which-builtin-type: 1.2.1 - regexp.prototype.flags@1.5.4: + regexp.prototype.flags@1.5.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 set-function-name: 2.0.2 require-directory@2.1.1: {} @@ -4826,9 +4791,9 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve@1.22.10: + resolve@1.22.9: dependencies: - is-core-module: 2.16.1 + is-core-module: 2.16.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -4855,37 +4820,37 @@ snapshots: globby: 10.0.1 is-plain-object: 3.0.1 - rollup-plugin-dts@6.1.1(rollup@4.30.1)(typescript@5.5.3): + rollup-plugin-dts@6.1.1(rollup@4.28.1)(typescript@5.7.2): dependencies: magic-string: 0.30.17 - rollup: 4.30.1 - typescript: 5.5.3 + rollup: 4.28.1 + typescript: 5.7.2 optionalDependencies: '@babel/code-frame': 7.26.2 - rollup@4.30.1: + rollup@4.28.1: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.30.1 - '@rollup/rollup-android-arm64': 4.30.1 - '@rollup/rollup-darwin-arm64': 4.30.1 - '@rollup/rollup-darwin-x64': 4.30.1 - '@rollup/rollup-freebsd-arm64': 4.30.1 - '@rollup/rollup-freebsd-x64': 4.30.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 - '@rollup/rollup-linux-arm-musleabihf': 4.30.1 - '@rollup/rollup-linux-arm64-gnu': 4.30.1 - '@rollup/rollup-linux-arm64-musl': 4.30.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 - '@rollup/rollup-linux-riscv64-gnu': 4.30.1 - '@rollup/rollup-linux-s390x-gnu': 4.30.1 - '@rollup/rollup-linux-x64-gnu': 4.30.1 - '@rollup/rollup-linux-x64-musl': 4.30.1 - '@rollup/rollup-win32-arm64-msvc': 4.30.1 - '@rollup/rollup-win32-ia32-msvc': 4.30.1 - '@rollup/rollup-win32-x64-msvc': 4.30.1 + '@rollup/rollup-android-arm-eabi': 4.28.1 + '@rollup/rollup-android-arm64': 4.28.1 + '@rollup/rollup-darwin-arm64': 4.28.1 + '@rollup/rollup-darwin-x64': 4.28.1 + '@rollup/rollup-freebsd-arm64': 4.28.1 + '@rollup/rollup-freebsd-x64': 4.28.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 + '@rollup/rollup-linux-arm-musleabihf': 4.28.1 + '@rollup/rollup-linux-arm64-gnu': 4.28.1 + '@rollup/rollup-linux-arm64-musl': 4.28.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 + '@rollup/rollup-linux-riscv64-gnu': 4.28.1 + '@rollup/rollup-linux-s390x-gnu': 4.28.1 + '@rollup/rollup-linux-x64-gnu': 4.28.1 + '@rollup/rollup-linux-x64-musl': 4.28.1 + '@rollup/rollup-win32-arm64-msvc': 4.28.1 + '@rollup/rollup-win32-ia32-msvc': 4.28.1 + '@rollup/rollup-win32-x64-msvc': 4.28.1 fsevents: 2.3.3 run-parallel@1.2.0: @@ -4900,17 +4865,12 @@ snapshots: dependencies: call-bind: 1.0.8 call-bound: 1.0.3 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 has-symbols: 1.1.0 isarray: 2.0.5 safe-buffer@5.2.1: {} - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - safe-regex-test@1.1.0: dependencies: call-bound: 1.0.3 @@ -4965,7 +4925,7 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -4976,12 +4936,6 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -5001,14 +4955,14 @@ snapshots: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 object-inspect: 1.13.3 side-channel-weakmap@1.0.2: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.2.6 object-inspect: 1.13.3 side-channel-map: 1.0.1 @@ -5056,7 +5010,7 @@ snapshots: call-bound: 1.0.3 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.6 es-object-atoms: 1.0.0 has-property-descriptors: 1.0.2 @@ -5139,25 +5093,25 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@2.0.0(typescript@5.5.3): + ts-api-utils@1.4.3(typescript@5.7.2): dependencies: - typescript: 5.5.3 + typescript: 5.7.2 - ts-node@10.9.2(@types/node@20.17.12)(typescript@5.5.3): + ts-node@10.9.2(@types/node@20.17.10)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.12 + '@types/node': 20.17.10 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.3 + typescript: 5.7.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -5203,7 +5157,7 @@ snapshots: gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 + reflect.getprototypeof: 1.0.9 typed-array-length@1.0.7: dependencies: @@ -5212,19 +5166,19 @@ snapshots: gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.0.0 - reflect.getprototypeof: 1.0.10 + reflect.getprototypeof: 1.0.9 - typescript-eslint@8.19.1(eslint@9.17.0)(typescript@5.5.3): + typescript-eslint@8.18.1(eslint@9.17.0)(typescript@5.7.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0)(typescript@5.5.3))(eslint@9.17.0)(typescript@5.5.3) - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0)(typescript@5.5.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0)(typescript@5.5.3) + '@typescript-eslint/eslint-plugin': 8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0)(typescript@5.7.2))(eslint@9.17.0)(typescript@5.7.2) + '@typescript-eslint/parser': 8.18.1(eslint@9.17.0)(typescript@5.7.2) + '@typescript-eslint/utils': 8.18.1(eslint@9.17.0)(typescript@5.7.2) eslint: 9.17.0 - typescript: 5.5.3 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - typescript@5.5.3: {} + typescript@5.7.2: {} unbox-primitive@1.1.0: dependencies: @@ -5241,9 +5195,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.1.2(browserslist@4.24.4): + update-browserslist-db@1.1.1(browserslist@4.24.3): dependencies: - browserslist: 4.24.4 + browserslist: 4.24.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -5263,21 +5217,21 @@ snapshots: vary@1.1.2: {} - vite-plugin-static-copy@1.0.6(vite@5.4.11(@types/node@20.17.12)): + vite-plugin-static-copy@1.0.6(vite@5.4.11(@types/node@20.17.10)): dependencies: chokidar: 3.6.0 - fast-glob: 3.3.3 + fast-glob: 3.3.2 fs-extra: 11.2.0 picocolors: 1.1.1 - vite: 5.4.11(@types/node@20.17.12) + vite: 5.4.11(@types/node@20.17.10) - vite@5.4.11(@types/node@20.17.12): + vite@5.4.11(@types/node@20.17.10): dependencies: esbuild: 0.21.5 postcss: 8.4.49 - rollup: 4.30.1 + rollup: 4.28.1 optionalDependencies: - '@types/node': 20.17.12 + '@types/node': 20.17.10 fsevents: 2.3.3 wasm-pack@0.13.1: @@ -5304,12 +5258,12 @@ snapshots: which-builtin-type@1.2.1: dependencies: call-bound: 1.0.3 - function.prototype.name: 1.1.8 + function.prototype.name: 1.1.7 has-tostringtag: 1.0.2 - is-async-function: 2.1.0 + is-async-function: 2.0.0 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.0 + is-generator-function: 1.0.10 is-regex: 1.2.1 is-weakref: 1.1.0 isarray: 2.0.5 @@ -5379,9 +5333,9 @@ snapshots: yocto-queue@0.1.0: {} - zustand@4.5.5(@types/react@18.3.18)(react@18.3.1): + zustand@4.5.5(@types/react@18.3.17)(react@18.3.1): dependencies: use-sync-external-store: 1.2.2(react@18.3.1) optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.17 react: 18.3.1