Skip to main content

By the end of this tutorial you’ll have a Next.js 15 app that accepts video files, uploads them via TUS (resumable, multi-gigabyte capable), tracks transcoding status, and plays back the finished asset via HLS in the @livepeer/react Player. The path uses the standard Livepeer Asset API; any Gateway provider that exposes it works without code changes. This is the Persona 2 activation moment for VOD. The live streaming tutorial proved the real-time path; this one proves the persistent-asset path. Most video platforms ship both; the Asset API plus the Stream API together cover the full “Mux with AI bolted on” surface.

Required Tools

  • Node.js 20 or later
  • A Livepeer Gateway endpoint that exposes the Asset API (paid provider or self-hosted)
  • API key for the Gateway provider
  • A code editor
The Asset API is standardised across providers. The tutorial below works against any provider that implements the Livepeer Asset API or a self-hosted Gateway built on the open spec.

Asset Lifecycle

An asset passes through four phases between upload and playback. The transition from uploading to processing to ready happens server-side after the TUS upload completes. Five webhook events expose lifecycle transitions to your backend: asset.created, asset.updated, asset.ready, asset.failed, asset.deleted. For development the tutorial below polls GET /asset/{id}; production setups use the webhooks.

Project Bootstrap

1

Create the project

2

Install dependencies

The livepeer Node SDK runs server-side (asset creation, status checks). @livepeer/react powers the playback Player. tus-js-client handles the resumable upload from the browser.
3

Configure environment

Save as .env.local:
Both are server-side only; neither leaves your Next.js host. The browser never sees the API key.

Upload Endpoints

Two server routes handle the asset lifecycle: one creates the asset and returns a TUS URL, the other polls status by ID. Save as src/app/api/assets/route.ts:
Save as src/app/api/assets/[id]/route.ts:
The status endpoint returns the asset phase (waiting, uploading, processing, ready, failed). The client polls this endpoint until ready arrives.

Upload Component

Save as src/app/components/Uploader.tsx:
Three things to notice. The TUS client uploads in 5 MB chunks, which means a dropped network connection resumes from the last successful chunk on retry. The polling loop runs at 5-second intervals for 10 minutes; production replaces this with a webhook subscription. The component is fully client-side after the asset is created; the file never touches your Next.js host.

Playback Page

Save as src/app/watch/[playbackId]/page.tsx:
This is a server component. The livepeer.playback.get() call runs at request time on the server, builds the source array via getSrc(), and hands it to the Player. The Player accepts HLS, MP4, and WebRTC source candidates; for VOD it picks HLS by default and falls through to MP4 if the asset has static MP4 renditions enabled.

Home Page

Save as src/app/page.tsx:
Run the dev server:
Open http://localhost:3000. Pick a video file; watch the upload progress bar, then the processing phase, then the watch link. Click through to playback.

Production Webhooks

Polling works for development. Production replaces it with webhook subscriptions. The Gateway emits five events: Register a webhook endpoint at your Gateway provider’s webhook configuration page. The endpoint receives signed JSON payloads containing the asset object and event type. Verify signatures server-side before trusting the payload. The polling loop in the Uploader component above is replaceable with a webhook listener that updates a database row, which the client subscribes to via Server-Sent Events or WebSocket.

Production Considerations

Six things change between this local setup and a production deployment. Webhooks over polling. Replace the polling loop with webhook subscriptions. Polling is fine for ten files a day; webhooks scale to ten thousand. Access control. Add JWT-based access control on playback for paid or gated content. Public playbacks need no token; gated ones use Livepeer’s playback access control with signed JWTs. Static MP4 renditions. Set staticMp4: true in asset.create() for assets that need fast time-to-first-frame. Short-form video benefits; long-form prefers HLS. Encryption. For content that must not be downloaded raw, enable encryption on asset creation. The Gateway encrypts assets with AES-CBC and serves decryption keys gated by access control. Storage policies. Decide which assets persist on IPFS for permanence and which stay in regional cloud storage for cost. Long-tail catalogue goes to IPFS; trending content stays in fast cache. Webhook signature verification. Always verify the signature header before trusting webhook payloads. Replay attacks are trivial without verification. Full hardening guidance in .

Common Errors

The tusUploadUrl field on the asset response is empty or stale. Confirm the Gateway provider exposes tusEndpoint (some implementations use tusUploadUrl; check the response shape). Adjust the assets/route.ts handler to match.
The TUS upload completed locally but the Gateway didn’t finalise. Check the asset status; if it sits at uploading after the file completes, the TUS endpoint may not have received the final chunk. Lower chunkSize to 1 MB and retry; some intermediate proxies cap large chunk sizes.
The transcoding queue is backed up, or the file is in an unsupported format. Most providers process common formats (MP4, MOV, WebM, MKV) without issue; exotic codecs or DRM-protected files fail at the transcode step. Check status.errorMessage for the specific reason.
The asset is in the ready state but the playback URL hasn’t propagated to the CDN. Wait 30 seconds and retry. If it persists, the asset may have failed silently; query GET /asset/{id} and inspect the status object.
The playback ID is wrong, or the asset belongs to a different account. Confirm the playback ID matches the asset created with the same API key. Cross-account playback requires explicit permission grants on the asset.
You have a working upload-to-playback pipeline. The asset lifecycle (upload, transcode, play) is the same for all VOD content; the next step is adding access control for gated content.

AI agent prompt

Next Steps

Low-Latency Live Streaming

The live-streaming counterpart to this tutorial.

Transcoding Quickstart

Self-hosted RTMP-to-HLS for full control of the transcoding path.

Multi-Tenant Billing

Add per-customer auth, quotas, and usage tracking.

Production Hardening

Webhook signatures, access control, encryption, storage policies.
Last modified on May 31, 2026