This repo contains a Go video-processing service plus integration artifacts for a secure HLS playback stack built on:
- Go + FFmpeg for transcoding
- Cloudflare R2 for private storage
- Cloudflare Worker for token-protected delivery
- Next.js for auth, access checks, and playback URL issuance
Run the API:
go run ./cmd/serverProcess a source video:
curl -X POST http://localhost:8080/api/v1/videos/process \
-H "Content-Type: application/json" \
-d '{
"videoId": "lesson-123",
"sourcePath": "/absolute/path/to/source.mp4",
"cleanupSource": true
}'Successful response:
{
"videoId": "lesson-123",
"videoPath": "videos/lesson-123/master.m3u8",
"thumbnailPath": "videos/lesson-123/thumb.jpg",
"duration": 812,
"variants": [
{
"name": "720p",
"playlist": "videos/lesson-123/720p/index.m3u8",
"width": 1280,
"height": 720
}
]
}The service never returns public R2 links. It uploads the full HLS tree to your private bucket and returns only storage paths.
If cleanupSource is true, the original local source file is deleted only after every generated HLS asset has been uploaded and verified in R2 with a matching object size.
You can also upload a video file directly to the processor. This endpoint now queues background processing and returns immediately:
curl -X POST http://localhost:8080/api/v1/videos/upload \
-F "videoId=lesson-123" \
-F "file=@/absolute/path/to/source.mp4"Accepted response:
{
"job": {
"jobId": "9f8c0a0d5f4e7b7f1f9d7e90",
"status": "queued",
"videoId": "lesson-123",
"createdAt": "2026-04-29T10:15:00Z"
}
}Then poll the job status:
curl http://localhost:8080/api/v1/video-jobs/9f8c0a0d5f4e7b7f1f9d7e90Successful job status:
{
"job": {
"jobId": "9f8c0a0d5f4e7b7f1f9d7e90",
"status": "succeeded",
"videoId": "lesson-123",
"createdAt": "2026-04-29T10:15:00Z",
"startedAt": "2026-04-29T10:15:01Z",
"completedAt": "2026-04-29T10:18:44Z",
"result": {
"videoId": "lesson-123",
"videoPath": "videos/lesson-123/master.m3u8",
"thumbnailPath": "videos/lesson-123/thumb.jpg",
"duration": 812
}
}
}For large uploads, the recommended flow is to upload the raw source to R2 first and then enqueue processing from that stored source object:
curl -X POST http://localhost:8080/api/v1/videos/enqueue \
-H "Content-Type: application/json" \
-d '{
"videoId": "lesson-123",
"sourceObjectKey": "video-sources/content-123/raw.mp4",
"cleanupSourceObject": true
}'This avoids proxying a 300-400 MB source file through your LMS app and then through the Go processor again. After a successful publish, the processor deletes the temporary raw source object when cleanupSourceObject is true.
Configure an authenticated callback so the LMS database is updated even when the admin closes the page:
VIDEO_JOB_CALLBACK_URL=https://www.paratattva.in/api/internal/video-processor/jobs
VIDEO_JOB_CALLBACK_SECRET=replace-with-a-strong-random-shared-secretTerminal succeeded and failed jobs are signed with HMAC-SHA256 and delivered in the background. Non-2xx responses are retried with capped exponential backoff. Delivery state is persisted with the job, so an undelivered callback resumes after a processor restart. The receiving endpoint must be idempotent because a callback can be delivered more than once.
Drop these into edtech-lms-platform:
examples/edtech-lms-platform/prisma/migration.sqlexamples/edtech-lms-platform/src/lib/hls-playback.tsexamples/edtech-lms-platform/src/app/api/learning/content/[contentId]/playback/route.ts
Set this in the LMS environment so Next.js can call the Go processor:
VIDEO_PROCESSOR_API_URL=http://localhost:8080Then the LMS admin route should:
- Create a presigned R2 upload URL for a temporary source object such as
video-sources/content-123/... - Upload the raw source file directly from the browser to R2
POSTthe source object key to/api/v1/videos/enqueue- Store the returned
jobId - Persist the returned
jobId, then accept the signed completion callback - Keep polling
/api/v1/video-jobs/:jobIdas a reconciliation fallback
If your LMS prefers to upload the file itself and send a server path later, /api/v1/videos/process remains available for synchronous server-side processing.
Example of the final result payload the LMS should store after the job succeeds:
{
"action": "process_hls",
"videoId": "lesson-123",
"sourcePath": "D:\\uploads\\lesson-123.mp4",
"cleanupSource": true
}Deploy this to Cloudflare:
examples/cloudflare-worker/src/index.tsexamples/cloudflare-worker/wrangler.toml
- R2 stays private.
- PostgreSQL stores only HLS object paths.
- Next.js issues short-lived HMAC playback tokens after auth and course-access checks.
- Cloudflare Worker validates the token on every request.
.m3u8playlists are rewritten so child manifests and media segments inherit the token.