HTML5 是不是就真的没办法播放 FLV 等格式视频了呢?不是。解决方案是 MSE,Media Source Extensions,就是说,HTML5 不仅可以直接播放上面支持的 mp4、m3u8、webm、ogg 格式,还可以支持由 JS 处理过后的视频流,这样我们就可以用 JS 把一些不支持的视频流格式,转化为支持的格式(如 H.264 的 mp4)。B 站开源的 flv.js 就是这个技术的一个典型实现。B 站的 PC HTML5 播放器,就是用 MSE 技术,将 FLV 源用 JS 实时转码成 HTML5 支持的视频流编码格式(其实就一个文件头的差异(这里文件头改成容器。感谢评论区谦谦的指教,是容器的差异,容器不只是文件头)),提供给 HTML5 播放器播放。

原理:前端本不支持rtsp播放,这里是通过Node.js驱动ffmpeg进行后台转码FLV,并通过前端MSE技术渲染到video标签当中

核心指令:ffmpeg把rtsp转rmtp

(ffmpeg默认推流方式采用UDP方式,若需要使用TCP协议,则需要修改-rtsp_transport tcp,-c copy表示不经转码,直接进行流复制,在以前的ffmpeg里,“-c copy”是以“-vcodec copy -acodec copy”这种形式表示的。现在的ffmpeg不存在这个问题了,而且这两种都可以用)

ffmpeg -rtsp_transport tcp -i your_rtsp_url -c copy -f flv rtcmp://127.0.0.1:1935/live/

转rmtp需要nginx的nginx-rtmp-module模块,nginx.conf配置如下

rtmp {
    server {
        listen 1935;#监听端口,若被占用,可以更改
        chunk_size 4096;#上传flv文件块儿的大小
        application live { #创建一个叫live的应用
             live on;#开启live的应用
             allow publish 127.0.0.1;
             allow play all;
        }
    }
}

首先确保你已安装了ffmpeg以及websocket-stream、fluent-ffmpeg模块

后端:nodejs

const WebSocket =require( 'ws')
const webSocketStream =require( 'websocket-stream/stream')
const ffmpeg =require( 'fluent-ffmpeg')

// 建立WebSocket服务
const wss = new WebSocket.Server({ port: 9999, perMessageDeflate: false })

// 监听连接
wss.on('connection', handleConnection)

// 连接时触发事件
function handleConnection (ws, req) {
console.log('一个客户端连接进来啦')
  // 获取前端请求的流地址(前端websocket连接时后面带上流地址)
  const url = req.url.slice(1)
  // 传入连接的ws客户端 实例化一个流
  const stream = webSocketStream(ws, { binary: true })
  // 通过ffmpeg命令 对实时流进行格式转换 输出flv格式
  const ffmpegCommand = ffmpeg(url)
    .addInputOption('-rtsp_transport', 'tcp')
    .on('start', function () { console.log('Stream started.') })
    .on('codecData', function () { console.log('Stream codecData.') })
    .on('error', function (err) {
      console.log('An error occured: ', err.message)
      stream.end()
    })
    .on('end', function () {
      console.log('Stream end!')
      stream.end()
    })
    .outputFormat('flv').videoCodec('copy').noAudio()

  stream.on('close', function () {
    ffmpegCommand.kill('SIGKILL')
  })

  try {
    // 执行命令 传输到实例流中返回给客户端
    ffmpegCommand.pipe(stream)
  } catch (error) {
    console.log(error)
  }
}

前端:vue3

<template>
    <div class="streamer">
        <video :id="props.videoId" autoplay muted controls width="100%" height="100%"></video>
    </div>
</template>

<script setup>
    import flvjs from '@/static/js/flv.min'
    import {defineProps,onMounted,onBeforeUnmount,ref} from 'vue'
    let flvPlayer=ref(null)
    const props = defineProps({
        url:{
            type:String,
            required:true
        },
        videoId: {
            type: String,
            default: 'player'
        },
    })
    onMounted(()=>{
        console.log(props.videoId)
        const videoElement = document.getElementById(props.videoId)
        flvPlayer.value = flvjs.createPlayer({
            isLive: true,
            type: 'flv',
            url: 'ws://localhost:9999/'+props.url,
            enableWorker: true,//启用分离线程
            enableStashBuffer: false,//关闭IO隐藏缓冲区
            stashInitialSize: 128, // 减少首桢显示等待时长
            autoCleanupSourceBuffer: true, //自动清除缓存
        })
        flvPlayer.value.attachMediaElement(videoElement)
        flvPlayer.value.load()//加载
        setTimeout(()=>{
            flvPlayer.value.play();
        } ,1000);
    })
    onBeforeUnmount(()=>{
        if(flvPlayer.value){
            flvPlayer.value.destroy()
        }
    })
</script>