跳到主要內容
/

用 Mux 在 Next.js 裡做付費影片串流:從上傳到播放

你想在產品裡加付費影片。聽起來不難,直到你開始研究:影片怎麼轉檔?怎麼做自適應串流?怎麼防止未付費用戶直接拿到影片 URL?怎麼在手機上也能順暢播放?

自建影片串流是一個看似簡單、實際上會吃掉你整個 sprint 的坑。

Mux 的存在就是讓你不用踩這個坑。

為什麼是 Mux

Mux 是一個影片基礎設施 API。你把影片丟給它,它幫你處理所有底層的事:轉檔、CDN 分發、adaptive bitrate streaming、DRM、播放器。你只需要拿到一個 playback ID,前端就能播。

跟自建方案比,Mux 解決了三個核心痛點:

轉檔和自適應串流。 你上傳一個 4K 影片,Mux 自動轉成多種解析度(360p 到 4K)。觀眾的網路慢,自動切到低畫質;網路好,自動切回高畫質。這套系統自己做需要 FFmpeg + HLS + CDN 的完整架構,Mux 一行 API 搞定。

Signed URL 和存取控制。 付費影片最怕的是 URL 外洩。Mux 的 signed playback 機制讓每個播放 URL 都有時效性和綁定條件,過期就失效。不需要自己實作 token 驗證邏輯。

成本結構。 Mux 按照影片儲存時間和觀看分鐘數計費。對小型 SaaS 來說,初期成本幾乎可以忽略——每分鐘儲存 0.007/月,每分鐘串流0.007/月,每分鐘串流 0.00XX。一個有 50 支教學影片、每月幾百個付費用戶的平台,月成本大概在 $20-50 美元。比自己架 S3 + CloudFront + 轉檔 Lambda 便宜得多,而且省下的工程時間才是最大的成本。

完整流程:從上傳到播放

整個流程分四步:建立 upload URL → 上傳影片 → 取得 playback ID → 前端播放。

Step 1:後端建立 Direct Upload

在 Next.js 的 API route 裡呼叫 Mux API,建立一個 direct upload URL:

// app/api/mux/upload/route.ts
import Mux from '@mux/mux-node'

const mux = new Mux({
  tokenId: process.env.MUX_TOKEN_ID!,
  tokenSecret: process.env.MUX_TOKEN_SECRET!,
})

export async function POST() {
  const upload = await mux.video.uploads.create({
    new_asset_settings: {
      playback_policy: ['signed'],
      encoding_tier: 'baseline',
    },
    cors_origin: process.env.NEXT_PUBLIC_APP_URL,
  })

  return Response.json({
    uploadUrl: upload.url,
    uploadId: upload.id,
  })
}

重點:playback_policy 設為 signed,這樣生成的 playback ID 必須搭配簽名才能播放,未付費用戶拿到 ID 也沒用。

Step 2:前端上傳影片

拿到 upload URL 後,前端用 @mux/mux-uploader-react 處理上傳。它支援拖放、進度條、斷點續傳:

import MuxUploader from '@mux/mux-uploader-react'

export function VideoUploader({ uploadUrl }: { uploadUrl: string }) {
  return (
    <MuxUploader
      endpoint={uploadUrl}
      onSuccess={() => console.log('上傳完成')}
    />
  )
}

上傳完成後,Mux 會在背景自動轉檔。根據影片長度,這通常需要幾秒到幾分鐘。

Step 3:Webhook 接收轉檔完成通知

影片轉檔完成後,Mux 會透過 webhook 通知你。你需要一個 API route 來接收:

// app/api/mux/webhook/route.ts
import { headers } from 'next/headers'

export async function POST(request: Request) {
  const body = await request.json()
  const headersList = await headers()
  const signature = headersList.get('mux-signature')

  // 驗證 webhook 簽名(production 必須做)
  // ...

  if (body.type === 'video.asset.ready') {
    const asset = body.data
    const playbackId = asset.playback_ids?.[0]?.id

    // 把 playbackId 存到 Supabase
    // await supabase.from('videos').update({ 
    //   playback_id: playbackId, 
    //   status: 'ready' 
    // }).eq('upload_id', asset.upload_id)
  }

  return Response.json({ received: true })
}

