Skip to main content

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

MetricWhat It Tells You
Total viewsReach
Unique viewersAudience size
Watch timeEngagement depth
Rebuffering rateQuality of experience
Startup timePlayer performance
View drop-offWhere users stop watching

Pricing

ComponentCost
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 DataFree 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

MistakeImpactFix
Not using direct uploadVideo passes through your server — slow, expensiveUse Mux's direct upload URLs
Polling too frequently for statusRate limitedPoll every 5 seconds, stop after "ready"
Missing playback policyVideos not accessibleSet playback_policy: ['public']
No fallback for encodingUsers see broken playerShow "Processing..." until status is "ready"
Exposing token secretAccount compromiseServer-side only

Building with video? Compare Mux vs Cloudflare Stream vs api.video on APIScout — pricing, features, and encoding quality.

Comments