refactor: Streaming MP4 files in the browser using video html element instead of waiting for the entire file to download before playing

This commit is contained in:
kevin.zhang
2024-04-15 09:51:40 +08:00
parent 0550e433d1
commit d13a3cf6e9

View File

@@ -3,7 +3,7 @@ import glob
import shutil import shutil
from fastapi import Request, Depends, Path, BackgroundTasks, UploadFile from fastapi import Request, Depends, Path, BackgroundTasks, UploadFile
from fastapi.responses import FileResponse from fastapi.responses import FileResponse, StreamingResponse
from fastapi.params import File from fastapi.params import File
from loguru import logger from loguru import logger
@@ -137,7 +137,37 @@ def upload_bgm_file(request: Request, file: UploadFile = File(...)):
async def stream_video(request: Request, file_path: str): async def stream_video(request: Request, file_path: str):
tasks_dir = utils.task_dir() tasks_dir = utils.task_dir()
video_path = os.path.join(tasks_dir, file_path) video_path = os.path.join(tasks_dir, file_path)
if os.path.isfile(video_path): range_header = request.headers.get('Range')
return FileResponse(video_path, media_type="video/mp4", filename=file_path) video_size = os.path.getsize(video_path)
else: start, end = 0, video_size - 1
return {"message": "File not found."}
length = video_size
if range_header:
range_ = range_header.split('bytes=')[1]
start, end = [int(part) if part else None for part in range_.split('-')]
if start is None:
start = video_size - end
end = video_size - 1
if end is None:
end = video_size - 1
length = end - start + 1
def file_iterator(file_path, offset=0, bytes_to_read=None):
with open(file_path, 'rb') as f:
f.seek(offset, os.SEEK_SET)
remaining = bytes_to_read or video_size
while remaining > 0:
bytes_to_read = min(4096, remaining)
data = f.read(bytes_to_read)
if not data:
break
remaining -= len(data)
yield data
response = StreamingResponse(file_iterator(video_path, start, length), media_type='video/mp4')
response.headers['Content-Range'] = f'bytes {start}-{end}/{video_size}'
response.headers['Accept-Ranges'] = 'bytes'
response.headers['Content-Length'] = str(length)
response.status_code = 206 # Partial Content
return response