Mux Video
"Mux: video hosting, HLS streaming, upload API, playback URLs, thumbnails, analytics, Mux Player, Next.js integration"
Mux is an API-first video platform built for developers. It handles the full video pipeline — ingest, encoding, storage, and delivery — so you never manage transcoding infrastructure. Every video uploaded becomes an adaptive bitrate HLS stream with a simple playback URL. Mux provides first-class SDKs, a drop-in player component, and a data analytics product that tracks quality of experience metrics. Design around Mux's asset lifecycle: create an asset, get a playback ID, use that ID everywhere. Lean on webhooks for async status updates rather than polling. Use signed URLs for access control and direct uploads for client-side ingest to avoid proxying large files through your server. ## Key Points - Use direct uploads for client-side video ingest to avoid proxying large files through your API server. - Set `encoding_tier: "smart"` to let Mux optimize encoding cost versus quality automatically. - Always handle webhooks for asset status changes rather than polling the API. - Store the `playback_id` in your database, not the full URL, since URL structures may evolve. - Use signed playback for any content that requires access control or monetization. - Pass `metadata` to Mux Player for accurate analytics attribution per viewer and video. - Set `max_resolution_tier` to avoid encoding higher resolutions than your content warrants. - Use `thumbnail.jpg?time=N` to pick a representative frame rather than defaulting to the first frame. - Verify webhook signatures before processing any event to prevent spoofed requests. - Do not proxy video uploads through your server; use direct uploads to Mux's endpoint. - Do not poll the asset status endpoint in a loop; rely on `video.asset.ready` webhooks. - Do not hardcode playback URLs with `.m3u8`; use Mux Player which handles HLS negotiation. ## Quick Example ```typescript // package.json dependencies: // "@mux/mux-node": "^8.0.0" // "@mux/mux-player-react": "^2.0.0" ``` ``` MUX_TOKEN_ID=your-token-id MUX_TOKEN_SECRET=your-token-secret MUX_WEBHOOK_SECRET=your-webhook-signing-secret ```
skilldb get video-services-skills/Mux VideoFull skill: 283 linesMux Video
Core Philosophy
Mux is an API-first video platform built for developers. It handles the full video pipeline — ingest, encoding, storage, and delivery — so you never manage transcoding infrastructure. Every video uploaded becomes an adaptive bitrate HLS stream with a simple playback URL. Mux provides first-class SDKs, a drop-in player component, and a data analytics product that tracks quality of experience metrics. Design around Mux's asset lifecycle: create an asset, get a playback ID, use that ID everywhere. Lean on webhooks for async status updates rather than polling. Use signed URLs for access control and direct uploads for client-side ingest to avoid proxying large files through your server.
Setup
Install the Node SDK and configure credentials:
import Mux from "@mux/mux-node";
const mux = new Mux({
tokenId: process.env.MUX_TOKEN_ID!,
tokenSecret: process.env.MUX_TOKEN_SECRET!,
});
For Next.js projects, add the Mux Player component:
// package.json dependencies:
// "@mux/mux-node": "^8.0.0"
// "@mux/mux-player-react": "^2.0.0"
Environment variables required:
MUX_TOKEN_ID=your-token-id
MUX_TOKEN_SECRET=your-token-secret
MUX_WEBHOOK_SECRET=your-webhook-signing-secret
Key Techniques
Creating Assets from a URL
async function createAssetFromUrl(videoUrl: string) {
const asset = await mux.video.assets.create({
input: [{ url: videoUrl }],
playback_policy: ["public"],
encoding_tier: "smart",
max_resolution_tier: "1080p",
});
return {
assetId: asset.id,
playbackId: asset.playback_ids?.[0]?.id,
status: asset.status, // "preparing" initially
};
}
Direct Uploads (Client-Side Ingest)
Create an upload URL server-side, then upload from the browser:
// Server: create a direct upload
async function createDirectUpload() {
const upload = await mux.video.uploads.create({
new_asset_settings: {
playback_policy: ["public"],
encoding_tier: "smart",
},
cors_origin: process.env.NEXT_PUBLIC_APP_URL,
});
return { uploadId: upload.id, uploadUrl: upload.url };
}
// Client: use Mux Uploader or fetch
import MuxUploader from "@mux/mux-uploader-react";
function VideoUploader({ uploadUrl }: { uploadUrl: string }) {
return (
<MuxUploader
endpoint={uploadUrl}
onSuccess={() => console.log("Upload complete")}
onError={(e) => console.error("Upload failed", e)}
/>
);
}
Playback URLs and Thumbnails
function getPlaybackUrls(playbackId: string) {
const base = `https://stream.mux.com/${playbackId}`;
return {
hls: `${base}.m3u8`,
thumbnail: `https://image.mux.com/${playbackId}/thumbnail.jpg`,
animatedGif: `https://image.mux.com/${playbackId}/animated.gif`,
storyboard: `https://image.mux.com/${playbackId}/storyboard.vtt`,
// Thumbnail at specific time
thumbnailAt10s: `https://image.mux.com/${playbackId}/thumbnail.jpg?time=10`,
// Custom dimensions
thumbnailSized: `https://image.mux.com/${playbackId}/thumbnail.jpg?width=640&height=360`,
};
}
Mux Player in Next.js
import MuxPlayer from "@mux/mux-player-react";
interface VideoPlayerProps {
playbackId: string;
title: string;
accentColor?: string;
}
function VideoPlayer({ playbackId, title, accentColor = "#FF5733" }: VideoPlayerProps) {
return (
<MuxPlayer
playbackId={playbackId}
metadata={{
video_title: title,
viewer_user_id: "user-abc-123",
}}
accentColor={accentColor}
autoPlay="muted"
streamType="on-demand"
thumbnailTime={5}
style={{ aspectRatio: "16/9", width: "100%" }}
/>
);
}
Signed (Private) Playback
import jwt from "jsonwebtoken";
function createSignedPlaybackUrl(
playbackId: string,
signingKeyId: string,
signingPrivateKey: string,
expiresInSeconds = 3600
) {
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign(
{
sub: playbackId,
aud: "v",
exp: now + expiresInSeconds,
kid: signingKeyId,
},
Buffer.from(signingPrivateKey, "base64"),
{ algorithm: "RS256" }
);
return `https://stream.mux.com/${playbackId}.m3u8?token=${token}`;
}
Webhook Handling
import { buffer } from "micro";
import Mux from "@mux/mux-node";
import type { NextApiRequest, NextApiResponse } from "next";
export const config = { api: { bodyParser: false } };
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const rawBody = (await buffer(req)).toString("utf8");
const signature = req.headers["mux-signature"] as string;
try {
Mux.Webhooks.verifySignature(rawBody, {
"mux-signature": signature,
}, process.env.MUX_WEBHOOK_SECRET!);
} catch {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(rawBody);
switch (event.type) {
case "video.asset.ready":
await handleAssetReady(event.data);
break;
case "video.asset.errored":
await handleAssetError(event.data);
break;
case "video.upload.asset_created":
await handleUploadComplete(event.data);
break;
}
res.status(200).json({ received: true });
}
async function handleAssetReady(data: { id: string; playback_ids: Array<{ id: string }> }) {
const playbackId = data.playback_ids[0]?.id;
// Update your database with the ready asset
await db.video.update({
where: { muxAssetId: data.id },
data: { status: "ready", playbackId },
});
}
Analytics with Mux Data
async function getVideoViewMetrics(videoId: string) {
const views = await mux.data.videoViews.list({
filters: [`video_id:${videoId}`],
timeframe: ["7:days"],
});
const metrics = await mux.data.metrics.breakdown("aggregate_startup_time", {
group_by: "browser",
timeframe: ["7:days"],
filters: [`video_id:${videoId}`],
});
return { views: views.data, metrics: metrics.data };
}
Asset Management
async function manageAssets() {
// List assets
const assets = await mux.video.assets.list({ limit: 25 });
// Get specific asset
const asset = await mux.video.assets.retrieve("asset-id");
// Add captions/subtitles
await mux.video.assets.createTrack("asset-id", {
url: "https://example.com/captions.vtt",
type: "text",
text_type: "subtitles",
language_code: "en",
name: "English",
});
// Delete asset
await mux.video.assets.delete("asset-id");
}
Best Practices
- Use direct uploads for client-side video ingest to avoid proxying large files through your API server.
- Set
encoding_tier: "smart"to let Mux optimize encoding cost versus quality automatically. - Always handle webhooks for asset status changes rather than polling the API.
- Store the
playback_idin your database, not the full URL, since URL structures may evolve. - Use signed playback for any content that requires access control or monetization.
- Pass
metadatato Mux Player for accurate analytics attribution per viewer and video. - Set
max_resolution_tierto avoid encoding higher resolutions than your content warrants. - Use
thumbnail.jpg?time=Nto pick a representative frame rather than defaulting to the first frame. - Verify webhook signatures before processing any event to prevent spoofed requests.
Anti-Patterns
- Do not proxy video uploads through your server; use direct uploads to Mux's endpoint.
- Do not poll the asset status endpoint in a loop; rely on
video.asset.readywebhooks. - Do not hardcode playback URLs with
.m3u8; use Mux Player which handles HLS negotiation. - Do not skip webhook signature verification in production, even if it works without it in dev.
- Do not create assets with
playback_policy: ["signed"]unless you have signing key infrastructure set up, or playback will silently fail. - Do not assume the asset is ready immediately after upload; always wait for the
readystatus. - Do not ignore the
video.asset.erroredwebhook; surface ingest failures to users. - Do not embed Mux token credentials in client-side code; keep them server-side only.
Install this skill directly: skilldb add video-services-skills
Related Skills
Amazon IVS
"Amazon Interactive Video Service: low-latency live streaming, real-time stages, chat, stream recording, channel management, viewer analytics"
Api.video
"api.video: video hosting API, upload, live streaming, player customization, analytics, webhooks"
Bunny Stream
"Bunny.net Stream: video hosting, HLS delivery, direct uploads, video collections, thumbnail generation, token authentication, webhook events"
Cloudflare Stream
"Cloudflare Stream: video hosting, adaptive streaming, direct upload, watermarks, signed URLs, Workers integration"
LiveKit
"LiveKit: real-time video/audio, WebRTC, rooms, tracks, screen sharing, recording, React components"
Vimeo OTT
"Vimeo OTT API: video-on-demand platform, subscription management, customer auth, content libraries, embed players, analytics, webhook events"