Build Loom

KINDAreplaces $18/mosaves $216/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A recorder that captures screen and camera together, encodes locally, uploads to storage you own, generates a transcript, and serves a share page with a working link, viewing permissions and an expiry, so the whole loop from recording to a link takes under a minute.

estimated effort weekendthe files for this build are in the project pack

CaptureThe browser's display and camera capture APIsEncodingMediaRecorder in the browser, with ffmpeg server side for the final fileStorageS3-compatible object storage behind a delivery networkPagesA small server for upload, share pages and permissionsTranscriptwhisper.cpp server side, after upload

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server, the upload endpoint and the processing pipeline.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why Remuxing for seekability and poster generation both go through it.

    Get it brew install ffmpeg, your package manager, or a static build. open ↗

    Verify ffmpeg -version prints a banner

  3. have ready5 to 10 USD a month plus a domain

    Why Display capture and camera capture both require a secure context, so plain HTTP cannot record at all.

    Get it A small server with a domain and automatic certificates, or a tunnel for local development.

    Verify The recorder page can request display capture

  4. API keystorage is cheap; egress varies by an order of magnitude between providers

    Why Recordings are large and viewers are elsewhere. Egress from a bucket without a network in front is usually the expensive path.

    Get it Create a private bucket and credentials, and put a delivery network in front. Compare egress pricing, since it dominates the cost here.

    Verify An object uploads, is refused directly, and serves through the network with a signature

  5. installfree, but it uses server time per recording

    Why Phase 4 only. Sharing works without it and the transcript arrives afterwards.

    Get it Build whisper.cpp and download a model sized for your server. open ↗

    Verify It transcribes a short file

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- recordings(id, owner_id, title, duration_ms, width, height, state, created_at, expires_at)
- assets(recording_id, kind, key, bytes) · kind is video, poster, transcript or captions
- shares(id, recording_id, token, visibility, password_hash, expires_at, created_at) · visibility is link, password or account
- views(recording_id, share_id, at, watched_ms, viewer_hash)
- comments(id, recording_id, at_ms, author, body, created_at) · anchored to a timestamp in the video
- The share token is separate from the recording identifier, so a link can be revoked without destroying the recording.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
PUBLIC_URLrequiredhttps://record.example.comUsed in share links. Must be HTTPS or capture will not work at all.
BUCKET_NAMErequiredrecordingsPrivate bucket holding video, posters and transcripts.
BUCKET_SECRET_KEYsecretrequiredreplace-with-secretScoped to this bucket only.
CDN_BASE_URLrequiredhttps://cdn.example.comWhat viewers fetch from. The bucket hostname never appears in a share page.
SIGNED_URL_TTL_SECONDSrequired7200How long a signed video URL lasts. Long enough for the recording, short enough to matter.
DEFAULT_RETENTION_DAYSrequired365How long recordings are kept unless marked to keep. A warning is sent first.
CHUNK_SIZE_MBrequired5Upload chunk size. Smaller resumes more finely and costs more requests.

The build, in order

  1. Recording screen and camera together

    One recording contains the screen and a camera inset, with audio, and it plays.

    1. Display capture and camera capture as separate streams, composed onto a canvas each frame.

      Files src/capture.ts

    2. MediaRecorder over the canvas stream plus microphone audio. Choose a container the browser actually supports.

      Files src/record.ts

    3. Listen for the display track ending and finalise. This is the case that produces corrupt files.

      Files src/record.ts

    done when · tick each as it passes
    watch out
    • The browser's own stop sharing control ends the display track without telling your application through any of the obvious paths, and a recorder that does not listen for the track ending produces a truncated or corrupt file. Listen for the track's end event and finalise the recording properly.
  2. Upload that survives a bad connection

    A ten minute recording uploads reliably, and an interruption does not mean starting again.

    1. Multipart upload with recorded offsets so a reconnection continues.

      Files src/upload.ts

    2. Chunks go as they are produced, so the wait after stopping is short. This is most of the perceived speed.

      Files src/upload.ts

    3. Move metadata to the front so the file is seekable, and extract a frame for the poster.

      terminal
      ffmpeg -i in.webm -c copy -movflags +faststart out.mp4
      ffmpeg -i out.mp4 -ss 2 -vframes 1 poster.jpg
    done when · tick each as it passes
    watch out
    • Browser-recorded files often have unseekable metadata, because the duration is not known until recording ends and the container is written progressively. A viewer cannot scrub such a file. Run a server-side remux to move the metadata to the front, which is fast and makes the file seekable.
  3. The link, and who may open it

    You get a link the moment the upload finishes, and you control who can use it.

    1. Separate from the recording identifier, so revocation does not destroy anything.

      Files src/shares.ts

    2. The share page issues short-lived signed URLs after checking the visibility rule. Objects stay private.

      Files src/access.ts

    3. Request the object directly, use a revoked token, and open an expired share. All three must fail.

    done when · tick each as it passes
    watch out
    • An unguessable link is obscurity, not access control, and for anything sensitive the video object itself must require a signature. Otherwise the share page's permissions are decoration and the object URL circulates instead.
  4. Transcript and captions

    Every recording is searchable and watchable without sound.

    1. After upload, in the background. The share link must not wait for it.

      Files src/transcribe.ts

    2. A caption track plus clickable lines that seek. The transcript is also how people skim a recording.

      Files src/player.ts

    3. Automatic is a state, reviewed is another. The distinction matters for accessibility.

      Files src/transcript.ts

    done when · tick each as it passes
    watch out
    • An automatic transcript presented as captions without review produces confidently wrong text for the viewers who most depend on it. Label it as automatic, make correcting it easy, and treat a reviewed transcript as a different state from a generated one.
  5. Comments and knowing it was watched

    Someone can reply at a moment in the video, and you can tell whether it was watched.

    1. Stored with a millisecond offset, rendered on the timeline, seeking on click.

      Files src/comments.ts

    2. Watch time and a count. Not a per-viewer timeline of exactly what they rewatched.

      Files src/views.ts

    3. One line telling the viewer what the sender can see. They cannot read your policy and should not have to.

      Files templates/share.html

    done when · tick each as it passes
    watch out
    • Detailed per-viewer watch tracking on a link you sent to a colleague is more surveillance than most people expect from a video message. Record coarse aggregates and be explicit in the interface about what the sender can see, because the recipient cannot see your privacy policy.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more screen recording.