Claude
Skills
Sign in
Back

stream-mux

Included with Lifetime
$97 forever

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".

Image & Video

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 ?? "
Files: 1
Size: 31.0 KB
Complexity: 39/100
Category: Image & Video

Related in Image & Video