How to Add Video Streaming with Mux
·APIScout Team
muxvideo streamingvideo apitutorialapi integration
How to Add Video Streaming with Mux
Mux handles the hard parts of video: encoding, hosting, adaptive streaming, and analytics. Upload a video file, get a playback URL. This guide covers the full integration: uploads, player setup, live streaming, and tracking engagement.
What You'll Build
- Video upload (direct from browser)
- Adaptive bitrate streaming (HLS)
- Video player with Mux Player
- Upload progress tracking
- Video analytics (views, watch time, engagement)
Prerequisites: Node.js 18+, Mux account (free: no upfront cost, pay per minute).
1. Setup
Install
npm install @mux/mux-node @mux/mux-player-react
Initialize
// lib/mux.ts
import Mux from '@mux/mux-node';
export const mux = new Mux({
tokenId: process.env.MUX_TOKEN_ID!,
tokenSecret: process.env.MUX_TOKEN_SECRET!,
});
Environment Variables
# .env.local
MUX_TOKEN_ID=your_token_id
MUX_TOKEN_SECRET=your_token_secret
2. Upload Videos
Create Upload URL
// app/api/upload/route.ts
import { NextResponse } from 'next/server';
import { mux } from '@/lib/mux';
export async function POST() {
const upload = await mux.video.uploads.create({
new_asset_settings: {
playback_policy: ['public'],
encoding_tier: 'baseline', // or 'smart' for better quality
},
cors_origin: process.env.NEXT_PUBLIC_URL,
});
return NextResponse.json({
uploadId: upload.id,
uploadUrl: upload.url,
});
}
Direct Browser Upload
// components/VideoUpload.tsx
'use client';
import { useState } from 'react';
export function VideoUpload({ onComplete }: {
onComplete: (uploadId: string) => void;
}) {
const [progress, setProgress] = useState(0);
const [uploading, setUploading] = useState(false);
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
// 1. Get upload URL from your server
const res = await fetch('/api/upload', { method: 'POST' });
const { uploadId, uploadUrl } = await res.json();
// 2. Upload directly to Mux
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
setProgress(Math.round((e.loaded / e.total) * 100));
}
};
xhr.onload = () => {
setUploading(false);
onComplete(uploadId);
};
xhr.open('PUT', uploadUrl);
xhr.send(file);
};
return (
<div>
<input
type="file"
accept="video/*"
onChange={handleUpload}
disabled={uploading}
/>
{uploading && (
<div>
<progress value={progress} max={100} />
<span>{progress}%</span>
</div>
)}
</div>
);
}
3. Check Upload Status
Poll for Asset Ready
// app/api/upload/[id]/route.ts
import { NextResponse } from 'next/server';
import { mux } from '@/lib/mux';
export async function GET(
req: Request,
{ params }: { params: { id: string } }
) {
const upload = await mux.video.uploads.retrieve(params.id);
if (upload.status === 'asset_created' && upload.asset_id) {
const asset = await mux.video.assets.retrieve(upload.asset_id);
return NextResponse.json({
status: asset.status, // 'preparing' | 'ready' | 'errored'
playbackId: asset.playback_ids?.[0]?.id,
duration: asset.duration,
aspectRatio: asset.aspect_ratio,
});
}
return NextResponse.json({
status: upload.status, // 'waiting' | 'asset_created'
});
}
4. Play Videos
Mux Player (React)
// components/VideoPlayer.tsx
'use client';
import MuxPlayer from '@mux/mux-player-react';
export function VideoPlayer({ playbackId, title }: {
playbackId: string;
title?: string;
}) {
return (
<MuxPlayer
playbackId={playbackId}
metadata={{
video_title: title,
viewer_user_id: 'user-123', // For analytics
}}
streamType="on-demand"
style={{ width: '100%', maxWidth: '800px' }}
/>
);
}
Thumbnail and Poster Images
Mux auto-generates thumbnails:
// Thumbnail at specific time
const thumbnailUrl = `https://image.mux.com/${playbackId}/thumbnail.jpg?time=10`;
// Animated GIF preview
const gifUrl = `https://image.mux.com/${playbackId}/animated.gif?start=5&end=10`;
// Storyboard for scrubbing
const storyboardUrl = `https://image.mux.com/${playbackId}/storyboard.vtt`;
5. Live Streaming
Create Live Stream
// app/api/live/route.ts
import { NextResponse } from 'next/server';
import { mux } from '@/lib/mux';
export async function POST() {
const liveStream = await mux.video.liveStreams.create({
playback_policy: ['public'],
new_asset_settings: {
playback_policy: ['public'],
},
latency_mode: 'low', // or 'standard' or 'reduced'
});
return NextResponse.json({
streamKey: liveStream.stream_key,
playbackId: liveStream.playback_ids?.[0]?.id,
rtmpUrl: 'rtmps://global-live.mux.com:443/app',
});
}
Stream from OBS/Software
Point OBS or any RTMP software to:
- Server:
rtmps://global-live.mux.com:443/app - Stream Key: The key from the API response
Play Live Stream
<MuxPlayer
playbackId={livePlaybackId}
streamType="live"
metadata={{ video_title: 'My Live Stream' }}
/>
6. Video Analytics
Mux Data (Built-In)
Mux Player automatically tracks engagement. Query analytics via API:
// Get video views for an asset
const views = await mux.data.videoViews.list({
filters: [`video_id:${assetId}`],
timeframe: ['7:days'],
});
// Get overall metrics
const metrics = await mux.data.metrics.breakdown('views', {
group_by: 'video_title',
timeframe: ['30:days'],
});
Key Metrics
| Metric | What It Tells You |
|---|---|
| Total views | Reach |
| Unique viewers | Audience size |
| Watch time | Engagement depth |
| Rebuffering rate | Quality of experience |
| Startup time | Player performance |
| View drop-off | Where users stop watching |
Pricing
| Component | Cost |
|---|---|
| Encoding | $0.015/minute (baseline), $0.035/minute (smart) |
| Storage | $0.007/minute/month |
| Streaming | $0.00059/minute delivered |
| Live streaming | $0.03/minute (input) + delivery |
| Mux Data | Free with Mux Video |
Example: 100 videos × 5 min each × 1,000 views/month:
- Encoding: $7.50 (one-time)
- Storage: $3.50/month
- Delivery: $295/month
- Total: ~$300/month
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Not using direct upload | Video passes through your server — slow, expensive | Use Mux's direct upload URLs |
| Polling too frequently for status | Rate limited | Poll every 5 seconds, stop after "ready" |
| Missing playback policy | Videos not accessible | Set playback_policy: ['public'] |
| No fallback for encoding | Users see broken player | Show "Processing..." until status is "ready" |
| Exposing token secret | Account compromise | Server-side only |
Building with video? Compare Mux vs Cloudflare Stream vs api.video on APIScout — pricing, features, and encoding quality.