You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:通过Socket.IO将FFmpeg流从Python推送到浏览器显示视频

浏览器播放FFmpeg推送的视频流解决方案

一、调整Python后端脚本

原脚本的事件参数和推送逻辑有小问题,修改后可正确响应前端请求并推送流:

import socketio
import subprocess
import uvicorn

sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
app = socketio.ASGIApp(sio)

@sio.event
async def connect(sid, environ):
    await sio.enter_room(sid, 'Streaming')
    print(f"客户端 {sid} 已连接")

# 修改事件名称为start_stream,明确触发逻辑
@sio.event
async def start_stream(sid):
    video_path = r'temp_server\result.avi'
    audio_path = r'temp_server\temp.wav' 
    ffmpeg_command = [
            'ffmpeg',
            '-re',  # 按视频实际帧率推送
            '-i', video_path,
            '-i', audio_path,
            '-c:v', 'copy',
            '-c:a', 'aac',
            '-f', 'mpegts',
            '-mpegts_flags', 'resend_headers',  # 每次连接重发TS头,避免客户端无法初始化
            'pipe:1'
        ]
    try:
        # 同时捕获stderr方便排查FFmpeg错误
        process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        while True:
            data = process.stdout.read(4096)  # 增大读取块提升传输效率
            if not data:
                break
            # 推送到当前请求的客户端
            await sio.emit('video_chunk', data, to=sid)
    except Exception as e:
        print(f"推流错误: {e}")
    finally:
        process.stdout.close()
        process.stderr.close()
        process.wait()
        print("推流结束")

if __name__ == '__main__':
    try:
        uvicorn.run(app, host='0.0.0.0', port=8000)
    except KeyboardInterrupt:
        exit()

修改点说明:

  • 重命名事件为start_stream,更清晰表达前端触发逻辑
  • 添加-mpegts_flags resend_headers参数,确保客户端每次连接都能获取TS流头部,避免无法播放
  • 增大读取块大小到4096字节,优化传输效率
  • 完善进程资源清理和异常捕获

二、浏览器端HTML+JavaScript实现

创建index.html文件,通过socket.io-client连接后端,利用MediaSource API处理MPEG-TS流并播放:

<!DOCTYPE html>
<html>
<head>
    <title>FFmpeg流播放器</title>
    <style>
        video {
            width: 800px;
            height: auto;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <video id="videoPlayer" controls autoplay muted></video>

    <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
    <script>
        const video = document.getElementById('videoPlayer');
        const socket = io('http://localhost:8000');
        
        let mediaSource;
        let sourceBuffer;
        let bufferQueue = [];

        // 初始化媒体源
        function initMediaSource() {
            mediaSource = new MediaSource();
            video.src = URL.createObjectURL(mediaSource);
            
            mediaSource.addEventListener('sourceopen', () => {
                // MPEG-TS对应的MIME类型(适配H.264视频+AAC音频)
                const mimeCodec = 'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"';
                if (MediaSource.isTypeSupported(mimeCodec)) {
                    sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
                    sourceBuffer.mode = 'sequence'; // 按顺序追加数据
                    
                    // 处理缓冲队列中待加载的数据
                    while (bufferQueue.length > 0 && !sourceBuffer.updating) {
                        appendBuffer(bufferQueue.shift());
                    }
                } else {
                    console.error('不支持该MIME类型:', mimeCodec);
                }
            });
        }

        // 向媒体源追加数据
        function appendBuffer(buffer) {
            try {
                sourceBuffer.appendBuffer(buffer);
            } catch (e) {
                console.error('追加缓冲失败:', e);
            }
        }

        // 缓冲更新完成后继续处理队列
        mediaSource.addEventListener('updateend', () => {
            if (bufferQueue.length > 0 && !sourceBuffer.updating) {
                appendBuffer(bufferQueue.shift());
            }
        });

        // 连接成功后请求开始推流
        socket.on('connect', () => {
            console.log('已连接到流媒体服务器');
            initMediaSource();
            socket.emit('start_stream');
        });

        // 接收视频流数据
        socket.on('video_chunk', async (chunk) => {
            const buffer = await chunk.arrayBuffer();
            if (sourceBuffer && !sourceBuffer.updating) {
                appendBuffer(buffer);
            } else {
                bufferQueue.push(buffer);
            }
        });

        // 处理断开连接
        socket.on('disconnect', () => {
            console.log('与服务器断开连接');
            if (mediaSource) {
                mediaSource.endOfStream();
            }
        });
    </script>
</body>
</html>

前端代码说明:

  • 使用socket.io-client建立与后端的实时连接,监听video_chunk事件接收流数据
  • 通过MediaSource API创建可动态追加数据的媒体源,适配MPEG-TS格式
  • 维护缓冲队列,避免在sourceBuffer更新时直接追加数据导致错误
  • 视频设置muted是满足浏览器自动播放策略要求

三、运行步骤

  1. 安装Python依赖:pip install python-socketio uvicorn
  2. 确保FFmpeg已安装并添加到系统PATH
  3. 运行Python后端脚本
  4. 在浏览器中打开index.html,即可观看推送的视频流

内容的提问来源于stack exchange,提问作者sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 11:30:20