Django+Video.js视频在Chrome中Seek功能失效问题求助
解决Chrome中Video.js播放S3视频时Seek功能失效的问题
问题根源
Chrome浏览器的视频播放对HTTP Range请求有硬性依赖:当用户点击进度条或使用方向键seek时,浏览器会发送带Range头的请求,要求服务器返回指定区间的视频数据。你的Django后端当前直接返回整个视频文件,既不处理Range请求,也不返回Accept-Ranges响应头,导致Chrome无法准确定位视频位置,只能从头播放。其他浏览器对无Range支持的场景做了降级处理,因此能正常工作。
一、修复Django后端,支持Range请求
修改GenericFileOperation的get方法,添加Range请求处理逻辑:
class GenericFileOperation(APIView): def get(self, request, material_type=None, course_id=None, format=None): key = request.GET.get('key') download = request.GET.get('download') if key is not None: s3_obj = download_assessment_file(key) splited_path = key.split("/") file_name = splited_path[-1] file_size = s3_obj['ContentLength'] mime_type, _ = mimetypes.guess_type(key) # 处理Range请求(跳过下载场景) range_header = request.META.get('HTTP_RANGE', '') if range_header and not download: # 解析Range头,格式示例:"bytes=0-1023" range_part = range_header.split('=')[-1] start_str, end_str = range_part.split('-') start = int(start_str) if start_str else 0 end = int(end_str) if end_str else file_size - 1 end = min(end, file_size - 1) # 从S3获取指定范围的内容 s3_range = f'bytes={start}-{end}' s3_obj_range = download_assessment_file(key, range=s3_range) content = s3_obj_range['Body'].read() content_length = end - start + 1 response = HttpResponse(content, content_type=mime_type, status=206) response['Content-Range'] = f'bytes {start}-{end}/{file_size}' response['Accept-Ranges'] = 'bytes' response['Content-Length'] = str(content_length) else: # 返回完整文件或处理下载请求 contents = s3_obj['Body'].read() temp = tempfile.TemporaryFile() temp.write(contents) temp.seek(0) response = HttpResponse(temp, content_type=mime_type) if download is not None: response['Content-Disposition'] = f"attachment; filename={file_name}" response['Accept-Ranges'] = 'bytes' response['X-Frame-Options'] = "*" return response
注意:需确保
download_assessment_file函数支持传递range参数调用S3的GetObject API,S3原生支持通过Range头获取文件片段。
二、优化React端Video.js代码,避免配置冲突
当前代码用innerHTML创建video元素,同时传递videoJsOptions,容易出现配置重复冲突。改为直接通过VideoJS API创建播放器:
import React, { useEffect, useRef } from 'react' import VideoJs from 'video.js' import 'video.js/dist/video-js.css'; import "videojs-hotkeys"; import 'videojs-seek-buttons' const videoJsOptions = { controls: true, autoplay: false, fluid: true, loop: false, playbackRates: [0.5, 1, 1.5, 2], aspectRatio: '12:5', html5: { vhs: { overrideNative: true }, nativeAudioTracks: false, nativeVideoTracks: false }, plugins: { seekButtons: { forward: 30, back: 10 }, hotkeys: {} } } const VideoPlayer = ({ url, fileType }) => { const videoContainerRef = useRef(null) const playerRef = useRef(null) useEffect(() => { // 销毁已有播放器 if (playerRef.current) { playerRef.current.dispose() } // 创建video元素并挂载 const videoElement = document.createElement('video') videoElement.classList.add('video-js') videoContainerRef.current.appendChild(videoElement) // 初始化播放器 const player = VideoJs(videoElement, videoJsOptions, () => { player.src({ src: url, type: fileType }) }) playerRef.current = player return () => { if (playerRef.current) { playerRef.current.dispose() playerRef.current = null } } }, [url, fileType]) return <div ref={videoContainerRef} /> } export default VideoPlayer
验证步骤
- 打开Chrome开发者工具的Network面板
- 播放视频并尝试seek,观察请求状态码是否为
206 Partial Content - 检查响应头是否包含
Accept-Ranges: bytes和Content-Range字段
内容的提问来源于stack exchange,提问作者theWorstCoder
相关产品推荐
相关产品推荐

