iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
Software Development

用 FastAPI 打造你的 AI 服務系列 第 13

[Day 13] StreamingResponse (二):支援 Range 請求的影片串流服務

  • 分享至 

  • xImage
  •  

繼上一篇介紹 StreamingResponse 的基礎應用後,今天我們要深入探討一個更實用的場景:如何實現支援 Range 請求的影片串流服務。這項技術能讓使用者在瀏覽器中直接播放影片,並且支援拖曳進度條跳到任意時間點播放,就像 YouTube 或 Netflix 一樣的體驗。

雖然跟 AI 比較沒有直接關係,只有部分 AI 應用會需要有播放影片功能,但還是想趁這個機會跟大家分享一下~ (畢竟之前也花了不少時間在處理這項需求XD)

什麼是 Range 請求?

Range 請求是 HTTP/1.1 規範中定義的一項重要功能,它允許客戶端請求資源的特定部分,而不是整個檔案。對於影片串流來說,這意味著:

  • 即時播放:不需要等待整部影片下載完成就能開始播放
  • 跳躍播放:使用者可以點擊進度條的任意位置立即跳轉
  • 節省頻寬:只下載需要的部分,減少不必要的資料傳輸
  • 更好的使用者體驗:響應速度快,操作流暢

當瀏覽器的 <video> 標籤請求影片時,它會在 HTTP 標頭中加入 Range 欄位,例如 Range: bytes=0-1023,表示要求下載從第 0 位元組到第 1023 位元組的內容。

傳統 StreamingResponse 的限制

一般的 StreamingResponse 雖然能夠串流影片,但無法處理 Range 請求。這會導致:

  1. 無法跳躍播放:使用者點擊進度條時,瀏覽器無法跳到指定位置
  2. 瀏覽器相容性問題:Chrome 等現代瀏覽器期望收到 206 狀態碼而非 200
  3. 效能不佳:每次都需要從頭開始傳輸,浪費頻寬

簡單 Demo (未使用 Range 請求)

先讓我們看一下直接傳送檔案的做法 (沒使用 Range 請求):

@app.get("/video")
def get_video():
    def iterfile(file_path: str):
        with open(file_path, mode="rb") as file_like:
            while True:
                chunk = file_like.read(1024 * 1024)  # 1MB chunks
                if not chunk:
                    break
                yield chunk
    
    video_path = "assets/record.mp4"
    
    # 檢查檔案是否存在
    if not os.path.exists(video_path):
        return {"error": "Video file not found"}
    
    return StreamingResponse(
        iterfile(video_path),
        media_type="video/mp4",
        headers={"Content-Disposition": "inline; filename=record.mp4"}
    )

以及一個簡單前端,使用 <video> 來串接影片 API (/video):

@app.get("/demo")
async def get_demo():
    html_content = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>Video Demo</title>
    </head>
    <body>
        <div>
            <h1>Video Streaming Demo</h1>
            <video width="800" height="600" controls>
                <source src="/video" type="video/mp4">
                Your browser does not support the video tag.
            </video>
        </div>
    </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)

Yes
(這是影片)

可以看到,影片是可以正常播放的,但是沒辦法隨意調整時間軸,一旦影片很長 (檔案很大),就會導致使用體驗極差。

Range 請求的核心實作

讓我們先從一個簡單的範例開始,理解 Range 請求的基本概念:

基礎實作

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import StreamingResponse
import os

app = FastAPI()
VIDEO_PATH = "sample.mp4"

@app.get("/video")
def get_video(request: Request):
    range_header = request.headers.get("range")
    if not range_header:
        raise HTTPException(status_code=416, detail="Range header missing")
    
    VIDEO_PATH = "assets/record.mp4"

    # 解析 Range 標頭 (e.g. bytes=1000-)
    start, end = range_header.replace("bytes=", "").split("-")
    start = int(start)
    file_size = os.path.getsize(VIDEO_PATH)
    end = int(end) if end else file_size - 1
    chunk_size = end - start + 1

    def iterfile(start_pos, end_pos):
        with open(VIDEO_PATH, "rb") as f:
            f.seek(start_pos)
            yield f.read(chunk_size)

    headers = {
        "Content-Range": f"bytes {start}-{end}/{file_size}",
        "Accept-Ranges": "bytes",
        "Content-Length": str(chunk_size),
    }
    return StreamingResponse(iterfile(start, end), media_type="video/mp4", headers=headers, status_code=206)

這個簡單的實作展示了 Range 請求的核心概念:解析 Range 標頭、計算檔案範圍、回傳 206 狀態碼。

Yes
(這也是影片)

可以看到,不只影片可以正常播放,也可以隨意調整時間軸,使用體驗好很多~

完整的實作

然而,在實際應用中,我們需要處理更多情況,例如:

  1. 無 Range 標頭的處理:當客戶端不提供 Range 標頭時,應回傳完整檔案
  2. 多種 Range 格式:支援 bytes=0-499bytes=500-bytes=-500 等不同格式
  3. 範圍驗證:檢查請求的範圍是否超出檔案大小
  4. 檔案存在性檢查:確保檔案存在且可讀取
  5. 錯誤處理:處理檔案不存在、範圍無效等異常情況
  6. 效能優化:使用適當的 chunk 大小進行串流傳輸

因此,實際應用中的程式碼會更長,有興趣的讀者可以參考這個回應的實作。

實作細節解析

HTTP 狀態碼的重要性

  • 200 OK:回傳完整檔案(無 Range 標頭或請求整個檔案)
  • 206 Partial Content:回傳檔案的部分內容(有效的 Range 請求)
  • 416 Range Not Satisfiable:請求的範圍無效(超出檔案大小或格式錯誤)

正確的狀態碼對瀏覽器的行為至關重要。Chrome 等現代瀏覽器在播放影片時會檢查是否支援 Range 請求,如果伺服器不回傳 206 狀態碼,可能會導致播放功能異常。

重要的 HTTP Header

  • Accept-Ranges: bytes:告知客戶端伺服器支援位元組範圍請求
  • Content-Range: bytes {start}-{end}/{total_size}:指定回傳內容在整個檔案中的位置
  • Content-Length:當前回應的內容長度(注意不是整個檔案的大小)
  • Content-Type:指定媒體類型,如 video/mp4

小結

透過實作 Range 請求支援,我們的 FastAPI 影片串流服務可以有更好的使用者體驗 (更流暢地播放),而伺服器也能更有效率地處理頻寬和資源。這項技術不僅適用於影片,也可以應用在其他大型檔案的下載服務上,如 PDF 閱讀器、音訊播放器等。


上一篇
[Day 12] StreamingResponse (一):即時資料傳輸與串流應用
系列文
用 FastAPI 打造你的 AI 服務13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言