背景

最近接到一个需求,前端需要接一段视频流播放,刚接到的时候感觉这不挺简单的吗,立刻祭出我的 video.js 大法,不过当我拿到后端发给我的视频流地址的时候 rtsp://video-stream/god 我有点蒙,不知道此为何物,但是我还是尝试着用 <video srd="rtsp://video-stream/god" /> 挣扎了一下,结果却是不尽如人意,随后我在官方仓库也是得到了认证:

1681443979 2.PNG

虽然 video.js 虐我千百遍,但是我依然待她如初恋,收藏起来,下次再用吧。那么 Web 端就彻底不能播放 RTSP 了吗?当然不是,Web 端可以播放,并且有很多方案,其中涉及到一些简单的但是收费的,还涉及到一些稍显复杂但是免费的,相信每一个程序员极客骨子里都是白嫖党,本文就不对收费方案进行介绍,给大家介绍几种免费的 Web 播放 RTSP 流的方案,并且最后每一个方案都给大家写了一个完整可运行的示例,大家按需使用。

关于 RTSP 流

比如我们熟知的海康威视以及大华等摄像头、监控设备供应商,他们的设备就是 RTSP 流,如果想要把监控视频接到 Web 端,那么就不可避免要解决这些问题,当然了,很多大厂或者供应商都有自己成熟的方案,但是如果是个人开发者或者小团队他们可能没接触过,遇到此类场景会没有头绪,本文就是基于此来写的,算是自己在开发过程中探索出来的前端可以独立 Cover 完成的。虽然其中会用到后端中转,但是都是很基础现成的 Nodejs 库,大家拿来即用就可。

这里笔者额外插一段垃圾话,网上方案千千万,没有示例早习惯,笔者一贯坚持的理念就是,如果你贴出代码来了,你就必须保证读者能执行,而不是因为环境或者代码不完整而体现自己的逼格,你既然是教学文章,那就要对读者负责。更有甚者给出所谓的方案还故意留问题,让读者付费咨询(我就不提姓名了)。别的我不敢保证,但是如果我提供的代码不能演示,你们在评论区也可以肆无忌惮地骂我,我接受~

解决方案

原理

在介绍详细的解决方案之前,需要先给大家介绍一下在 Web 播放 RTSP 流的原理,明白了原理之后我们就能知道为啥有付费方案了?其实所有的解决方案基本上都是基于一种原理来实现的,只不过解决方案多种多样,依赖的包和库有区别而已。

RTSP 流 -> Web 能接收的流(WebsocketWebRTC) -> Web Video

因此,介绍解决方案的时候,我们也分两部分去介绍。

  • RTSP 流 -> WebSocket / WebRTC
  • WebSocket / WebRTC -> WebVideo

这两部分大家可以自由组合,就会出现很多种不同的方案,最后的效果就是在 Web 端呈现 RTSP 流播放~言归正传,直接介绍两种 Web 播放 RTSP 推流的方案。

所以上面的结论就是,付费方案就是你不需要关注 RTSP -> Web 能接收的流的过程,它们用服务器帮你转换提供给你了,也就是你只需要关注 Web 端就可以了,而免费方案就是我们自己搭建一个转流服务,但是大家都是程序员,搭建也不复杂,为啥必须付费呢? 各位你们说是不是这个道理。

方案I - 基于 ffmpeg 的 Node 后端推流方案 + 基于 jspmeg / flvjs 的前端视频展示

后端部分:基于 ffmpeg 的 node 推流方案

这种方案应该是网上能搜索到的文章中最主流的方案,其中 Nodejs 部分可选择的包就有很多种:

上面几个包最后都能实现我们所要的效果,但是唯一都有一个前提条件:必须在系统里安装 FFMPEG,因为 RTSP 流转 WebSocket 就是通过底层调用 FFMPEG 来实现的。因为这些包都是经过封装后的,所以使用起来非常的简单,我这里就以第一个 node-rtsp-stream 为例,来说一下如可启动:

const Stream = require('node-rtsp-stream');

// Name of the stream, used to identify it in the API
new Stream({
  name: 'socket',
  streamUrl: 'rtsp://localhost:554/test',
  wsPort: 9999,
    // ffmpeg 的一些配置参数,比如转换分辨率等,大家可以去 ffmpeg 官网自行查询
  ffmpegOptions: {
    '-stats': '',
    '-r': 20,
    '-s': '1280 720'
  }
});

你没有看错,就是这么简单,你只需要把你要接入的 RTSP 流地址填写进去即可,然后在前端你需要展示的就是转换过后的 WebSocket 地址,前端如何显示,在下方,耐心观看。