這個 webhook 是整個流程的關鍵銜接點。影片上傳和轉檔是非同步的,你的前端不能一直 polling 問「好了沒」——讓 Mux 主動通知你,然後你更新資料庫狀態。

Step 4:前端播放

影片準備好之後,用 @mux/mux-player-react 播放:

import MuxPlayer from '@mux/mux-player-react'

export function VideoPlayer({ 
  playbackId, 
  token 
}: { 
  playbackId: string
  token: string 
}) {
  return (
    <MuxPlayer
      playbackId={playbackId}
      tokens={{ playback: token }}
      accentColor="#6366f1"
      style={{ aspectRatio: '16/9' }}
    />
  )
}

MuxPlayer 是一個功能完整的播放器組件:自適應串流、快捷鍵、字幕、畫質切換,全部內建。你不需要自己組裝 video.js 或 hls.js。

付費牆:搭配 Supabase Auth

Signed playback 的流程是這樣的:

  1. 用戶點擊「觀看影片」
  2. 前端帶著 session token 呼叫你的 API
  3. API 驗證用戶身份和付費狀態(查 Supabase)
  4. 驗證通過,用 Mux signing key 生成一個有時效的 playback token
  5. 前端拿到 token,傳給 MuxPlayer
// app/api/mux/token/route.ts
import Mux from '@mux/mux-node'
import { createClient } from '@/lib/supabase/server'

export async function POST(request: Request) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  
  if (!user) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // 檢查付費狀態
  const { data: subscription } = await supabase
    .from('subscriptions')
    .select('status')
    .eq('user_id', user.id)
    .single()

  if (subscription?.status !== 'active') {
    return Response.json({ error: 'Subscription required' }, { status: 403 })
  }

  const { playbackId } = await request.json()

  // 生成 signed token
  const token = Mux.jwt.signPlaybackId(playbackId, {
    keyId: process.env.MUX_SIGNING_KEY_ID!,
    keySecret: process.env.MUX_SIGNING_KEY_SECRET!,
    expiration: '1h',
  })

  return Response.json({ token })
}

這個模式的安全性在於:playback token 有時效(上面設的是 1 小時),即使被截取也只能用很短的時間。而且每次播放都要經過你的 API 驗證,付費狀態是即時檢查的。

成本估算:中文市場的開發者視角

對於一個剛起步的付費教學平台,典型的成本結構:

  • 50 支影片,平均 15 分鐘:儲存成本約 $5.25/月
  • 200 個付費用戶,每人每月看 2 小時:串流成本約 $5-10/月
  • 轉檔:每分鐘影片 $0.015,一次性成本

初期總成本大概 **1520/。這個數字在你有200個付費用戶的時候幾乎可以忽略——即使每人只收15-20/月**。這個數字在你有 200 個付費用戶的時候幾乎可以忽略——即使每人只收 5/月,營收也已經是成本的 50 倍。

隨著用戶增長,成本會線性增加,但始終遠低於自建方案的工程維護成本。你的時間應該花在做內容和獲客上,不是花在除錯 FFmpeg 設定。

完整的影片 SaaS 還需要什麼

Mux 解決了影片的基礎設施問題,但一個完整的付費影片平台還需要:

  • 內容管理:哪些影片屬於哪個課程、什麼順序播放(這是你的資料庫設計)
  • 進度追蹤:用戶看到哪裡了、完成率多少(MuxPlayer 有 timeupdate 事件可以監聽)
  • 權限管理:哪些影片免費、哪些要訂閱、哪些要單獨購買(這是 access model 的設計)

這些是產品層的問題,不是影片基礎設施的問題。分清楚這個邊界,你的架構會乾淨很多。


想看完整的付費影片平台架構設計?我們在 影片串流教學 裡拆解了從 Mux 設定到 access model 的每個細節。