import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; import { createServer, type Server } from "node:http"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { AccessToken, TrackSource } from "livekit-server-sdk"; import { CdpConnection, cleanupRun, createPage, reservePort, waitForVersion, type PageHandle, } from "./native-one-viewer-gate"; const WIDTH = 1_920; const HEIGHT = 1_080; const FRAME_RATE = 30; const MAX_BITRATE = 5_000_000; const SETTLE_MS = 5_000; const SAMPLE_MS = 10_000; interface RuntimeResult { result: { value?: T }; exceptionDetails?: { text?: string; exception?: { description?: string }; }; } interface ProcessInfo { type: string; id: number; cpuTime: number; } interface ProcessSnapshot { processInfo: ProcessInfo[]; } async function evaluate( cdp: CdpConnection, page: PageHandle, expression: string, timeoutMs: number, ): Promise { const value = await cdp.call>( "Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true, userGesture: true, }, page.sessionId, Date.now() + timeoutMs, ); if (value.exceptionDetails) { throw new Error( value.exceptionDetails.exception?.description ?? value.exceptionDetails.text ?? "Browser evaluation failed", ); } return value.result.value as T; } function sourceHtml(): string { return `Codec Preflight Source `; } async function startServer(port: number): Promise { const livekitBundle = await readFile( resolve("node_modules/livekit-client/dist/livekit-client.umd.js"), ); const server = createServer((request, response) => { const path = new URL(request.url ?? "/", `http://127.0.0.1:${port}`).pathname; if (path === "/livekit.js") { response.setHeader("content-type", "text/javascript; charset=utf-8"); response.end(livekitBundle); return; } response.setHeader("content-type", "text/html; charset=utf-8"); response.end(path === "/source" ? sourceHtml() : "Codec Preflight"); }); await new Promise((resolveListen, rejectListen) => { server.once("error", rejectListen); server.listen(port, "127.0.0.1", resolveListen); }); return server; } function browserProbe(): string { return `(() => { const WIDTH=${WIDTH},HEIGHT=${HEIGHT},FRAME_RATE=${FRAME_RATE},MAX_BITRATE=${MAX_BITRATE}; const SETTLE_MS=${SETTLE_MS},SAMPLE_MS=${SAMPLE_MS};let stream=null,track=null; const delay=(ms)=>new Promise((resolve)=>setTimeout(resolve,ms)); async function waitFor(predicate,timeout,label){const deadline=performance.now()+timeout; while(performance.now()item.mimeType.toLowerCase()===mime); if(!primary.length)throw new Error(mime+' unavailable'); const repair=new Set(['video/rtx','video/red','video/ulpfec','video/flexfec-03']); return [...primary,...all.filter((item)=>repair.has(item.mimeType.toLowerCase()))]} async function gathered(pc){if(pc.iceGatheringState==='complete')return; await new Promise((resolve)=>{const changed=()=>{if(pc.iceGatheringState!=='complete')return; pc.removeEventListener('icegatheringstatechange',changed);resolve()};pc.addEventListener('icegatheringstatechange',changed)})} async function negotiate(a,b){await a.setLocalDescription(await a.createOffer());await gathered(a); await b.setRemoteDescription(a.localDescription);await b.setLocalDescription(await b.createAnswer()); await gathered(b);await a.setRemoteDescription(b.localDescription)} function statsValue(item){if(!item)return null;const keys=['id','timestamp','bytesSent','bytesReceived', 'framesEncoded','framesDecoded','framesDropped','framesPerSecond','frameWidth','frameHeight','totalEncodeTime', 'totalDecodeTime','qualityLimitationReason','qualityLimitationDurations','encoderImplementation', 'decoderImplementation','powerEfficientEncoder','powerEfficientDecoder','frames'];const value={}; for(const key of keys)value[key]=item[key]??null;return value} async function snapshot(sender,receiver,rendered){const [sendReport,receiveReport]=await Promise.all([sender.getStats(),receiver.getStats()]); let outbound=null,inbound=null;sendReport.forEach((item)=>{if(item.type==='outbound-rtp'&&item.kind==='video'&&!item.isRemote)outbound=item}); receiveReport.forEach((item)=>{if(item.type==='inbound-rtp'&&item.kind==='video'&&!item.isRemote)inbound=item}); const sendCodec=outbound?.codecId?sendReport.get(outbound.codecId):null; const receiveCodec=inbound?.codecId?receiveReport.get(inbound.codecId):null; const mediaSource=outbound?.mediaSourceId?sendReport.get(outbound.mediaSourceId):null; const codec=(item)=>item?{mimeType:item.mimeType||null,sdpFmtpLine:item.sdpFmtpLine||null}:null; return {rendered:rendered(),sender:statsValue(outbound),receiver:statsValue(inbound), mediaSource:statsValue(mediaSource),senderCodec:codec(sendCodec),receiverCodec:codec(receiveCodec)}} function delta(after,before,key){const a=after?.[key],b=before?.[key];return Number.isFinite(a)&&Number.isFinite(b)&&a>=b?a-b:null} function summarize(before,after,sampleMs){const encoded=delta(after.sender,before.sender,'framesEncoded'); const decoded=delta(after.receiver,before.receiver,'framesDecoded');const bytes=delta(after.sender,before.sender,'bytesSent'); const sourceFrames=delta(after.mediaSource,before.mediaSource,'frames'); const encodeTime=delta(after.sender,before.sender,'totalEncodeTime');const decodeTime=delta(after.receiver,before.receiver,'totalDecodeTime'); return {senderCodec:after.senderCodec,receiverCodec:after.receiverCodec,sender:after.sender,receiver:after.receiver, measured:{sampleMs,encodedFps:encoded===null?null:encoded*1000/sampleMs, decodedFps:decoded===null?null:decoded*1000/sampleMs,bitrateKbps:bytes===null?null:bytes*8/sampleMs, encodeMsPerFrame:encodeTime!==null&&encoded>0?encodeTime*1000/encoded:null, decodeMsPerFrame:decodeTime!==null&&decoded>0?decodeTime*1000/decoded:null, sourceFps:sourceFrames===null?after.mediaSource?.framesPerSecond:sourceFrames*1000/sampleMs, encodedSourceRatio:sourceFrames>0&&encoded!==null?encoded/sourceFrames:null, renderedFrames:delta(after,before,'rendered')}}} async function setup(){stream=await navigator.mediaDevices.getDisplayMedia({video:{ width:{ideal:WIDTH,max:WIDTH},height:{ideal:HEIGHT,max:HEIGHT},frameRate:{ideal:FRAME_RATE,max:FRAME_RATE}},audio:false}); track=stream.getVideoTracks()[0];if(!track)throw new Error('No display video track');track.contentHint='motion'; return {settings:track.getSettings(),constraints:track.getConstraints(),contentHint:track.contentHint,label:track.label}} function capabilities(){const simple=(codec)=>({mimeType:codec.mimeType,sdpFmtpLine:codec.sdpFmtpLine||null}); return {sender:(RTCRtpSender.getCapabilities('video')?.codecs||[]).map(simple), receiver:(RTCRtpReceiver.getCapabilities('video')?.codecs||[]).map(simple)}} async function edge(codec,sourceTrack){ const sendPc=new RTCPeerConnection({iceServers:[]}),receivePc=new RTCPeerConnection({iceServers:[]}); const transceiver=sendPc.addTransceiver(sourceTrack,{direction:'sendonly'});transceiver.setCodecPreferences(preferences(codec)); let receiver=null,remoteTrack=null;receivePc.addEventListener('track',(event)=>{receiver=event.receiver;remoteTrack=event.track}); await negotiate(sendPc,receivePc);await waitFor(()=>sendPc.connectionState==='connected'&&receivePc.connectionState==='connected',10000,'connection'); await waitFor(()=>receiver&&remoteTrack,5000,'remote track');const video=document.createElement('video');let rendered=0; video.autoplay=true;video.muted=true;video.playsInline=true;document.body.append(video); video.srcObject=new MediaStream([remoteTrack]);if(video.requestVideoFrameCallback){const frame=()=>{rendered+=1;video.requestVideoFrameCallback(frame)};video.requestVideoFrameCallback(frame)} await video.play();const parameters=transceiver.sender.getParameters();if(!parameters.encodings.length)parameters.encodings=[{}]; parameters.encodings[0].maxBitrate=MAX_BITRATE;parameters.encodings[0].maxFramerate=FRAME_RATE; parameters.encodings[0].scaleResolutionDownBy=1;parameters.degradationPreference='balanced';await transceiver.sender.setParameters(parameters); await waitFor(async()=>{const report=await receiver.getStats();let frames=0;report.forEach((item)=>{if(item.type==='inbound-rtp'&&item.kind==='video')frames=Math.max(frames,item.framesDecoded||0)});return frames>0&&rendered>0},10000,'decoded frame'); return {sendPc,receivePc,sender:transceiver.sender,receiver,remoteTrack,rendered:()=>rendered}} async function run(codec,count,topology){if(!track)throw new Error('Capture not initialized');const edges=[]; try{edges.push(await edge(codec,track)); if(topology==='relay')edges.push(await edge(codec,edges[0].remoteTrack)); else for(let index=1;indexsnapshot(item.sender,item.receiver,item.rendered))),started=performance.now(); await delay(SAMPLE_MS);const sampleMs=performance.now()-started; const after=await Promise.all(edges.map((item)=>snapshot(item.sender,item.receiver,item.rendered))); return edges.map((_,index)=>summarize(before[index],after[index],sampleMs))} finally{for(const item of edges){item.sendPc.close();item.receivePc.close()}await delay(300)}} async function runLivekit(codec,url,publisherToken,subscriberToken){if(!track)throw new Error('Capture not initialized'); const sdk=globalThis.LivekitClient,publisher=new sdk.Room({dynacast:true}),subscriber=new sdk.Room();let remoteTrack=null,rendered=0; subscriber.on(sdk.RoomEvent.TrackSubscribed,(nextTrack)=>{if(nextTrack.kind==='video')remoteTrack=nextTrack}); try{await publisher.connect(url,publisherToken,{autoSubscribe:false,rtcConfig:{iceServers:[]}}); await subscriber.connect(url,subscriberToken,{rtcConfig:{iceServers:[]}}); const publication=await publisher.localParticipant.publishTrack(track,{source:sdk.Track.Source.ScreenShare, videoCodec:codec,backupCodec:false,screenShareEncoding:{maxBitrate:MAX_BITRATE,maxFramerate:FRAME_RATE}, degradationPreference:'balanced'}); await waitFor(()=>publication.videoTrack?.sender&&remoteTrack,15000,'LiveKit publication'); const video=document.createElement('video');video.autoplay=true;video.muted=true;video.playsInline=true;document.body.append(video); video.srcObject=new MediaStream([remoteTrack.mediaStreamTrack]);if(video.requestVideoFrameCallback){const frame=()=>{rendered+=1;video.requestVideoFrameCallback(frame)};video.requestVideoFrameCallback(frame)} await video.play();const receiver={getStats:async()=>{const report=await remoteTrack.getRTCStatsReport();if(!report)throw new Error('LiveKit receiver stats unavailable');return report}}; await waitFor(async()=>{const report=await receiver.getStats();let frames=0;report?.forEach((item)=>{if(item.type==='inbound-rtp'&&item.kind==='video')frames=Math.max(frames,item.framesDecoded||0)});return frames>0&&rendered>0},15000,'LiveKit decoded frame'); await delay(SETTLE_MS);const sender=publication.videoTrack.sender; const before=await snapshot(sender,receiver,()=>rendered),started=performance.now();await delay(SAMPLE_MS); const sampleMs=performance.now()-started,after=await snapshot(sender,receiver,()=>rendered); return {...summarize(before,after,sampleMs),senderParameters:sender.getParameters()}} finally{await Promise.allSettled([publisher.disconnect(false),subscriber.disconnect(false)]);await delay(300)}} function stop(){stream?.getTracks().forEach((item)=>item.stop())} Object.defineProperty(globalThis,'__CODEC_PREFLIGHT__',{value:{setup,capabilities,run,runLivekit,stop}}); })()`; } async function processSnapshot(cdp: CdpConnection): Promise { return cdp.call( "SystemInfo.getProcessInfo", {}, undefined, Date.now() + 5_000, ); } function processCpu( before: ProcessSnapshot, after: ProcessSnapshot, wallMs: number, ): Record { const baseline = new Map( before.processInfo.map((item) => [`${item.type}:${item.id}`, item.cpuTime]), ); let cpuSeconds = 0; let stableProcesses = 0; for (const item of after.processInfo) { const previous = baseline.get(`${item.type}:${item.id}`); if (previous === undefined || item.cpuTime < previous) continue; cpuSeconds += item.cpuTime - previous; stableProcesses += 1; } const wallSeconds = wallMs / 1_000; return { wallSeconds, cpuSeconds, logicalCoreEquivalent: cpuSeconds / wallSeconds, stableProcesses, processIdentityChanged: stableProcesses !== before.processInfo.length || stableProcesses !== after.processInfo.length, }; } async function main(): Promise { const chromePath = process.env.CHROME_PATH?.trim(); if (!chromePath) throw new Error("CHROME_PATH must point to Chrome"); const codec = process.env.CODEC_PREFLIGHT_CODEC?.trim().toLowerCase(); if (codec !== "vp8" && codec !== "h264") { throw new Error("CODEC_PREFLIGHT_CODEC must be vp8 or h264"); } const edges = Number(process.env.CODEC_PREFLIGHT_EDGES ?? "1"); if (edges !== 1 && edges !== 2) { throw new Error("CODEC_PREFLIGHT_EDGES must be 1 or 2"); } const topology = process.env.CODEC_PREFLIGHT_TOPOLOGY?.trim() || "direct"; if (topology !== "direct" && topology !== "relay" && topology !== "livekit") { throw new Error("CODEC_PREFLIGHT_TOPOLOGY must be direct, relay, or livekit"); } const [debugPort, pagePort] = await Promise.all([reservePort(), reservePort()]); const profile = await mkdtemp(join(tmpdir(), "screener-codec-preflight-")); let chrome: ChildProcessWithoutNullStreams | null = null; let cdp: CdpConnection | null = null; let server: Server | null = null; try { server = await startServer(pagePort); chrome = spawn(chromePath, [ `--remote-debugging-port=${debugPort}`, `--user-data-dir=${profile}`, "--no-first-run", "--no-default-browser-check", "--disable-extensions", "--disable-background-timer-throttling", "--disable-backgrounding-occluded-windows", "--disable-renderer-backgrounding", "--autoplay-policy=no-user-gesture-required", "--auto-select-tab-capture-source-by-title=Codec Preflight Source", "--window-size=1920,1080", "about:blank", ], { stdio: "pipe", windowsHide: true }); chrome.stdout.resume(); chrome.stderr.resume(); const version = await waitForVersion(debugPort, chrome); cdp = await CdpConnection.connect(version.webSocketDebuggerUrl, Date.now() + 5_000); const gpuInfo = await cdp.call("SystemInfo.getInfo", {}, undefined, Date.now() + 5_000); await createPage(cdp, `http://127.0.0.1:${pagePort}/source`, undefined, true); const page = await createPage(cdp, `http://127.0.0.1:${pagePort}/test`, undefined, true); await evaluate(cdp, page, browserProbe(), 5_000); const capabilities = await evaluate( cdp, page, "globalThis.__CODEC_PREFLIGHT__.capabilities()", 5_000, ); const capture = await evaluate( cdp, page, "globalThis.__CODEC_PREFLIGHT__.setup()", 15_000, ); const before = await processSnapshot(cdp); const started = performance.now(); let media: unknown; if (topology === "livekit") { const livekitUrl = process.env.CODEC_PREFLIGHT_LIVEKIT_URL?.trim(); const apiKey = process.env.CODEC_PREFLIGHT_LIVEKIT_KEY?.trim(); const apiSecret = process.env.CODEC_PREFLIGHT_LIVEKIT_SECRET?.trim(); if (!livekitUrl || !apiKey || !apiSecret) { throw new Error("LiveKit preflight credentials are missing"); } const room = `codec-preflight-${Date.now()}`; const token = async (identity: string, publish: boolean) => { const access = new AccessToken(apiKey, apiSecret, { identity, ttl: 60 }); access.addGrant({ roomJoin: true, room, canPublish: publish, canPublishSources: publish ? [TrackSource.SCREEN_SHARE] : undefined, canSubscribe: !publish, canPublishData: false, }); return access.toJwt(); }; const [publisherToken, subscriberToken] = await Promise.all([ token("host", true), token("viewer", false), ]); media = await evaluate( cdp, page, `globalThis.__CODEC_PREFLIGHT__.runLivekit(${JSON.stringify(codec)}, ${JSON.stringify(livekitUrl)}, ${JSON.stringify(publisherToken)}, ${JSON.stringify(subscriberToken)})`, 60_000, ); } else { media = await evaluate( cdp, page, `globalThis.__CODEC_PREFLIGHT__.run(${JSON.stringify(codec)}, ${edges}, ${JSON.stringify(topology)})`, 45_000, ); } const wallMs = performance.now() - started; const after = await processSnapshot(cdp); await evaluate(cdp, page, "globalThis.__CODEC_PREFLIGHT__.stop()", 5_000); const report = { schemaVersion: 1, browser: version.Browser, configuration: { width: WIDTH, height: HEIGHT, frameRate: FRAME_RATE, maxBitrate: MAX_BITRATE, contentHint: "motion", settleMs: SETTLE_MS, sampleMs: SAMPLE_MS, edges, topology, }, gpuInfo, capabilities, capture, codec, media, cpu: processCpu(before, after, wallMs), }; const outputDirectory = resolve("benchmark-results"); await mkdir(outputDirectory, { recursive: true }); const output = join( outputDirectory, `browser-codec-preflight-${codec}-${topology}-${edges}.json`, ); await writeFile(output, `${JSON.stringify(report, null, 2)}\n`, "utf8"); console.log(output); } finally { await cleanupRun({ cdp, native: null, chrome, server: null, profile, ports: [debugPort], }); if (server) { await new Promise((resolveClose) => server!.close(() => resolveClose())); } } } await main();