关于本地可测试的 RTSP 推流的创建过程,可以参考本文最后章节 其他 - 创建本地 RTSP 推流

前端部分

上面后端的部分真的可谓是非常简单,那么我们前端也不能落后,其实都是站在巨人的肩膀上前行,前面也提到了,我们的 video 大法在这种场景不好用了,因此我们就得考虑别的方案,调研一圈发现了两个比较成熟且验证过后比较靠谱的方案,笔者一一给大家介绍使用姿势。

  • jsmpeg.js 或 jsmpeg-player

这两个包是一样的,只不过如果你是原生 HTML 你就用 jsmpeg.js,如果你用的 React/Vue 这种框架,你就用 jsmpeg-player,因为现在大部分前端都是使用框架进行开发,所以笔者直接用 jsmpeg-player 进行演示。

import React, { useEffect } from "react";
import JSMpeg from "@cycjimmy/jsmpeg-player";

export default function JsmpegPlayer() {

  useEffect(() => {
    // 根据你后端 RTSP 推流服务转的 WebSocket 地址修改
    new JSMpeg.VideoElement('#video', 'ws://localhost:9999');
  }, []);

  return (
    <div>
      <h1>Jsmpeg Player</h1>
      <div>
        <video id="video" width="640" height="480"></video>
      </div>
    </div>
  );
}

IMG_8314.GIF

  • flv.js

FLV 相关代码也是类似的,直接贴代码了:

import React, { useEffect, useRef } from 'react';
import flvjs from 'flv.js';

