UNPKG

mfx

Version:

In-browser video editing toolkit, with effects accelerated by WebGL

142 lines (124 loc) 3.6 kB
import type { MFXEncodedChunk } from "./types"; import { RingBuffer } from "ring-buffer-ts"; import { MFXTransformStream } from "./stream"; import { ExtendedVideoFrame } from "./frame"; /** * @group Debug */ export class ConsoleWritableStream<T = any> { writable: WritableStream<T>; constructor(id: string) { let size = 0; let length = 0; this.writable = new WritableStream({ write(chunk) { console.log("id", chunk); length++; size += (chunk as ArrayBuffer)?.byteLength || (chunk as any).size || (chunk as string)?.length; }, close() { console.log( `Stream ${id} closed: Read ${length} chunks at total of ${size} bytes`, ); }, abort(err) { console.error("Stream ${id} aborted:", err); }, }); } } // Expensive function, sample frames before piping for digest /** * @group Debug */ export class Digest extends MFXTransformStream< ExtendedVideoFrame | MFXEncodedChunk, ExtendedVideoFrame | MFXEncodedChunk > { get identifier() { return "Digest"; } globalChecksum = ""; constructor( cb: (sum: string) => void, final: (sum: string) => void = () => { /** noop */ }, ) { const calculateChecksum = async (buffer: BufferSource) => { const hashBuffer = await crypto.subtle.digest("SHA-256", buffer); return Array.from(new Uint8Array(hashBuffer)) .map((b) => b.toString(16).padStart(2, "0")) .join(""); }; let value = ""; super({ transform: async (chunk, controller) => { let buffer: Uint8Array = new Uint8Array(); if (Array.isArray(chunk) && chunk[0] instanceof Uint8Array) { buffer = chunk[0]; } else if ( chunk instanceof ExtendedVideoFrame || chunk instanceof VideoFrame ) { buffer = new Uint8Array(chunk.allocationSize()); await chunk.copyTo(buffer); } else if (chunk instanceof Blob) { buffer = new Uint8Array(await chunk.arrayBuffer()); } else if (chunk.video) { buffer = new Uint8Array(chunk.video.chunk.byteLength); await chunk.video.chunk.copyTo(buffer); } else if (chunk.audio) { buffer = new Uint8Array(chunk.audio.chunk.byteLength); await chunk.audio.chunk.copyTo(buffer); } const checksum = await calculateChecksum(buffer); const combinedSum = new TextEncoder().encode(`${value}_${checksum}`); value = await calculateChecksum(combinedSum); if (typeof cb === "function") { cb(value); } this.globalChecksum = value; controller.enqueue(chunk); }, flush: () => { final(value); }, }); } } /** * @group Debug */ export class FPSDebugger extends MFXTransformStream< ExtendedVideoFrame, ExtendedVideoFrame > { get identifier() { return "FPSDebugger"; } ringBuffer: RingBuffer<number>; lookupWindow: number; lastRecordedTime = performance.now(); constructor(lookupWindow = 30) { super({ transform: async (frame, controller) => { this.ringBuffer.add(performance.now() - this.lastRecordedTime); this.lastRecordedTime = performance.now(); controller.enqueue(frame); }, }); this.lookupWindow = lookupWindow; this.ringBuffer = new RingBuffer<number>(lookupWindow); } getFPS() { return ( 1000 / (this.ringBuffer.toArray().reduce((a, b) => a + b, 0) / this.ringBuffer.getBufferLength()) ); } }