Minimal reproduction demonstrating that MediaStreamVideoTrackSource produces a variable frame rate (VFR) MP4 with very few frames when recording a static screen.
When recording a static screen (e.g., a presentation slide) with getDisplayMedia(), Chrome drastically reduces frame delivery to save power. MediaStreamVideoTrackSource only encodes the frames the browser actually delivers, resulting in an MP4 with huge timestamp gaps — for example, ~102 frames spread across a 279-second recording.
This causes problems for downstream processing (e.g., AWS MediaConvert, ffmpeg) which may misinterpret the duration or produce artifacts.
A 60-second recording should produce an MP4 with roughly 1800 frames (60s x 30fps), or at minimum a consistent frame rate even when the source content is static.
A 60-second recording of a static slide produces an MP4 with far fewer frames than expected (sometimes as low as 2-3 fps effective rate), with variable frame timing.
- Open the demo in Chrome (HTTPS required for
getDisplayMedia) - Click Start Recording
- Share a screen showing a static slide or document (no animations)
- Wait 30-60 seconds without moving the mouse or changing content
- Click Stop Recording
- Download the MP4
# Check frame count and frame rate
ffprobe -v error -select_streams v:0 \
-show_entries stream=nb_frames,duration,r_frame_rate,avg_frame_rate \
-of default=noprint_wrappers=1 recording.mp4
# Count actual frames
ffprobe -v error -select_streams v:0 \
-count_frames -show_entries stream=nb_read_frames \
-of default=noprint_wrappers=1 recording.mp4
# Show per-frame timestamps to see gaps
ffprobe -v error -select_streams v:0 \
-show_entries frame=pts_time,duration_time \
-of csv=p=0 recording.mp4 | head -20- Browser: Chrome 130+ (tested on macOS)
- mediabunny: 1.34.2
- Encoding:
MediaStreamVideoTrackSourcewith AVC codec, 2.5 Mbps, keyframe interval 2s - Output: Fragmented MP4 via
StreamTarget
https://trms.github.io/mediabunny-screen-capture/
- Node.js 20+
- npm
git clone https://github.com/trms/mediabunny-screen-capture.git
cd mediabunny-screen-capture
npm installnpm run devThis starts a Vite dev server with HTTPS (required for getDisplayMedia()).
Open https://localhost:5173/mediabunny-screen-capture/ in Chrome.
Your browser will warn about the self-signed certificate — click "Advanced" → "Proceed" to continue.
npm run build
npm run preview # Preview the production build locallyThe encoding pipeline in this repro matches our production code exactly:
- Screen constraints:
getDisplayMedia({ video: { aspectRatio: { ideal: 16/9 }, width: { ideal: 1920 }, height: { ideal: 1080 } } }) - Output:
Mp4OutputFormat({ fastStart: 'fragmented' })withStreamTarget - Video:
MediaStreamVideoTrackSourcewith{ codec: 'avc', bitrate: 2_500_000, keyFrameInterval: 2 } - Audio:
MediaStreamAudioTrackSourcewith{ codec: 'aac', bitrate: 128_000 } - Frame rate:
output.addVideoTrack(source, { frameRate: 30 })