export default function FlvPlayer() {
  const flvRef = useRef(null);
  useEffect(() => {
    // 根据你后端 RTSP 推流服务转的 WebSocket 地址修改
    const videoElement = document.getElementById('videoElement');
    if (flvjs.isSupported()) {
      const flvPlayer = flvjs.createPlayer({
        type: 'flv',
        isLive: true,
        url: 'ws://localhost:9998',
      });
      flvPlayer.attachMediaElement(videoElement);
      flvPlayer.load();
      flvRef.current = flvPlayer;
      // flvPlayer.play();
    }
  }, []);

  return (
    <div>
      <h1>Flv.js Player</h1>
      <div style={{ width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
        <video id="videoElement" controls></video>
        <button onClick={() => {flvRef?.current?.play()}}>Play</button>
      </div>
    </div>
  );
}

关于后端是否运行成功,其实非常简单,你只需要看控制台是否在不断的进行服务推流即可,如下图所示:

1681443979.PNG

最后,flv.js 的实现效果如下图所示:

IMG_8315.GIF

体验下来,笔者这边发现,Flv.js 的播放体验要优于 jsmpeg.js,可能是内部做了优化的原因,也可能是转码的原因,具体是什么就没有深入,感兴趣的小伙伴或者大牛可以在评论区告诉我一下。

方案II - 基于 WebRTC 的推流方案

在第一种方案测试完成之后,我的需求其实已经写完了,但是大家在讨论优化点的时候不可避免地就加入了延迟这个关键指标,因为我们是将原有的 RTSP 流采用 FFMPEG 转换成了 WebScoket 然后在流给浏览器,虽然笔者对于视频底层和原理相关了解的不是很深,但是经过一层转换,肯定是会加大延迟的,所以就继续查相关资料,发现了第二种方案,并且面板数据上说延迟会低一些。因此我的需求虽然完结了,但是文章总是要精益求精,就来和大家继续探索一下。

这里就只给大家介绍一种基于 WebRTC 的实现方案,我相信肯定还有其他的解决方案,但是原理万变不离其宗,一种就足够了,先来看一下实现效果:

IMG_8316.GIF

如上图所示,可以看到前端浏览器页面也是直接就渲染了出来,但是细心的小伙伴可能发现了,没错,前端使用这种方式进行 RTSP 流的推送依然需要额外的一些依赖,也就是要额外启动一些应用才可以。

应用依赖

基于 WebRTC 实现 RTSP 流的推送,需要优先下载 webrtc-streamer,官方提供了各种系统各种版本的安装包,大家下载安装即可。

WebRTC Streamer 是用于 V4L2 捕获设备、RTSP源 和屏幕捕获的 WebRTC 流媒体。**关于这个东西底层原理,其实大家不需要了解,笔者也不知道。我们只需要知道它能捕获 RTSP 流在前端使用播放出来就可以了。

前端部分

前端代码看起来比较复杂,但是实际上都是复制粘贴,因为核心代码片段 WebRTC Streamer 官方已经给我们提供了足够多的基础组件,笔者在这里就以其中一个为例展示。

<html>
<head>
	<script type="module" src="./js/webrtc-streamer-element.js"></script>
	<style>
		h2 {
			margin: 0 auto;
			text-align: center; 
		}
	</style>
</head>
<body>
	<h2 id="message"></h2>
	<webrtc-streamer id="stream" options="rtptransport=tcp&timeout=60&width=0&height=0&bitrate=0&rotation=0" style="display:none"></webrtc-streamer>
	<p id="hint">默认从url连接上获取 video || audio 参数进行播放,本示例如果没有会选择本地 RTSP 推流</p>
	<p style="color: red">【注意】:WebRTC 播放 RTSP 流需要前置启动 webrtc-streamer 客户端,否则推流无法播放</p>
	<script>     
		let messageElement = document.getElementById("message"); 
        customElements.whenDefined('webrtc-streamer').then(() => {
            let streamElement = document.getElementById("stream");

			var params = new URLSearchParams(location.search);
			if (params.has("options")) {
				streamElement.setAttribute('options', params.get("options"));
			}
			let url = {
				video:params.get("video") || 'rtsp://localhost:554/test',
			};
			streamElement.setAttribute('url', JSON.stringify(url));
			streamElement.style.display = "block"
		}).catch( (e) => {
			messageElement.innerText = "webrtc-streamer webcomponent fails to initialize error:" + e
		})
	</script>
</body>
</html>

更详细的代码已经存放到仓库,大家可以 clone 下来运行一下,需要注意的就是要想能看到正常播放视频,要满足两个条件:

  • 必须启动 WebRTC Streamer
  • 必须有一个可用的 RTSP 推流(关于本地可测试的 RTSP 推流的创建过程,可以参考本文最后章节 其他 - 创建本地 RTSP 推流。)

其他

创建本地 RTSP 推流

前面给大家介绍了纯前端(Nodejs 在我看来也算是前端了)实现 RTSP 推流的播放,但是我们在开发和测试过程中,并没有办法一直获得一个稳定可用的 RTSP 推流服务,基于此笔者再给大家介绍一下如何在本地创建一个稳定可用的 RTSP 推流服务。

上面的软件安装非常的简单,安装过程我就不过多介绍了,我简单介绍一下安装他们都是用来干啥的。

工具 说明
FFMPEG 推流工具,可以将本地视频(比如.mp4)转换成 RTSP 流
EasyDarwin 推流服务,启动一个本地 RTSP 流的服务,用于前端访问
VLC 流播放器,可以测试本地 RTSP 流服务是否启动成功,是否可以播放
  • 第二步:启动 EasyDarwin,如下图所示即启动成功

1681443980 2.PNG

  • 第三步:使用 FFMPEG 将本地视频转成 RTSP 流
ffmpeg -re -stream_loop -1 -i D:\videos\test.mp4 -rtsp_transport tcp -vcodec h264 -f rtsp rtsp://localhost/test

其中几个比较重要的参数如下:

# -stream_loop 循环推流,避免播放完推流就断了
# D:\videos\test.mp4 替换成你本地的视频文件即可
# rtsp://localhost/test 推流服务地址
  • 第四步:在 VLC 播放器测试是否可以播放(媒体 -> 打开网络串流)

如下图所示,本地 RTSP 推流服务创建成功,至此你就可以在 Web 端进行上面所有方案的测试了。

1681443980.PNG

注意:EasyDarwin 默认会将推流服务启动在 554 端口,因此 VLC 添加网络串流的时候,记得输入端口号。

Demo 测试仓库

因为 RTSP 推流的使用场景一般都是监控/无人机场景,所以前端如果想测试还挺麻烦的,网上的本地推流参考文献也比较麻烦,为了让有类似需求的新手能够快速进行测试,我这边写了一个仓库 web-rtsp-video并且把上面介绍的所有的 Demo 都放到了仓库里,各位 clone 下来直接运行就可以了~

总结

上面两种方案均是借助了业界比较成熟的技术来实现的,无论是 FFMPEG 还是 WebRTC Streamer,在使用上都有一定的局限性,并没有绝对的优劣势之分,经过测试 WebRTC 方案的延迟确实要比 FFMPEG 要小一些但是也没有到天差地别,因此大家在选择的时候根据自己的实际场景按需使用就行了。

文章看到最后,希望能帮助大家答疑解惑并且真实的给大家解决问题,这就是我写文章的初衷了~

Web 播放 RTSP 推流最佳方案实践
标签: