stream-mux
MUX video infrastructure — live streaming, VOD uploads, asset management, and webhook processing. Use this skill when the user says "add mux", "setup video streaming", "add live streaming", "setup mux", "video uploads", or "stream-mux".
What this skill does
# Stream MUX
MUX-powered video infrastructure with live streaming (RTMP ingest + HLS playback), client-side direct uploads for VOD, asset lifecycle management, and webhook processing for real-time status updates. All asset metadata is persisted to Postgres via Drizzle.
## Prerequisites
- Next.js app with `src/` directory and App Router
- `env-config` skill applied (`src/env.ts` with Zod schema)
- `db` skill applied (Drizzle ORM + Postgres)
- `storage` skill applied (for supplementary file storage)
- MUX account with API credentials ([mux.com/dashboard](https://dashboard.mux.com))
## Installation
```bash
bun add @mux/mux-node
```
## Environment Variables
Add to `.env.local`:
```env
# MUX Video
MUX_TOKEN_ID=your-mux-token-id
MUX_TOKEN_SECRET=your-mux-token-secret
MUX_WEBHOOK_SIGNING_SECRET=your-mux-webhook-signing-secret
```
### Update `src/env.ts`
Add to the `server` object:
```typescript
server: {
// ... existing variables
MUX_TOKEN_ID: z.string().min(1).optional(),
MUX_TOKEN_SECRET: z.string().min(1).optional(),
MUX_WEBHOOK_SIGNING_SECRET: z.string().optional(),
},
```
Add to the `runtimeEnv` object:
```typescript
runtimeEnv: {
// ... existing variables
MUX_TOKEN_ID: process.env.MUX_TOKEN_ID,
MUX_TOKEN_SECRET: process.env.MUX_TOKEN_SECRET,
MUX_WEBHOOK_SIGNING_SECRET: process.env.MUX_WEBHOOK_SIGNING_SECRET,
},
```
## What Gets Created
```
src/
├── lib/
│ ├── video/
│ │ ├── mux.ts # getMuxClient() singleton
│ │ ├── mux-live.ts # Live stream CRUD operations
│ │ ├── mux-vod.ts # VOD upload + asset management
│ │ ├── mux-webhooks.ts # Webhook verification + event handling
│ │ └── types-mux.ts # MuxAsset, MuxLiveStream, MuxUpload types
│ └── db/
│ └── schema/
│ └── mux-assets.ts # Drizzle schema: mux_assets table
└── app/
└── api/
└── mux/
├── upload/
│ └── route.ts # POST — get direct upload URL
├── assets/
│ ├── route.ts # GET — list assets
│ └── [id]/
│ └── route.ts # GET asset, DELETE asset
├── live/
│ └── route.ts # POST create, GET list live streams
└── webhook/
└── route.ts # POST — MUX webhook handler
```
## Setup Steps
### Step 1: Create `src/lib/video/types-mux.ts`
```typescript
export type MuxAssetStatus =
| "preparing"
| "ready"
| "errored"
| "deleted";
export type MuxAssetType = "vod" | "live";
export type MuxAsset = {
id: string;
playbackId: string;
status: MuxAssetStatus;
duration: number | null;
resolution: string | null;
createdAt: Date;
};
export type MuxLiveStream = {
id: string;
streamKey: string;
rtmpUrl: string;
playbackId: string;
status: string;
};
export type MuxUpload = {
id: string;
url: string;
assetId: string | null;
status: string;
};
export type CreateLiveStreamOptions = {
playbackPolicy?: "public" | "signed";
reconnectWindow?: number;
maxContinuousDuration?: number;
newAssetSettings?: {
playbackPolicy?: "public" | "signed";
};
};
export type MuxWebhookEvent = {
type: string;
data: {
id: string;
playback_ids?: Array<{ id: string; policy: string }>;
status?: string;
duration?: number;
resolution_tier?: string;
stream_key?: string;
asset_id?: string;
upload_id?: string;
new_asset_settings?: Record<string, unknown>;
[key: string]: unknown;
};
environment?: { name: string; id: string };
created_at: string;
};
```
### Step 2: Create `src/lib/video/mux.ts`
```typescript
import Mux from "@mux/mux-node";
let muxClient: Mux | null = null;
export function getMuxClient(): Mux {
if (muxClient) return muxClient;
const tokenId = process.env.MUX_TOKEN_ID;
const tokenSecret = process.env.MUX_TOKEN_SECRET;
if (!tokenId) throw new Error("MUX_TOKEN_ID is not set");
if (!tokenSecret) throw new Error("MUX_TOKEN_SECRET is not set");
muxClient = new Mux({
tokenId,
tokenSecret,
});
return muxClient;
}
```
### Step 3: Create `src/lib/video/mux-live.ts`
```typescript
import { getMuxClient } from "@/lib/video/mux";
import type {
MuxLiveStream,
CreateLiveStreamOptions,
} from "@/lib/video/types-mux";
/**
* Create a new MUX live stream with RTMP ingest.
* Returns the stream key, RTMP URL, and playback ID.
*/
export async function createLiveStream(
options: CreateLiveStreamOptions = {}
): Promise<MuxLiveStream> {
const mux = getMuxClient();
const stream = await mux.video.liveStreams.create({
playback_policy: [options.playbackPolicy ?? "public"],
reconnect_window: options.reconnectWindow ?? 60,
max_continuous_duration: options.maxContinuousDuration ?? 43200,
new_asset_settings: {
playback_policy: [
options.newAssetSettings?.playbackPolicy ?? "public",
],
},
});
const playbackId = stream.playback_ids?.[0]?.id ?? "";
return {
id: stream.id,
streamKey: stream.stream_key ?? "",
rtmpUrl: `rtmp://global-live.mux.com:5222/app/${stream.stream_key ?? ""}`,
playbackId,
status: stream.status ?? "idle",
};
}
/**
* Get the current status of a live stream.
*/
export async function getLiveStreamStatus(
liveStreamId: string
): Promise<MuxLiveStream> {
const mux = getMuxClient();
const stream = await mux.video.liveStreams.retrieve(liveStreamId);
const playbackId = stream.playback_ids?.[0]?.id ?? "";
return {
id: stream.id,
streamKey: stream.stream_key ?? "",
rtmpUrl: `rtmp://global-live.mux.com:5222/app/${stream.stream_key ?? ""}`,
playbackId,
status: stream.status ?? "idle",
};
}
/**
* Delete a live stream.
*/
export async function deleteLiveStream(liveStreamId: string): Promise<void> {
const mux = getMuxClient();
await mux.video.liveStreams.delete(liveStreamId);
}
/**
* List all live streams.
*/
export async function listLiveStreams(): Promise<MuxLiveStream[]> {
const mux = getMuxClient();
const streams = await mux.video.liveStreams.list();
const results: MuxLiveStream[] = [];
for await (const stream of streams) {
const playbackId = stream.playback_ids?.[0]?.id ?? "";
results.push({
id: stream.id,
streamKey: stream.stream_key ?? "",
rtmpUrl: `rtmp://global-live.mux.com:5222/app/${stream.stream_key ?? ""}`,
playbackId,
status: stream.status ?? "idle",
});
}
return results;
}
```
### Step 4: Create `src/lib/video/mux-vod.ts`
```typescript
import { getMuxClient } from "@/lib/video/mux";
import type { MuxAsset, MuxUpload } from "@/lib/video/types-mux";
/**
* Create a direct upload URL for client-side video uploads.
* The client POSTs the video file directly to this URL.
*/
export async function createUploadUrl(): Promise<MuxUpload> {
const mux = getMuxClient();
const upload = await mux.video.uploads.create({
cors_origin: "*",
new_asset_settings: {
playback_policy: ["public"],
encoding_tier: "baseline",
},
});
return {
id: upload.id,
url: upload.url ?? "",
assetId: upload.asset_id ?? null,
status: upload.status ?? "waiting",
};
}
/**
* Get a single MUX asset by ID.
*/
export async function getAsset(assetId: string): Promise<MuxAsset> {
const mux = getMuxClient();
const asset = await mux.video.assets.retrieve(assetId);
const playbackId = asset.playback_ids?.[0]?.id ?? "";
return {
id: asset.id,
playbackId,
status: mapAssetStatus(asset.status ?? "preparing"),
duration: asset.duration ?? null,
resolution: asset.resolution_tier ?? null,
createdAt: new Date(asset.created_at ?? Date.now()),
};
}
/**
* List all MUX assets.
*/
export async function listAssets(): Promise<MuxAsset[]> {
const mux = getMuxClient();
const assets = await mux.video.assets.list();
const results: MuxAsset[] = [];
for await (const asset of assets) {
const playbackId = asset.playback_ids?.[0]?.id ?? "Related in Image & Video
watch
IncludedWatch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or Whisper API fallback), and hands the result to Claude so it can answer questions about what's in the video.
physical-ai-defect-image-generation
IncludedUse when the user wants to orchestrate defect image generation, run associated setup, or handle outputs on OSMO. The Day 0 path handles cold-start with USD-to-ROI, image-edit augmentation, and AnomalyGen to create initial PCBA datasets. The Day 1 path performs inference and labeling on real images. This skill helps with first-time asset setup, creation of finetuning checkpoints, and configuring deployment. Trigger keywords: defect image generation, dig workflow, dig pipeline, defect image detection workflow, aoi pipeline, aoi anomalygen, usd2roi anomalygen, day 0 pcba, day 1 pcba, day 1 real-photo alignment, day 1 manual roi, metal surface anomaly, glass defect, anomalygen finetune, setup_pcb, setup_metal, setup_glass, setup_pretrained, dig setup, dig datasets, dig pretrained checkpoint, dig image-edit endpoint.
accelint-react-best-practices
IncludedReact performance optimization and best practices. ALWAYS use this skill when working with any React code - writing components, hooks, JSX; refactoring; optimizing re-renders, memoization, state management; reviewing for performance; fixing hydration mismatches; debugging infinite re-renders, stale closures, input focus loss, animations restarting; preventing remounting; implementing transitions, lazy initialization, effect dependencies. Even simple React tasks benefit from these patterns. Covers React 19+ (useEffectEvent, Activity, ref props). Triggers - useEffect, useState, useMemo, useCallback, memo, inline components, nested components, components inside components, re-render, performance, hydration, SSR, Next.js, useDeferredValue, combined hooks.
elevenlabs-agents
IncludedBuild conversational AI voice agents with ElevenLabs Platform using React, JavaScript, React Native, or Swift SDKs. Configure agents, tools (client/server/MCP), RAG knowledge bases, multi-voice, and Scribe real-time STT. Use when: building voice chat interfaces, implementing AI phone agents with Twilio, configuring agent workflows or tools, adding RAG knowledge bases, testing with CLI "agents as code", or troubleshooting deprecated @11labs packages, Android audio cutoff, CSP violations, dynamic variables, or WebRTC config. Keywords: ElevenLabs Agents, ElevenLabs voice agents, AI voice agents, conversational AI, @elevenlabs/react, @elevenlabs/client, @elevenlabs/react-native, @elevenlabs/elevenlabs-js, @elevenlabs/agents-cli, elevenlabs SDK, voice AI, TTS, text-to-speech, ASR, speech recognition, turn-taking model, WebRTC voice, WebSocket voice, ElevenLabs conversation, agent system prompt, agent tools, agent knowledge base, RAG voice agents, multi-voice agents, pronunciation dictionary, voice speed control, elevenlabs scribe, @11labs deprecated, Android audio cutoff, CSP violation elevenlabs, dynamic variables elevenlabs, case-sensitive tool names, webhook authentication
humanizer
IncludedHumanize AI-generated text by detecting and removing patterns typical of LLM output. Rewrites text to sound natural, specific, and human. Uses 28 pattern detectors, 560+ AI vocabulary terms across 3 tiers, and statistical analysis (burstiness, type-token ratio, readability) for comprehensive detection. Use when asked to humanize text, de-AI writing, make content sound more natural/human, review writing for AI patterns, score text for AI detection, or improve AI-generated drafts. Covers content, language, style, communication, and filler categories.
generating-mermaid-diagrams
IncludedSalesforce architecture diagrams using Mermaid with ASCII fallback. Use this skill when generating text-based diagrams for Salesforce architecture, OAuth flows, ERDs, integration sequences, or Agentforce structure. TRIGGER when: user says "diagram", "visualize", "ERD", or asks for sequence diagrams, flowcharts, class diagrams, or architecture visualizations in Mermaid. DO NOT TRIGGER when: user wants PNG/SVG image output (use generating-visual-diagrams), or asks about non-Salesforce systems.