Skip to content

ebadfd/sr-web

Repository files navigation

Screen Recorder

A free, browser-only screen recorder. Records, trims, and downloads — never uploads. Single static page, no backend.

The whole pipeline runs in your tab via the Screen Capture API, MediaRecorder, and Web Audio. Recordings live in browser memory until you download them; close the tab and they're gone. There are no analytics, no accounts, and no third-party trackers.

Features

  • Record full screen, a window, or a browser tab
  • Live preview while recording
  • Real audio waveform on the trim track (decoded from the recording itself, not faked)
  • In-browser trim with draggable region + handles
  • Download as .webm
  • Web Share API integration where supported (mobile, macOS Safari) — hidden where it isn't
  • Keyboard shortcuts: R to start, Esc to stop / cancel trim
  • Dismissible sponsor slot, persisted in localStorage
  • Static export — drop the dist/ folder on any static host

Browser support

Browser Video Audio Notes
Chrome / Edge Full support, including system + tab audio
Firefox partial Often video-only on Linux/macOS — Firefox doesn't expose system audio
Safari (desktop) getDisplayMedia audio not supported; video records fine
Mobile browsers The Screen Capture API isn't exposed on mobile yet

The MIME selection adapts: when a stream has no audio track, the recorder requests video/webm;codecs=vp8 (and friends) without the ,opus suffix that Firefox refuses to honor for audioless streams.

Run locally

Requires Bun.

bun install
bun run dev          # dev server at http://localhost:3000
bun run build        # static export to ./dist
bun run lint         # eslint

The build produces a static site in dist/ that you can drop on any static host: GitHub Pages, Cloudflare Pages, Netlify, an S3 bucket, etc. There is no backend.

Project layout

src/app/
  layout.tsx        # metadata + JetBrains Mono font + JSON-LD
  page.tsx          # entire app: state machine, MediaRecorder, trim pipeline, UI
  globals.css       # Base16 Moss palette + component styles
public/
  logo-main.svg     # also used as favicon

It's a single React client component on purpose — the whole app is one page.

Notes for the curious

A few things that took multiple iterations to get right:

  • Duration tracking. video.duration on a MediaRecorder-produced WebM blob is Infinity on Firefox (and sometimes on Chrome) because the muxer doesn't write Cues / Segment Info. We bypass the issue entirely by capturing Date.now() deltas around MediaRecorder.start() and stop(), and use that as the canonical duration everywhere.
  • MIME selection is dynamic. Firefox's MediaRecorder.isTypeSupported("video/webm;codecs=vp8,opus") returns true as a static capability check, but the recorder fails silently on start() if the stream has no audio track. We probe stream.getAudioTracks().length after getDisplayMedia and pick a codec spec that matches.
  • MediaRecorder.onerror matters. Without it, Firefox failures are invisible — ondataavailable and onstop simply never fire. We surface errors as a typed error view with codes (ERR_PERMISSION_DENIED, ERR_API_UNSUPPORTED, ERR_RECORDING_FAILED).
  • Trim is real-time re-encode. We attach the recorded blob to a hidden <video>, route it through a 2D canvas (captureStream) plus an AudioContext.createMediaElementSource graph, and feed the combined output into a second MediaRecorder for the duration of the kept range. Slow, but works without WASM and stays inside the browser.
  • Waveform is decoded from the actual audio. AudioContext.decodeAudioData on the recorded blob, peak-bucketed into 80 bars. When there's no audio track (Firefox/Linux), we render no bars — better than a fake signal.
  • Web Share is feature-detected. navigator.canShare({ files: [...] }) — the Share button only renders when the browser actually supports sharing files. Most desktop browsers fail this check.

Credits

  • Design exported from claude.ai/design — see the chat transcripts in the design bundle for the iteration history. Base16 Moss palette, JetBrains Mono.
  • Built with Next.js 16 (static export) + React 19.

License

MIT © badfd explorations

About

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors