Live streaming
Publish an embedded CEF browser to RTMP or RTMPS with SuiteCut 2.0. Native broadcasts uselaunchBrowser and createBroadcast from the root package. Existing Playwright streams use capture.stream through suitecut/playwright.
Publish a page
Build the pinned CEF browser and set SUITECUT_NATIVE_EXECUTABLE as described inGetting started. Native support is verified on macOS Apple Silicon. Set SUITECUT_STREAM_URL to your ingest URL, including its stream key.
import { launchBrowser, createBroadcast } from 'suitecut'
const url = process.env.SUITECUT_STREAM_URL
if (!url) throw new Error('Set SUITECUT_STREAM_URL')
const source = await launchBrowser({ width: 1920, height: 1080, framesPerSecond: 60 })
let broadcast
try {
await source.navigate('https://example.com')
broadcast = await createBroadcast({ source, stream: { url, audio: true } })
await broadcast.ready()
await Promise.race([
source.failure, broadcast.failure,
new Promise(resolve => setTimeout(resolve, 30_000)),
])
} finally {
await broadcast?.stop()
await source.close()
}The broadcast owns the publisher; the caller owns the source. Always stop and close both. Observe both failure promises. stream.audio: true sends CEF page audio directly. Native streaming does not mix synthesized narration or apply the final renderer's zoom transforms.
Existing Playwright streams
Run npx playwright install chromium, npx suitecut install, andnpx suitecut doctor to prepare the browser and media tools. Set SUITECUT_STREAM_URL to the complete publish URL from your streaming service, including its stream key.
import { record } from 'suitecut/playwright'
const url = process.env.SUITECUT_STREAM_URL
if (!url) throw new Error('Set SUITECUT_STREAM_URL')
const controller = new AbortController()
process.once('SIGINT', () => controller.abort())
await record('live website tour', async ({ page, suitecut }) => {
await page.goto('https://example.com')
await suitecut.hold(30_000)
}, {
signal: controller.signal,
capture: {
viewport: { width: 1280, height: 720 },
deviceScaleFactor: 2,
framesPerSecond: 30,
stream: {
url,
size: { width: 1280, height: 720 },
bitrateKbps: 4500,
audio: true,
onDiagnostic: (event) => console.log(event.event, event.bottleneck),
reconnect: { initialDelayMs: 1000, maxDelayMs: 30_000, maxAttempts: 10 },
},
},
})Publishing starts before the callback and stops when it returns or the session is aborted. The capture options also work in Playwright Test through suitecutCapture, except tab audio. Use one publisher per stream key. Disable parallel tests and retries that target the same key. RTMP is an ingest protocol; browser playback needs a media server or streaming service.
Stream options
Pass shared publisher options in stream for native broadcasts orcapture.stream for Playwright. Native browser dimensions and frame rate belong inlaunchBrowser(); the capture.* fields below are Playwright options.
url: required RTMP or RTMPS publish URL.bitrateKbps: 100 through 50,000, default 4500.audio: boolean, defaultfalse. Settruefor Chromium tab audio.adaptiveBitrate: enabled by default. Setfalsefor fixed bitrate.onDiagnostic: receives aLiveStreamDiagnosticfor congestion and bitrate changes.size: fixed output width and height, defaulting to the capture size. Both must be even, with a maximum of 7680 by 4320. Pages are letterboxed to fit.capture.framesPerSecond: 30 or 60, default 30.capture.quality: JPEG quality 1 through 100, default 90 for streaming.capture.deviceScaleFactor: 1 through 4. The browser preserves its CSS viewport and renders at the requested device-pixel density before the stream encoder downsamples with Lanczos.
The output is H.264 video with stereo AAC at 48 kHz. Keep stream keys in environment variables. SuiteCut omits publish URLs and encoder stderr from manifests and diagnostic events. Fixed-bitrate publishing passes the URL to FFmpeg's local process arguments.
Capture tab audio
Native broadcasts capture CEF page audio directly with stream.audio: true. Switch native sources with broadcast.selectSource(); queued audio from the old source is discarded. No browser extension is required.
For Playwright, audio: true captures the selected page through an isolated Chromium profile and bundled extension. Use the standalone record() API with current Playwright Chromium. Headed and headless modes work. Firefox, WebKit, custom executables, blocked service workers, and the Playwright Test fixture reject tab audio. Separate popup windows are unsupported; popups opened as tabs work.
Audio follows page selection and navigation. Switching pages discards queued sound from the old page. Pausing capture sends silence, and resuming uses current audio. The isolated browser enables autoplay. Microphone input and synthesized narration are not mixed into the stream.
Adaptive bitrate and diagnostics
SuiteCut measures socket backpressure and lowers video bitrate during sustained network congestion. It prepares a replacement H.264 encoder and switches at a keyframe while keeping the RTMP publisher and AAC encoder alive. Audio timestamps continue across the handoff. Failed replacements leave the previous encoder running. Bitrate recovers gradually after five healthy minutes, up to the configured maximum.
Temporary congestion discards stale capture data with bounded audio and video buffers. Twenty seconds without output progress triggers reconnection. SetadaptiveBitrate: false to use the fixed-bitrate FFmpeg path.reconnect: false disables reconnection, but does not disable bitrate handoffs.
onDiagnostic reports capture lag, processing and write durations, output progress, socket backlog, and the current bottleneck. bitrate-changing announces preparation,bitrate-adjusted confirms the handoff, and bitrate-change-failed reports a failed replacement. Keep handlers short. Exceptions in the handler do not stop the stream.
Reconnect and stop
Disconnected or stalled encoders retry with exponential backoff. The default delay starts at one second and reaches a maximum of 30 seconds. Set reconnect.initialDelayMs andreconnect.maxDelayMs to change those delays. Set reconnect.maxAttemptsto limit consecutive retries; its default of zero retries indefinitely. A connection lasting at least 30 seconds resets the budget. Set reconnect: false to disable retries.
For native broadcasts, await broadcast.ready(), observebroadcast.failure, and call broadcast.stop() before closing the source. For Playwright, the callback waits for the first successful publish. After that, the browser script keeps running during reconnection. Only the latest frame is retained, so missed frames are discarded. Pass an AbortSignal to stop startup waits or a running session. Exhausting the retry budget rejects record() and fails Playwright Test fixture teardown.
Playwright live effects
The stream follows suitecut.selectPage(), popup selection, and page closure. Idle pages repeat their latest frame. Capture pauses freeze the image while the broadcast clock continues. Zoom crops the encoded frames without changing website layout or mouse coordinates. Highlights, cursor movement, click pulses, typing, scrolling, and captions also appear live.
checkpoint() holds the view without saving a clip. Final-render editing, result cards, and audio mixing are offline operations. The returned manifest is a session summary with no replay timeline. Closed-page history and completed events are discarded. Narration synthesis uses temporary files that are removed when synthesis completes.
Verify locally
After building CEF and setting SUITECUT_NATIVE_EXECUTABLE, verify native capture, streaming, and recording:
pnpm test:nativeVerify the explicit Playwright backend separately:
pnpm install --frozen-lockfile
pnpm exec playwright install chromium
pnpm test:stream:audio
pnpm test:stream:bitrate
pnpm test:stream:congestionFrom the repository, this check starts a local RTMP receiver, exercises live effects and page switching, interrupts the receiver, and verifies sustained recovery. It also checks that no source artifacts remain. Receiver footage and screenshots are saved under.suitecut/live-verification/. Nothing is published externally.