最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • webRTC实践---web视频直播

    正文概述 掘金(想要变强qwer)   2020-12-22   766

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

    webrtc流媒体服务器介绍

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

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

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

    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 (
        <>
          <video id="webRTC-player" autoPlay muted />
          <Flex>
            <Button width="30%" onClick={handleStart}>
              START
            </Button>
            <Button width="30%" onClick={call}>
              Call
            </Button>
            <Button width="30%" onClick={handleStop}>
              STOP
            </Button>
          </Flex>
        </>
      );
    });
    
    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]);
    

    起源地下载网 » webRTC实践---web视频直播

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元