webRTC实践

您所在的位置:网站首页 web视频源码 webRTC实践

webRTC实践

2023-04-12 21:43| 来源: 网络整理| 查看: 265

笔者所在一家智能家居公司,安防camera业务组,负责核心的音视频直播、视频回放等业务员功能。原有播放器基于hybrid分层,由原生提供,js层进行调用。存在问题是性能消耗大,接口繁多,调用负责,难以维护、tutk+p2p连接不稳定、无法移植pc端等。so,由架构牵头最近开始了h5video流媒体播放器的研究,分享一下经验和体会。

webrtc流媒体服务器介绍

流媒体服务器的主要功能是以流式协议(RTP/RTSP、MMS、RTMP, webrtc等)将视频文件传输到客户端,供用户在线观看;也可从视频采集、压缩软件接收实时视频流,再以流式协议直播给客户端。

webrtc 流媒体服务器主要以webrtc协议为核心, 进行视频采集与播放.

以TURN/信令支持webrtc标准交互后, 就可以提供点对点直播, 设备带宽性能足够时也支持一对多(少量)直播. 如下图: 多方通信架构一般有三种方案:

Mesh 方案,即多个终端之间两两进行连接,形成一个网状结构。比如 A、B、C 三个终端进行多对多通信,当 A 想要共享媒体(比如音频、视频)时,它需要分别向 B 和 C 发送数据。同样的道理,B 想要共享媒体,就需要分别向 A、C 发送数据,依次类推。这种方案对各终端的带宽要求比较高。

MCU(Multipoint Conferencing Unit)方案,该方案由一个服务器和多个终端组成一个星形结构。各终端将自己要共享的音视频流发送给服务器,服务器端会将在同一个房间中的所有终端的音视频流进行混合,最终生成一个混合后的音视频流再发给各个终端,这样各终端就可以看到 / 听到其他终端的音视频了。实际上服务器端就是一个音视频混合器,这种方案服务器的压力会非常大。

SFU(Selective Forwarding Unit)方案,该方案也是由一个服务器和多个终端组成,但与 MCU 不同的是,SFU 不对音视频进行混流,收到某个终端共享的音视频流后,就直接将该音视频流转发给房间内的其他终端。它实际上就是一个音视频路由转发器。

webrtc 默认支持 Mesh架构. SFU是当前主流的方案,大多数厂商都采用SFU架构模型.

实现步骤 h5使用video标签初始化播放器。 import React, { memo } from 'react'; import { Button, Flex } from '@leedarson/ui-mobile'; import useWebRTC from './useWebRTC'; const WebRTCPlayer = memo(() => { const { handleStart, call, handleStop } = useWebRTC(); return ( START Call STOP ); }); export default WebRTCPlayer; 复制代码 使用websocket进行鉴权和流媒体传输,考虑安全性使用wss协议传输,即ssl+ws,类https。 import logger from '@leedarson/logger'; class WSWebRTC { constructor(url) { this.ws = new WebSocket(url); this.ws.conn_status = false; } init(localId, remoteId, processSignalingMessage) { this.ws.onopen = () => { const login = { clientId: localId }; this.ws.conn_status = true; this.ws.send(JSON.stringify(login)); }; this.ws.sendFormatMsg = obj => { const m = { peerId: remoteId, payload: obj }; const str = JSON.stringify(m); this.ws.send(str); }; this.ws.onmessage = event => { if (typeof event.data === 'string') { // 鉴权 processSignalingMessage(event.data); return; } if (event.data instanceof ArrayBuffer) { const buffer = event.data; logger.log('Received arraybuffer', buffer); this.ws.close(); } logger.log(`Received Message: ${event.data}`, typeof event.data); }; // 指定连接关闭后的回调函数 this.ws.onclose = () => { this.ws.conn_status = false; }; this.ws.onerror = event => { this.ws.conn_status = false; logger.error('webRTC_Error:', event); }; } } export default WSWebRTC; 复制代码 使用navigator.mediaDevices.getUserMedia方法获取流

该方法会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。需要注意在移动端设备上调用需要在https的基础之上,主要原因是浏览器基于安全考虑,防止视频流被攻击窃取。(pc端无此限制)笔者在app上调用时,由于代码打包在app本地使用 http://localhost:3000 加载,调用时该方法直接报错。需要注意这里https的限制时无法跳过的,笔者解决方案是将webrtc播放器相关的前端页面部署在https证书加密的服务器上远程调用来解决的。

const handleStart = useCallback(async () => { // 1.连接webSocket handleWSConnect(); try { // 2.获取视频流通道 const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true, }); localVideo.srcObject = stream; localStreamRef.current = stream; // 3.拉流播放 call(); } catch (error) { logger.error('读取权限or视屏流失败', error); } }, [call, handleWSConnect, localVideo]); 复制代码 使用webRTC接口RTCPeerConnection拉去视频流播放。 const onIceCandidate = useCallback( event => { if (event.candidate) { const msg = { candidate: event.candidate.candidate, sdpMLineIndex: event.candidate.sdpMLineIndex, sdpMid: event.candidate.sdpMid, }; if (answerReadyRef.current || localRole === 'Callee') wsClientRef.current.sendFormatMsg(msg, true); else { qRemoteCandidates.current.push(msg); } } }, [localRole], ); const handleAddStreamEvent = useCallback( event => { const { streams = [], stream } = event; if (callerRef.current.ontrack) { if (remoteVideo.srcObject !== streams[0]) { const stream0 = streams[0]; remoteVideo.srcObject = stream0; logger.log(` received remote stream`); } } else if (remoteVideo.srcObject !== stream) { remoteVideo.srcObject = stream; logger.log(` received remote stream`); } }, [remoteVideo], ); // 成功回调 const onCreateOfferSuccess = useCallback(desc => { callerRef.current.setLocalDescription(desc).then( () => { logger.log('successful'); }, error => logger.error('error', error), ); const msg = { type: desc.type, sdp: desc.sdp, }; wsClientRef.current.sendFormatMsg(msg, true); }, []); const call = useCallback(() => { // 1.验证wss连接 if (!wsClientRef.current || !wsClientRef.current.conn_status) { logger.error("call is failed case mqtt don't sub ok, please click call later!"); } // 2.创建连接 callerRef.current = new RTCPeerConnection(configuration); const caller = callerRef.current; caller.onicecandidate = onIceCandidate; caller.oniceconnectionstatechange = event => logger.log('ICE state change event: ', event); caller.ontrack = handleAddStreamEvent; if (localStreamRef.current){ localStreamRef.current.getTracks().forEach(track => { caller.addTrack(track, localStreamRef.current); }); } caller.createOffer(offerOptions).then(onCreateOfferSuccess, error => logger.log(error)); }, [configuration, handleAddStreamEvent, onCreateOfferSuccess, onIceCandidate]); 复制代码


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3