arm
  • Home
  • Work
  • About
  • Writing
  • Resume
  • Contact
Open to work
❡Case study · Deep dive · 9 min read

Amar — a cinematic HLS pipeline for a working video editor.

FFmpeg → BullMQ → Cloudflare R2 → adaptive streaming, and the three things I would change now.

activeclientclient workvideoshipped
A portfolio that streams like premium
Amarjeet is a professional video editor. He needed a portfolio that could stream cinematic 4K footage smoothly, handle multiple players on a page at once, and feel as premium as his work. So I built a full transcoding-to-streaming pipeline behind a deliberately minimal front end.
3
Quality tiers
4K
Source footage
1
Real client
0
Buffering spinners
Multi-player grid

§01The problem

Dropping raw 4K MP4s onto a page is a great way to make a portfolio feel broken — multi-second loads, no quality adaptation, and a browser that chokes when several videos share a screen. A video editor's site failing to play video is the worst possible first impression.

The real requirement was adaptive streaming: serve the right quality for each viewer's connection, start fast, and let several players coexist without fighting over bandwidth or memory.

§02The pipeline

Upload bypasses the server, a queue does the heavy lifting, and the player just reads a playlist. Each stage is independent so a failure in one does not corrupt the others.

Upload
Signed URL → R2Direct from clientServer bypassed
Queue
BullMQHono backendRedis
Transcode
FFmpeg360p / 720p / 1080pHLS segments + master playlist
Storage
Cloudflare R2Structured paths
Playback
HLS adaptiveReact context playersQuality auto-switch

§03Three decisions worth defending

Decision 01

Direct-to-R2 uploads via signed URLs

Large source files never touch the app server — the client uploads straight to R2 with a pre-signed URL, and only the completion event hits the backend. The server stays cheap and never streams gigabytes.

Decision 02

A queue, not a request, for transcoding

Transcoding is minutes of work; an HTTP request is the wrong place for it. BullMQ decouples “upload finished” from “encode done”, with retries and visibility into the job state.

Decision 03

Context-driven multi-instance players

Several HLS players on one page need to share state — only one plays at a time, others pause. A small React context coordinates them instead of each player reinventing its own controls.

§04Three things I would change

Change 01

Move transcoding off the main process

I ran FFmpeg inside the main Hono process — fine at low traffic, but a heavy encode blocks the event loop. Next time: a dedicated worker service on a separate machine.

Change 02

Stream job progress over SSE

The upload UI shows a spinner until transcoding finishes. A progress bar streamed via server-sent events would be far better UX for long encodes — the data is already in BullMQ.

Change 03

Consider Cloudflare Stream

I built the pipeline from scratch to learn it end to end. For a production client deadline, Cloudflare Stream handles transcode + storage + CDN in one service — I would use it and spend the saved time on the UI.

“Building the pipeline took a week. Making it reliable took three more — and that ratio is the whole job.”— Parm, on AmarV4
Talk about this project →
Open to work

Have something in mind? Let's make it.

iamparmjeetmishra@gmail.com→
GithubMailLinkedInX/Twitter
arm© 2026 — Made with ♡ by Parm
HomeWorkAboutWritingResumeContact