大伙儿今天又来跟大家唠唠嗑了。最近,我自个儿上手实践了一下用JS搞直播这个事儿,过程嘛有那么点意思,也有不少坑,今天就给大家伙儿从头到尾捋一捋我是咋折腾的。
准备工作,先搭个架子
我就寻思,直播嘛肯定得有个地儿能看,有个地儿能推流。咱用JS,那浏览器就是咱的主战场了。第一步,我先建了个特简单的HTML页面,就一个`
光有页面不行,还得有“服务员”帮忙传话,也就是后端。我寻思着用*搭个简单的后台,主要是为了处理信令,就是告诉张三李四要连线了,他们IP地址是啥之类的。这块儿我用了 `express` 框架,再配合 `ws` 这个库搞 WebSocket 通信。为啥用 WebSocket?因为它能保持长连接,实时性传信令方便。
我先是在我电脑上装了 * 环境,然后 `npm init` 初始化了个项目,接着 `npm install express ws` 把这俩哥们给装上了。写了段简单的代码,让 WebSocket 服务器先跑起来再说。
获取本地音视频,这是第一关
架子搭好了,接下来就得让浏览器能用咱的摄像头和麦克风。这玩意儿浏览器有现成的API,叫 `*()`。用起来也还行,就是得处理好用户授权的问题,人家不让你用,你可不能硬来。
我这么写的:
javascript
// 这段代码我就不放标签里了,方便看
async function getLocalMedia() {
try {
const stream = await *({ video: true, audio: true });
// 拿到视频流,就可以在video标签里播放了
const localVideo = *('localVideo'); // 假设我有个id为localVideo的video标签
* = stream;
// 把这个stream存起来,后面推流要用
// localStream = stream;
} catch (e) {
*('getUserMedia错误!', e);
alert('打不开你的摄像头或者麦克风!');
调用这个 `getLocalMedia` 函数,如果用户同意了,我本地的画面和声音就能在页面上那个 `
WebRTC 上场,建立连接是关键
本地画面能看了,接下来就是重头戏:怎么把这个画面传给别人,或者看别人的画面。这就得靠 WebRTC (Web Real-Time Communication) 这套东西了。这玩意儿复杂,但好在浏览器都内置支持了,不用咱自己从头写编解码、网络传输那么底层的东西。
WebRTC建立连接大概是这么个流程,我当时也是边查资料边摸索:
- 创建 `RTCPeerConnection` 对象:两边(比如主播端和观众端)都得创建一个这玩意儿,它就是负责P2P连接的大总管。
- 信令交换:这是最绕的一块儿。俩人想直接通话,得先通过一个“中间人”(就是我前面搭的 WebSocket 服务器)交换一些信息,比如:
- SDP (Session Description Protocol):描述媒体能力,比如用啥编解码器,分辨率多少等等。一方向另一方发送一个 "offer" (提议),另一方收到后回复一个 "answer" (应答)。
- ICE (Interactive Connectivity Establishment) 候选者:这玩意儿是用来打通NAT(网络地址转换)的,简单说就是找到俩人之间能通的路。双方会收集一堆自己的网络地址信息(叫ICE候选者),然后通过信令服务器交换给对方。
- 添加媒体流:主播端把自己本地的 `localStream` (就是 `getUserMedia` 拿到的那个流) 添加到 `RTCPeerConnection` 对象里。
- 接收远端媒体流:观众端监听 `RTCPeerConnection` 的 `ontrack` 事件,一旦收到远端的媒体轨道,就把它显示在另一个 `
我把这些逻辑都封装在不同的函数里,比如 `createPeerConnection()`、`sendOffer()`、`sendAnswer()`、`sendIceCandidate()` 等等。然后通过 WebSocket 把这些信令消息传来传去。
举个例子,主播端想推流:
- 创建 `RTCPeerConnection`。
- 把本地视频流 `addTrack` 进去。
- 创建 `offer`,然后通过 WebSocket 发给观众端。
- 监听 WebSocket 消息,等着观众端的 `answer` 和 ICE 候选者。
观众端想拉流:
- 创建 `RTCPeerConnection`。
- 监听 `ontrack` 事件,准备接收视频。
- 收到主播端的 `offer` 后,创建 `answer`,通过 WebSocket 发回去。
- 也收集自己的 ICE 候选者发给主播端。
踩过的坑和一些小细节
这过程里头,坑可不少。
第一个大坑就是 STUN/TURN 服务器。 如果俩人都在同一个局域网,那还好说。但只要隔了路由器,尤其是那种复杂的网络环境,P2P直连就很容易失败。这时候就需要 STUN 服务器来帮助发现公网IP和端口,或者 TURN 服务器来做中继转发。我一开始没配这个,或者配的公共 STUN 服务器不太稳定,连接成功率就很低。后来找了几个靠谱的 STUN 服务器地址配置到 `RTCPeerConnection` 的 `iceServers` 选项里,情况才好点。
第二个是信令逻辑的顺序。 offer/answer 的交换,ICE候选者的收集和交换,哪个先哪个后,哪个出了错,都可能导致连接建不起来。我当时就是对着浏览器控制台里的日志,一步步看是哪个消息没发出去,或者哪个消息对方没正确处理,调试了好半天。
还有就是异步操作。 `getUserMedia`、创建offer/answer这些都是异步的,得用 `async/await` 或者 `Promise` 好好处理,不然代码执行顺序乱了套,肯定出问题。
是UI交互。 比如,用户点了“开始直播”按钮,我得先去获取本地媒体,然后创建 `RTCPeerConnection`,然后开始走信令流程。这些状态的切换也得管理不能让用户瞎点。
最终效果和一点感想
折腾了好几天,总算是能在一个浏览器窗口点击“开始直播”,然后在另一个浏览器窗口(或者另一台电脑的浏览器)通过信令“加入”这个直播,看到第一个窗口的画面和声音了。虽然功能还很简陋,比如没有聊天室,没有多人连麦,没有录制,但起码核心的“直播”功能算是跑通了。
整个过程下来,感觉JS这玩意儿潜力还是挺大的。以前觉得直播这种东西肯定得用C++或者专门的流媒体服务器才能搞,没想到光靠浏览器和*也能搭出个雏形。真要做商业级别的直播,那稳定性、并发处理、安全性等等,要考虑的东西就海了去了,我这也就是个入门级的实践。
通过这回实践,我对 WebRTC 的理解深入了不少,也算是把以前零零散散看的一些资料给串起来了。以后有空的话,还想再给它加点功能,比如搞个简单的房间管理,或者试试屏幕共享啥的。今天就先分享到这儿,希望能给想自己动手试试的朋友一点小小的启发!
还没有评论,来说两句吧...