精华内容
下载资源
问答
  • WebRTC 入门与实战教程 | 掘金技术征文
    千次阅读
    2019-04-23 13:00:49

    之前公司准备用 webRTC 来实现视频聊天,研究了几天,撸了个 demo 出来,(虽然最后并没有采用这项技术,囧),听说掘金最近在搞 webRTC 有奖征文活动,自不量力一下,硬着头皮写个教程吧,顺便也给自己整理下思路。

    WebRTC简单介绍

    WebRTC (Web Real-Time Communication) 是一个可以用在视频聊天,音频聊天或P2P文件分享等Web App中的 API。-MDN

    目前这项Web技术支持的浏览器有chrome, firefox和safari。

    WebRTC 有三个主要的API

    • getUserMedia - 采集本地音频和视频流
    • RTCPeerConnection - 用来创建对端连接并传输音视频的API
    • RTCDataChannel - 用于传输二进制数据。

    这三个API里面涉及了很多知识,要想全面细致的了解这项技术,建议去看官网,这篇教程只会介绍重要的知识点并且以此为前提,写一个聊天室和多人视频聊天的demo

    直播软件(比如斗鱼直播,熊猫直播等)是在客户端采集和编码主播的音频和视频,传输给流媒体服务器,流媒体服务器将媒体数据转发出去,客户端收到视频流进行解码和播放 架构简化图

    WebRTC提供端对端的音视频通讯,不需要媒体服务器转发媒体数据,架构简化图如下,

    其中每个相互连接的客户端叫做对等端

    WebRTC采集和传输音视频数据的过程可以分为三步进行

    1. 实时捕获本地的音视频流
    2. 实时编码音视频并在网络中向对等端传输多媒体数据
    3. 对等端接受发送者的音视频,实时解码播放

    捕获本地媒体流

    这一步非常简单,调用navigator.getUserMedia这个api就可以,第一个参数是音视频的限制条件,第一个参数是捕获媒体流成功的回调,回调参数是stream, 第三个参数是捕获失败的回调,在成功的回调函数种将stream赋值给video的srcObject即可显示视频

    示例代码如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <div>
            <button id="start">开始录制</button>
            <button id="stop">停止录制</button>
        </div>
        <div>
            <video autoplay controls id="stream"></video>
        </div>
        <script>
            // 只获取视频
            let constraints = {audio: false, video: true}; 
            let startBtn = document.getElementById('start')
            let stopBtn = document.getElementById('stop')
            let video = document.getElementById('stream')
            startBtn.onclick = function() {
                navigator.getUserMedia(constraints, function(stream) {
                    video.srcObject = stream;
                    window.stream = stream;
                }, function(err) {
                    console.log(err)
                })
            }
            stopBtn.onclick = function() {
                video.pause();
            }
        </script>
    </body>
    </html>
    复制代码

    本地视频已经以采集和播放了,接下来要解决如何和对方进行视频会议,也就是实时传输音视频,WebRTC是提供端对端的音视频传输,也就是封装好的RTCPeerConnection这个API,调用这个API可以创建对等端连接并传输音视频,但是光有这个api还不够,在传送音视频数据之前,我们需要先传输信令

    什么是信令

    信令是协调通信的过程。为了使WebRTC应用程序能够建立一个"通话",其客端户需要交换以下信息:

          会话控制消息用于打开或关闭通信
    
          错误消息
    
          媒体元数据,如编解码器和编解码器设置,带宽和媒体类型
    
          密钥数据,用于建立安全的连接
    
          网络数据,如主机IP地址和端口
    复制代码

    这个信令过程需要一个方式使客户端之间来回地进行消息传递, WebRTC标准并没有规定信令方法和协议,我们可以采用JavaScript会话建立协议JSEP来实现信令的交换,这里用一个例子来说明这个过程,假设A和B之间要建立RTCPeerConnection连接

         1. A创建一个RTCPeerConnection对象。
    
         2. A使用RTCPeerConnection .createOffer()方法产生一个offer(一个SDP会话描述)。
    
         3. A用生成的offer调用setLocalDescription(),设置成自己的本地会话描述。
    
         4. A将offer通过信令机制发送给B。
    
         5. B用A的offer调用setRemoteDescription(),设置成自己的远端会话描述,以便他的RTCPeerConnection知道A的设置。
    
         6. B调用createAnswer()生成answer
    
         7. B通过调用setLocalDescription()将其answer设置为本地会话描述。
    
         8. B然后使用信令机制将他的answer发回给A。
    
         9. A使用setRemoteDescription()将B的应答设置为远端会话描述。
    复制代码

    A和B除了交换会话信息,还需要交换网络信息。"查找候选项"是指使用ICE框架查找网络接口和端口的过程。

         1. A使用onicecandidate创建一个RTCPeerConnection对象,注册一个处理器函数。
    
         2. 处理器在网络候选变得可用时被调用。
    
         3. 在处理器中,A通过信令通道将候选数据发送给B。
    
         4. 当B从A那里获得候选消息时,调用addIceCandidate(),将候选项添加到远端对等描述中。
    复制代码

    稳住,别晕,我们用代码来说明这个过程吧,交换信令需要信令服务器,这个我们晚点讨论,我们现在考虑同一个页面上的两个RTCPeerConnection对象,一个代表本地,一个代表远端, 来说明信令交换和网络信息交换的过程和细节。 这个是官方文档的demo,第五个实验室教程, 能够科学上网的小伙伴建议直接去看教程,由浅入深地教你搭建WebRTC项目,在这里借用这个demo来解释建立RTCPeerConnection连接建立的过程

    首先建立index.html

    html结构如下:

     <!DOCTYPE html>
     <html>
    
         <head>
             <title>Realtime communication with WebRTC</title>
             <style>
                 body {
                     font-family: sans-serif;
                 }
         
                 video {
                     max-width: 100%;
                     width: 320px;
                 }
             </style>
         </head>
    
         <body>
             <h1>Realtime communication with WebRTC</h1>
         
             <video id="localVideo" autoplay playsinline></video>
             <video id="remoteVideo" autoplay playsinline></video>
         
             <div>
                 <button id="startButton">开始</button>
                 <button id="callButton">调用</button>
                 <button id="hangupButton">挂断</button>
             </div>
             <script src="./main.js">
             </script>
     </body>
    
    </html>
    复制代码

    一个video元素显示通过getUserMedia()方法获取的本地视频流,另一个显示通过 RTCPeerconnection传输的视频流,这里是同样的视频流,但是在真实的应用中,一个video显示本地视频流,另一个显示远端视频流

    main.js代码如下

        'use strict';
        // 传输视频,不传输音频
        const mediaStreamConstraints = {
          video: true,
          audio: false
        };
        
        // 设置只交换视频
        const offerOptions = {
          offerToReceiveVideo: 1,
        };
        
        let startTime = null;
        
        // 设置两个video,分别显示本地视频流和远端视频流
        const localVideo = document.getElementById('localVideo');
        const remoteVideo = document.getElementById('remoteVideo');
        
        let localStream;
        let remoteStream;
        // 建立两个对等连接对象,分表代表本地和远端
        let localPeerConnection;
        let remotePeerConnection;
    
    
    
    function gotLocalMediaStream(mediaStream) {
      localVideo.srcObject = mediaStream;
      localStream = mediaStream;
      trace('Received local stream.');
      callButton.disabled = false; 
    }
    
    function handleLocalMediaStreamError(error) {
      trace(`navigator.getUserMedia error: ${error.toString()}.`);
    }
    
    function gotRemoteMediaStream(event) {
      const mediaStream = event.stream;
      remoteVideo.srcObject = mediaStream;
      remoteStream = mediaStream;
      trace('Remote peer connection received remote stream.');
    }
    
    function logVideoLoaded(event) {
      const video = event.target;
      trace(`${video.id} videoWidth: ${video.videoWidth}px, ` +
            `videoHeight: ${video.videoHeight}px.`);
    }
    
    function logResizedVideo(event) {
      logVideoLoaded(event);
      if (startTime) {
        const elapsedTime = window.performance.now() - startTime;
        startTime = null;
        trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`);
      }
    }
    
    localVideo.addEventListener('loadedmetadata', logVideoLoaded);
    remoteVideo.addEventListener('loadedmetadata', logVideoLoaded);
    remoteVideo.addEventListener('onresize', logResizedVideo);
    
    
    function handleConnection(event) {
      const peerConnection = event.target;
      const iceCandidate = event.candidate;
    
      if (iceCandidate) {
        const newIceCandidate = new RTCIceCandidate(iceCandidate);
        const otherPeer = getOtherPeer(peerConnection);
    
        otherPeer.addIceCandidate(newIceCandidate)
          .then(() => {
            handleConnectionSuccess(peerConnection);
          }).catch((error) => {
            handleConnectionFailure(peerConnection, error);
          });
    
        trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
              `${event.candidate.candidate}.`);
      }
    }
    
    function handleConnectionSuccess(peerConnection) {
      trace(`${getPeerName(peerConnection)} addIceCandidate success.`);
    };
    
    function handleConnectionFailure(peerConnection, error) {
      trace(`${getPeerName(peerConnection)} failed to add ICE Candidate:\n`+
            `${error.toString()}.`);
    }
    
    function handleConnectionChange(event) {
      const peerConnection = event.target;
      console.log('ICE state change event: ', event);
      trace(`${getPeerName(peerConnection)} ICE state: ` +
            `${peerConnection.iceConnectionState}.`);
    }
    
    function setSessionDescriptionError(error) {
      trace(`Failed to create session description: ${error.toString()}.`);
    }
    
    function setDescriptionSuccess(peerConnection, functionName) {
      const peerName = getPeerName(peerConnection);
      trace(`${peerName} ${functionName} complete.`);
    }
    
    function setLocalDescriptionSuccess(peerConnection) {
      setDescriptionSuccess(peerConnection, 'setLocalDescription');
    }
    
    function setRemoteDescriptionSuccess(peerConnection) {
      setDescriptionSuccess(peerConnection, 'setRemoteDescription');
    }
    
    function createdOffer(description) {
      trace(`Offer from localPeerConnection:\n${description.sdp}`);
    
      trace('localPeerConnection setLocalDescription start.');
      localPeerConnection.setLocalDescription(description)
        .then(() => {
          setLocalDescriptionSuccess(localPeerConnection);
        }).catch(setSessionDescriptionError);
    
      trace('remotePeerConnection setRemoteDescription start.');
      remotePeerConnection.setRemoteDescription(description)
        .then(() => {
          setRemoteDescriptionSuccess(remotePeerConnection);
        }).catch(setSessionDescriptionError);
    
      trace('remotePeerConnection createAnswer start.');
      remotePeerConnection.createAnswer()
        .then(createdAnswer)
        .catch(setSessionDescriptionError);
    }
    
    function createdAnswer(description) {
      trace(`Answer from remotePeerConnection:\n${description.sdp}.`);
    
      trace('remotePeerConnection setLocalDescription start.');
      remotePeerConnection.setLocalDescription(description)
        .then(() => {
          setLocalDescriptionSuccess(remotePeerConnection);
        }).catch(setSessionDescriptionError);
    
      trace('localPeerConnection setRemoteDescription start.');
      localPeerConnection.setRemoteDescription(description)
        .then(() => {
          setRemoteDescriptionSuccess(localPeerConnection);
        }).catch(setSessionDescriptionError);
    }
    
    const startButton = document.getElementById('startButton');
    const callButton = document.getElementById('callButton');
    const hangupButton = document.getElementById('hangupButton');
    callButton.disabled = true;
    hangupButton.disabled = true;
    
    function startAction() {
      startButton.disabled = true;
      navigator.getUserMedia(mediaStreamConstraints, gotLocalMediaStream, handleLocalMediaStreamError)
      trace('Requesting local stream.');
    }
    // 创建对等连接
    function callAction() {
      callButton.disabled = true;
      hangupButton.disabled = false;
    
      trace('Starting call.');
      startTime = window.performance.now();
    
      const videoTracks = localStream.getVideoTracks();
      const audioTracks = localStream.getAudioTracks();
      if (videoTracks.length > 0) {
        trace(`Using video device: ${videoTracks[0].label}.`);
      }
      if (audioTracks.length > 0) {
        trace(`Using audio device: ${audioTracks[0].label}.`);
      }
      // 服务器配置
      const servers = null; 
    
      localPeerConnection = new RTCPeerConnection(servers);
      trace('Created local peer connection object localPeerConnection.');
    
      localPeerConnection.addEventListener('icecandidate', handleConnection);
      localPeerConnection.addEventListener(
        'iceconnectionstatechange', handleConnectionChange);
    
      remotePeerConnection = new RTCPeerConnection(servers);
      trace('Created remote peer connection object remotePeerConnection.');
    
      remotePeerConnection.addEventListener('icecandidate', handleConnection);
      remotePeerConnection.addEventListener(
        'iceconnectionstatechange', handleConnectionChange);
      remotePeerConnection.addEventListener('addstream', gotRemoteMediaStream);
    
      localPeerConnection.addStream(localStream);
      trace('Added local stream to localPeerConnection.');
    
      trace('localPeerConnection createOffer start.');
      localPeerConnection.createOffer(offerOptions)
        .then(createdOffer).catch(setSessionDescriptionError);
    }
    function hangupAction() {
      localPeerConnection.close();
      remotePeerConnection.close();
      localPeerConnection = null;
      remotePeerConnection = null;
      hangupButton.disabled = true;
      callButton.disabled = false;
      trace('Ending call.');
    }
    
    startButton.addEventListener('click', startAction);
    callButton.addEventListener('click', callAction);
    hangupButton.addEventListener('click', hangupAction);
    
    function getOtherPeer(peerConnection) {
      return (peerConnection === localPeerConnection) ?
          remotePeerConnection : localPeerConnection;
    }
    
    function getPeerName(peerConnection) {
      return (peerConnection === localPeerConnection) ?
          'localPeerConnection' : 'remotePeerConnection';
    }
    
    function trace(text) {
      text = text.trim();
      const now = (window.performance.now() / 1000).toFixed(3);
      console.log(now, text);
    }
    复制代码

    好吧,很长,我们来一起看看main.js到底做了什么

    WebRTC使用RTCPeerConnection这个API来建立连接,在客户端(也就是我们所说的端对端中的端)之间传输媒体流,在这个例子中,两个RTCPeerConnection对象在同一个页面,这个例子没有什么实际的用处,但是却可以很好地说明这个API的工作流程

    建议两个WebRTC用户之间的连接涉及三个任务:

    每一个调用需要为每个端创建一个RTCPeerConnection对象
    获取和分享网络信息:可能的连接端点,也就是ICE候选
    获取和分享本地和远端的描述:SDP格式的本地媒体的元信息
    复制代码

    假设A和B想通过RTCPeerConnection建立视频一个聊天室

    首先,A和B交换网络信息,查找候选项就是使用ICE框架查找网络接口和端口的过程

    A创建一个RTCPeerConnection对象,绑定一个onicecandidate事件的处理器(addEventListener('icecandidate')),这个过程对应main.js下面的代码:

        let localPeerConnection;
        // servers在这个例子中并没有使用,servers是配置STUN and TURN s服务器的,后面我们会介绍这个两种服务器
        localPeerConnection = new RTCPeerConnection(servers);
        localPeerConnection.addEventListener('icecandidate', handleConnection);
        localPeerConnection.addEventListener('iceconnectionstatechange', handleConnectionChange);
    复制代码

    WebRTC是为了直接端对端而设计的,让用户可以使用最直接的路由进行连接,而不需要经过服务器,然而,WebRTC 需要处理真实世界中的网络:客户端应用需要穿透NAT和防火墙,端对端直接连接失败的时候需要回退处理,在这个过程,WebRTC使用STUN服务器来获取IP,TURN服务器来作为回退服务器以免端对端连接失败

    A 调用getUserMedia()获取视频流,然后addStream(localPeerConnection.addStream):

    navigator.getUserMedia(mediaStreamConstraints, gotLocalMediaStream, handleLocalMediaStreamError).
    function gotLocalMediaStream(mediaStream) {
      localVideo.srcObject = mediaStream;
      localStream = mediaStream;
      trace('Received local stream.');
      callButton.disabled = false;  // 采集到了视频流,可以开始调用了
    }
    localPeerConnection.addStream(localStream);
    trace('Added local stream to localPeerConnection.');
    复制代码

    当网络候选项(candidate)变得可用的时候,onicecandidate的处理器会被调用 A发送序列化的数据给B, 在真实的应用中,这个过程(交换信令)需要一个信令服务器, 当然,在我们这个例子中, 两个RTCPeerConnection在同一个页面中, 可以直接沟通,不需要额外的信令传输 当B获取到A的candidate信息的时候, 他会调用addIceCandidate, 将candidate添加到候选中

    function handleConnection(event) {
      const peerConnection = event.target;
      const iceCandidate = event.candidate;
    
      if (iceCandidate) {
        const newIceCandidate = new RTCIceCandidate(iceCandidate);
        const otherPeer = getOtherPeer(peerConnection);
    
        otherPeer.addIceCandidate(newIceCandidate)
          .then(() => {
            handleConnectionSuccess(peerConnection);
          }).catch((error) => {
            handleConnectionFailure(peerConnection, error);
          });
    
        trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
              `${event.candidate.candidate}.`);
      }
    }
    复制代码

    WebRTC的对等端也需要找出并交换本地和远程的音视频媒体信息, 比如编解码能力,使用会话描述协议也就是SDP,通过交换元数据,也就是offer和answer来达到交换媒体配置信息的目的

    A运行RTCPeerConnection的createOffer()方法,返回一个promise,提供了一个RTCSessionDescription: A的本地会话描述

    trace('localPeerConnection createOffer start.');
    localPeerConnection.createOffer(offerOptions)
      .then(createdOffer).catch(setSessionDescriptionError);
    复制代码

    如果Promise成功了,A通过setLocalDescription方法将promise成功返回的描述设置成自己的本地描述,并且将描述通过信令通道发送给B, B使用setRemoteDescription将A发送给他的描述设置成自己的远端描述,B运行RTCPeerConnection的createAnswer()方法,可以产生一个相匹配的描述,B将自己产生的描述设置为本地描述并且发送给A, A将其设置成自己的远端描述

    function createdOffer(description) {
      trace(`Offer from localPeerConnection:\n${description.sdp}`);
    
      trace('localPeerConnection setLocalDescription start.');
      localPeerConnection.setLocalDescription(description)
        .then(() => {
          setLocalDescriptionSuccess(localPeerConnection);
        }).catch(setSessionDescriptionError);
    
      trace('remotePeerConnection setRemoteDescription start.');
      remotePeerConnection.setRemoteDescription(description)
        .then(() => {
          setRemoteDescriptionSuccess(remotePeerConnection);
        }).catch(setSessionDescriptionError);
    
      trace('remotePeerConnection createAnswer start.');
      remotePeerConnection.createAnswer()
        .then(createdAnswer)
        .catch(setSessionDescriptionError);
    }
    
    function createdAnswer(description) {
      trace(`Answer from remotePeerConnection:\n${description.sdp}.`);
    
      trace('remotePeerConnection setLocalDescription start.');
      remotePeerConnection.setLocalDescription(description)
        .then(() => {
          setLocalDescriptionSuccess(remotePeerConnection);
        }).catch(setSessionDescriptionError);
    
      trace('localPeerConnection setRemoteDescription start.');
      localPeerConnection.setRemoteDescription(description)
        .then(() => {
          setRemoteDescriptionSuccess(localPeerConnection);
        }).catch(setSessionDescriptionError);
    }
    复制代码

    搞定!

    最终demo

    信令服务器

    信令服务器也是web服务器,只是传输的不是普通的数据,而是信令,你可以根据自己的喜好选择服务器(如 Apache,Nginx 或 Nodejs), 这里使用node和搭配socket.io来搭建信令服务器

    socket.io

    官网,使用socekt.io可以非常容易的创建聊天室 用socket.io当信令服务器,后端是node, 前端是vue框架,搭建了一个聊天室,并且可以向聊天室的人发起视频聊天的demo,STUN是用的谷歌提供的免费服务器,TURN是我们公司的TURN服务器,这只是为了让demo可以运行,真实的项目需要自己搭建STUN和TRUN服务器,github项目地址

    • 克隆项目
    • npm install 安装依赖
    • node run start在本地的9000端口启动后端服务器
    • npm run dev在8080端口开启前端项目
    • 访问http://localhost:8080访问项目,输入用户名和密码,进入聊天室,在用户列表里面可以选择其他用户进行视频聊天(可以通过再开一个窗口,再输入用户名和密码来模拟多人)

    图示说明:打开谷歌浏览器,开三个标签页,分别创建A,B, C三个用户,点击加入聊天室后点击开始采集本地视频, A, B, C的页面如下,

    • A

    • B
    • C

    A页面切换到用户列表,可以看到有A,B和C三个用户,

    点击B后面的互动按钮,给B发送视频互动请求

    切换到B页面,发现B收到了A的互动邀请,点击接受

    回到A页面,可以看到本人A和和A互动的用户B

    再点击和C互动,C接受互动邀请,那么A现在就可以看到,A,B,C三路视频了,同理,B现在看到A和B两路视频,C看到C和A两路视频,你也可以让B用户和C用户进行互动,这样就是一个三人的视频会议了

    此时, A页面如下

    注意: 这个项目启动了两个本地node服务器,socket.io的访问地址是后端服务器也就是http://localhost:9000, 如果需要部署到线上,需要将src/APP.vue的joinRoom的方法中的socket的url替换成线上服务器的地址,

    转载于:https://juejin.im/post/5cb81e7e6fb9a068985fb24e

    更多相关内容
  • webrtc入门与实战视频培训课程是通过作者多年经验总结出的一套webrtc入门教程,学完此课程,你能搭建出一套android互通或者web互通或者android对web互通的webrtc服务器,此课程由浅入深讲解了从编译到完整搭建一套...
  • webrtc入门资源

    2016-02-02 21:39:46
    WebRTC,名称源自网页实时通信(Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的技术,是谷歌2010年以6820万美元收购Global IP Solutions公司而获得的一项技术。
  • webrtc 入门第一章 基本设备操作

    千次阅读 2022-02-24 10:46:39
    webrtc 入门第一章 基本设备访问 一、介绍 1、webrtc是什么 webrtc是一个由google发起的开源实时通信方案,其中包括视频/音频采集、编解码、数据传输、音视频展示的功能。在浏览器,桌面应用,移动设备或者lot设备上...

    一、介绍

    1、webrtc是什么

    webrtc是一个由google发起的开源实时通信方案,其中包括视频/音频采集、编解码、数据传输、音视频展示的功能。在浏览器,桌面应用,移动设备或者lot设备上都有可以运行的api接口,均可实现实时通信能力。web开发者可以基于web api开发基于视频、音频的实时通信应用,如视频会议,远程教育,视频通话,视频直播,游戏直播,远程协助,互动游戏,实时人脸识别等功能。

    2、优点是什么

    webrtc主要应用在实时通信方面,其优点总结为如下几点。

    1、跨平台:可以在Web,Android,iOS,Windows,MacOS、Linux等环境下运行

    2、实时传输:传输速度快,低延迟,适合实时性要求较高的场景

    3、音视频引擎:有强大的音视频处理能力

    4、免插件:不需要安装任何插件,兼容性高,打开浏览器即可使用,并且免费开源

    5、强大的打洞能力:webrtc技术包含了使用STUN、ICE、TURN、RTP-over-TCP的关键NAT和防火墙穿透技术

    本次将通过webrtc实现一对一视频对话的功能,首先要进行本地媒体资源的操作

    二、实践

    1、打开摄像头
    <body>
      <input type="button" title="开启摄像头" value="开启前置摄像头" v-on:click="getMedia(0)"/>
      <input type="button" title="开启摄像头" value="开启后置摄像头" v-on:click="getMedia(1)"/>
       <video height="120px" autoplay="autoplay" :src="video_1_url" crossOrigin="anonymous" muted="muted" controls></video>
        <hr/>
     </body>
      <script src="/static/js/Vue.2.5.3.js"></script>
    <script type="text/javascript"> 
     // 兼容版本
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
        window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
    getMedia: function (a) {
        let that = this;
        if (that.exArray.length <= a) {
            alert("设备不存在")
            return
        }
    
    
        if (navigator.getUserMedia) {
        	// 获取用户媒体数据,
            navigator.getUserMedia({
                'video': {
                    'optional': [{
                        'sourceId': that.exArray[a] //0为前置摄像头,1为后置
                    }]
                },
                'audio': true // 不需要音频的话为false
            }, that.successFunc, that.errorFunc); //success是获取成功的回调函数,也可以使用await来处理异步
        } else {
            alert('Native device media streaming (getUserMedia) not supported in this browser.');
        }
    },
            // 摄像头加载成功
                successFunc: function (stream) {
                    if (this.video.mozSrcObject !== undefined) {
                        //Firefox中,video.mozSrcObject最初为null,而不是未定义的,我们可以靠这个来检测Firefox的支持
                        this.video.mozSrcObject = stream;
                    } else {
                    	// 将流媒体复制给video标签进行播放 
                        this.video.srcObject = stream 
                    }
                    this.stream = stream
    
    
                    // 获取track  视频轨道
                    let deviceInfo = stream.getVideoTracks()
                    let tracks = deviceInfo[0]
                    // 可以对轨道进行操作
                    console.log(tracks.getConstraints())
                    this.deviceName = deviceInfo[0].label
                    this.video.play();
    
    
                    // 音频
                    this.audio = new Audio();
                    this.audioType = this.getAudioType(this.audio);
                    if (this.audioType) {
                        this.audio.src = 'polaroid.' + this.audioType;
                        this.audio.play();
                    }
                }
                 loadDevice: function () {
                    let that = this;
    
                    navigator.mediaDevices.enumerateDevices()
                        .then(function (sourceInfos) {
                                // 获取设备列表
                                console.log(sourceInfos)
                                for (let i = 0; i != sourceInfos.length; ++i) {
                                    let sourceInfo = sourceInfos[i];
                                    //这里会遍历audio,video,所以要加以区分
                                    if (sourceInfo.kind === 'videoinput') {
                                        that.exArray.push(sourceInfo.deviceId);
                                    }
                                }
                            }
                        ).catch(function (err) {
                        alert("获取设备列表错误")
                    });
                },
                
    <script type="text/javascript">
    

    本次只展示部分重要代码,部分代码可以自行补充,打开摄像头流程如下

    1先使用getUserMedia 约束条件获取到媒体, 其中 ‘audio’: true 表示获取视频,video:turn 表示获取声音

    2.使用navigator.mediaDevices.enumerateDevices 方法获取到设备

    3.获取到媒体资源后使用video标签进行播放

    4.在媒体流中通过getVideoTracks 方法可以获取到当前流媒体的轨道即正在使用的设备。
    在这里插入图片描述

    2、打开麦克风
    <body>
    	<input type="button" value="开启麦克风" v-on:click="checkVoice()"/>
    </body>
    <script type="text/javascript">
        // 开启声音
      checkVoice: function () {
                    if (navigator.getUserMedia) {
                        window.audioContext = new AudioContext()
                        navigator.getUserMedia({
                            'video': false,
                            'audio': true
                        }, this.onVoice, this.errorFunc); //success是获取成功的回调函数
                    } else {
                        alert('Native device media streaming (getUserMedia) not supported in this browser.');
                    }
    
                },
      // 获取到媒体声音后 音量展示 stream表示流媒体              
      onVoice: function (stream) {      
                    let that = this;
           			//创建一个管理、播放声音的对象
                    let audioContext = new AudioContext()
                    let analyser = audioContext.createAnalyser()
                    liveSource = audioContext.createMediaStreamSource(stream); //将麦克风的声音输入这个对象\
                    // 音频连入分析器
                    liveSource.connect(analyser)
                    // 分析器再将音频连入麦克风-这样播放的音频就会通过分析器
                    analyser.connect(audioContext.destination)
    
                    analyser.fftSize = 128
    
    
                    setInterval(() => {
                        let dataArray = new Uint8Array(analyser.frequencyBinCount)
                        analyser.getByteFrequencyData(dataArray)
                        // console.log(dataArray)
                        let maxVal = 0;
                        for (let i = 0; i < dataArray.length; i++) {
                            if (maxVal < dataArray[i]) {
                                maxVal = dataArray[i];
                            }
                        }
                        that.voiceWidth = Math.round(maxVal) + "px"
                        that.drow(dataArray)
                    }, 50)
                },
                    // 画图案
                drow: function (array) {
                    var canvas = document.getElementById('canvas3'),
                        cwidth = canvas.width,
                        cheight = canvas.height - 2,
                        meterWidth = 10, //能量条的宽度
                        gap = 2, //能量条间的间距
                        capHeight = 2,
                        meterNum = 800 / (10 + 2), //计算当前画布上能画多少条
                        ctx = canvas.getContext('2d')
                    var step = Math.round(array.length / meterNum);
                    ctx.clearRect(0, 0, cwidth, cheight); //清理画布准备画画
                    gradient = ctx.createLinearGradient(0, 0, 0, 300);
                    gradient.addColorStop(1, '#0f0');
                    gradient.addColorStop(0.5, '#ff0');
                    gradient.addColorStop(0, '#f00');
                    ctx.fillStyle = gradient;
                    for (var i = 0; i < meterNum; i++) {
                        var value = array[i * step];
                        ctx.fillRect(i * 12, cheight - value + capHeight, meterWidth, cheight);
                    }
                },
    </script>
    

    和打开视频流程一样,

    1.同样使用getUserMedia()方法 约束条件’audio’: true , 即可请求麦克风。

    2.当获取到音频流后传递给audio对象的srcObject方法即可。

    3.音频可视化,audioContext 方法可以创建一个音频分析器,将音频流载入后就能展示音频的大小

    在这里插入图片描述

    3、截屏及下载图片
    <body>
    	<input type="button" title="截屏" value="截屏" v-on:click="getPhoto()"/><br/>
    	<canvas id="canvas1" height="120px"></canvas>
    </body>
    <script>
        
             getPhoto: function () {
                 	// 创建canvas画布
                     this.canvas1 = document.getElementById('canvas1');
                this.context1 = this.canvas1.getContext('2d');
                    //将video对象内指定的区域捕捉绘制到画布上指定的区域,实现拍照。
                    this.context1.drawImage(this.video, 0, 0, 90, 120);
                 
                 
                 	// 转图片下载
                    // 抓取照片数据
                    let imgData = this.canvas1.toDataURL(); //将图像转换为base64数据
                    let base64Data = imgData.substr(22); //在前端截取22位之后的字符串作为图像数据
    
              
                    imgData = imgData.replace(this.changeImageType("png"), 'image/octet-stream');
                    let save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
                    save_link.href = imgData;
                    save_link.download = (new Date()).getTime() + '.' + "png";
    
                    let event = document.createEvent('MouseEvents');
                    event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
                    save_link.dispatchEvent(event);
                }
        		// 修改图片类型操作
                changeImageType: function (type) {
                    type = type.toLowerCase().replace(/jpg/i, 'jpeg');
                    let r = type.match(/png|jpeg|bmp|gif/)[0];
                    return 'image/' + r;
                },
          
    </script>
    

    在摄像头正常可以播放的时候,可以使用canvas进行图像捕捉

    1.使用媒体约束条件,调用getUserMedia()方法,实现摄像头播放正常

    2.获取到媒体流时,传递给video的srcObject对象进行播放

    3.创建canvas 标签,调用canvas的drawImage 方法,传递video对象可以生成一张2d图片。

    4.将canvas 图转换格式实现本地下载

    4.视频同步与
    <body>
        <input type="button" title="视频" value="视频" v-on:click="getVideo()"/><br/>
    	<canvas id="canvas2" height="120px"></canvas>
    </body>
    <script>
    			//视频
               getVideo: function () {
                    this.canvas2 = document.getElementById('canvas2');
                	this.context2 = this.canvas2.getContext('2d');
                    this.drawVideoAtCanvas(this.video, this.context2);
                },
                 // 将视频帧绘制到Canvas对象上,Canvas每60ms切换帧,形成肉眼视频效果
                drawVideoAtCanvas: function (video, context) {
                    window.setInterval(function () {
                        context.drawImage(video, 0, 0, 90, 120);
                    }, 60);
                },
    </script>
    

    在摄像头正常可以播放的时候,可以使用canvas进行图像捕捉

    1.使用媒体约束条件,调用getUserMedia()方法,实现摄像头播放正常

    2.获取到媒体流时,传递给video的srcObject对象进行播放

    3.将video的视频源载入canvas ,实现图案绘制,最后每隔60毫秒,绘制一张图片实现视频的同步

    三、总结

    1.访问相关设备API

    方法名参数说明
    getUserMedia定义约束对象,是否调用音频或者视频采集摄像头或者麦克风设备
    getDisplayMedia定义约束对象,设置视频采集宽高,是否调用视频捕获计算机屏幕的方法(本章未用到)
    MediaStreamConstanintsvideo,audio调用getUserMedia和getDisplayMedia方法的约束条件,ture表示采集,false表示不采集

    2.在本章学习使用媒体设备的基本操作,首先我们要解决的兼容性,因为不同的浏览器的不同版本对webrtc的兼容性不同,因此在使用前首先要解决兼容性。

    3.再次我们学回了如何访问媒体设备,使用设置约束调剂调用音频或者视频流。

    4.最后我们将媒体流以不同的形式进行了展示,做到了可视化。每一步都很有意思

    5.在使用webrtc的时候需要我们部署https服务访问,否则可能会因为安全问题而操作失败
    6.当然在后面还有一些分享桌面,捕捉等操作没在本次示例中演示。具体可以查看代码仓库。

    四、感谢

    感谢作者 亢少军老师的 《WebRTC音视频开发 》 课本指导

    展开全文
  • 它是开发人员为开发人员提供的阅读友好的 WebRTC 入门套件。 它包括所有必要步骤的供应商/SDK 列表和 WebRTC 字典,其中包含熟悉 WebRTC 开发所需的所有术语,并帮助您通过部署 WebRTC 解决方案引导您的方式。 ##...
  • 什么是webrtcwebrtc的来历。2.webrtc只能用于浏览器么?3.学习webrtc的难点:4.如何学习webrtc?5.学习计划:掌握:技术: 写在最前面的话 根据项目需求,最近开始学习webrtc,这块内容起点较高,比较庞杂,需要一...

    写在最前面的话

    根据项目需求,最近开始学习webrtc,这块内容起点较高,比较庞杂,需要一个系统的学习资料,在慕课网找到了李超老师的视频,这里贴出连接,尊重原创,尊重知识产权,尊重技术开源。
    https://coding.imooc.com/class/329.html
    本学习笔记是该课程的学习笔记,感觉收货很多,当然在自己实践coding的过程中也有不少踩坑的地方,这里记录一下。

    1.什么是webrtc?webrtc的来历。

    http://www.sohu.com/a/304429218_120122487
    https://baike.baidu.com/item/WebRTC/5522744?fr=aladdin
    https://segmentfault.com/a/1190000019593273
    Webrtc是Google花了6千万美金收购GIPS于2011年开源的音视频处理引擎。这个引擎可以在各个平台上编译运行,比如Android,ios,mac,Windows上都可以编译运行。Google的愿景是想把webrtc用于浏览器之间进行音视频实时互动通话这种快速的开发而使用的。Webrtc主要有2个功能:第1个是实时数据传输。实时传输就是在端与端之间选择一条最高效最快的传输通道,这方面webrtc是做的非常优秀的。第2个就是音视频引擎。但是它并不是简简单单的音视频的编解码,还能做各种编解码,都可以加入进去;比如视频方面:H264、vp8/vp9、H265、音频方面:opus、albc、等等。除此之外还包括音视频同步;还包括网络传输可能出现的丢包、抖动等等,做数据的平滑处理与恢复等等。。。这都是音视频引擎需要做的。在这两个方面(实时数据传输、音视频引擎的各种算法)和处理各种极端的情况等等都是webrtc的优势。
    所以,webrtc是Google开源的、跨平台的、主要用于浏览器之间的实时数据传输的音视频引擎。这就是webrtc。
    1

    2.webrtc只能用于浏览器么?

    当然不是,都能够在哪些领域应用呢?最主要的就是音视频会议在线教育,这是用的最多的。还有照相机、音乐播放器、共享远程桌面、录制、即时通讯工具、P2P网络加速、文件输出工具、游戏以及实时的人脸识别软件。
    Webrtc的愿景就是各浏览器之间可以快速的开发出可以实时互动的音视频的应用产品。浏览器现在已经不再是只能通过服务器获取到运算的结果然后进行渲染的简单事情了。首先是H5出现了,使得浏览器可以像普通客户端一样与服务器进行交互,做更多更重要的事情。需要掌握开发效率高学习成本低的JavaScript语言。
    Webrtc+webgl可以实现很酷的音乐播放器;
    Webrtc+AI可以进行实时的人脸识别;

    目前支持webrtc的浏览器厂商:google的Chrome、Safari苹果的、FireFox老牌浏览器、Windows的最新的Edge浏览器。目前这些浏览器对webrtc的功能基本都是支持的。

    3G时代使得移动音视频通话成为了可能,已经可以进行通话了,虽然通话质量不太好;4G的时代使得音视频通话的质量有了比较好的保障,现在的5G,音视频通话将会是一个必备的产品。

    3.学习webrtc的难点:

    ①Webrtc庞大、繁杂、门槛高。它涉及了各种协议规范,比如传输方面STUN,TURN,ICE框架,p2p穿越等等。媒体协商时使用的sdp,sdp对于webrtc做了专门的修改,等等都需要阅读大量的规范才能了解,这就增加了学习难度,而且这些文档都是英文文档。
    ②客户端与服务器分离。Webrtc只是一个客户端的规范,它没有定义服务端,服务端可以根据自己的业务自己去实现,去定义。要客户端与服务端之间进行通信,就必须搭建服务端,但是如果没有一定的基础和积累,很难写出服务端并与客户端进行很好的配合。
    ③网络屏蔽、系统的资料少。
    ④网上的demo错误多。

    4.如何学习webrtc?

    首先,上手webrtc的api,怎么调用api做出应用程序。
    其次,学习基础理论知识,再去做实战练习。
    第三,学习深入,深度学习,原理。
    第四,做实战练习。
    最后,全面了解webrtc。【webrtc的音视频传输以及非音视频传输、对于网络质量的统计与分析等等】。

    5.学习计划:

    第一阶段:基础入门:
    Webrtc架构了解。
    Nodejs原理及搭建。
    JS简单语法。

    第二阶段:音视频采集;
    音视频设备管理;
    音视频数据采集;
    拍照;
    录制;

    第三部分:信令服务器;
    信令服务器的作用;
    Nodejs+socket.io实现信令服务器;
    多人聊天的实现;

    第四部分:网络传输与协议;
    STUN/TURN/ICE(底层链路检测)
    RTP/RTCP(传输数据)
    DTLS/SRTP(数据安全性)
    数据统计与网络质量评估
    最后一部分:音视频通话;
    媒体能力协商过程
    端对端传输逻辑
    实现1:1实时通话
    共享远程桌面
    Android与浏览器互通

    掌握:

    ①Webrtc API的使用;
    ②Webrtc的工作原理;
    ③信令服务器的设计与搭建;
    ④实现1:1的实时互动直播系统;

    技术:

    1.设备管理(前置后置摄像头)
    2.音视频数据采集
    3.桌面采集
    4.p2p穿越,如果穿越不成功则进行服务中转
    5.STUN/TURN/ICE链路检测的规范协议
    6.RTP/RTCP进行数据反馈传输的协议
    7.为了数据安全的DTLS/SRTP数据传输协议
    8.网络限流、流量控制
    9. 传输之前进行媒体协商,媒体协商包括编解码器、采样率、时钟周期等协商设置。
    10.协商完成之后,进行音视频数据的传输,但是最底层走的还是UDP协议。
    11.文字聊天的非音视频数据传输。
    12.统计与网络质量评估。

    展开全文
  • webrtc 入门 教程

    2015-02-26 15:26:54
    本人的webrtc学习比较 研究webrtc2年 所有的心得都写出来了 和大家分享
  • webRTC 入门

    千次阅读 2019-11-04 15:32:19
    WebRTC基础介绍 webRTC 的最直接用途是两个浏览器间的p2p,不过加上中转服务器后也可以用来做其他的。 webRTC 是基于UDP的,作为对比,webSocket 是基于TCP的。 流程 除了getUserMedia之外,主要是两件事情: 两端...

    概念

    • WebRTC基础介绍
      webRTC 的最直接用途是两个浏览器间的p2p,不过加上中转服务器后也可以用来做其他的。
      webRTC 是基于UDP的,作为对比,webSocket 是基于TCP的。
      出处见水印

    • Mesh,SFU,MCU 的区别

      • Mesh: p2p
      • SFU: 中心节点只转发
      • MCU:中心节点混流
        在这里插入图片描述
    • STUN 和 TURN

      • STUN 只处理地址信息
      • TURN 提供转发功能
        在这里插入图片描述
        在这里插入图片描述

    流程

    除了getUserMedia之外,主要是两件事情:

    1. 两端间确定流媒体的格式(SDP)
    2. 两端间确定对方的网络地址(ICE), 由于浏览器极有可能是在内网中的,所以需要有一般的p2p内网穿透的过程

    出处见水印

    调试工具

    chrome://webrtc-internals/
    https://test.webrtc.org/

    代码

    官方的例子

    getUserMedia

    <!doctype html>
    <html lang="zh-CN">
    	<head> <meta charset="UTF-8">
    		<title>GetUserMedia实例</title>
    	</head>
    	<body>
    	<video id="video" autoplay></video>
    </body>
    <script type="text/javascript" src="webRTC.js"></script>
    </html>
    
    // simple camera
    var constraints = {
        video: true
    };
    
    function successCallback(stream) {
        var video = document.querySelector("video");
        //window.URL.createObjectURL is deprecated, so just assign the stream directly
        //video.src=window.URL.createObjectURL(stream);
        video.srcObject = stream;
    }
    
    function errorCallback(error) {
        console.log("navigator.getUserMedia error:", error);
    }
    
    navigator.getUserMedia(constraints, successCallback, errorCallback);
    
    

    local

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>webrtc案例</title>
    </head>
    <body>
        <div class="container">
            <h1>单机版视频呼叫</h1>
            <hr>
            <div class="video_container" align="center">
                <video id="local_video" autoplay playsinline muted></video>
                <video id="remote_video" autoplay></video>
            </div>
            <hr>
            <div class="button_container">
                <button id="startButton">采集视频</button>
                <button id="callButton">呼叫</button>
                <button id="hangupButton">关闭</button>
            </div>
            <script src="main.js"></script>
        </div>
    </body>
    </html>
    
    var startButton = document.getElementById('startButton');
    var callButton = document.getElementById('callButton');
    var hangupButton = document.getElementById('hangupButton');
    callButton.disabled = true;
    hangupButton.disabled = true;
    
    startButton.addEventListener('click', startAction);
    callButton.addEventListener('click', callAction);
    hangupButton.addEventListener('click', hangupAction);
    
    var localVideo = document.getElementById('local_video');
    var remoteVideo = document.getElementById('remote_video');
    var localStream;
    var pc1;
    var pc2;
    
    const offerOptions = {
        offerToReceiveVideo: 1,
        offerToReceiveAudio: 1
    };
    
    function startAction() {
        //采集摄像头视频
        navigator.mediaDevices.getUserMedia({
                video: true,
                audio: true
            })
            .then(function(mediaStream) {
                localStream = mediaStream;
                localVideo.srcObject = mediaStream;
                startButton.disabled = true;
                callButton.disabled = false;
            }).catch(function(error) {
                console.log(JSON.stringify(error));
            });
    }
    
    function callAction() {
    
        hangupButton.disabled = false;
        callButton.disabled = true;
    
        pc1 = new RTCPeerConnection();
        pc1.addEventListener('icecandidate', function(event) {
            console.log("p1 ice candidate:", event);
            var iceCandidate = event.candidate;
            if (iceCandidate) {
                // 实际通信中,这里应为pc1 把iceCandidate发送给Signal服务器,Signal服务器(通过websocket?)把p1 candidate的信息告诉p2
                pc2.addIceCandidate(iceCandidate);
            }
        });
        localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
    
        pc2 = new RTCPeerConnection();
        pc2.addEventListener('addstream', function(event) {
            console.log("p2 add stream", event)
            remoteVideo.srcObject = event.stream;
        });
    
        pc1.createOffer(offerOptions).then(function(offer) {
            console.log("p1 createOffer", offer);
            pc1.setLocalDescription(offer);
            // 实际通信中, p1 应该把offer 通过Signal服务器发给p2
            pc2.setRemoteDescription(offer);
    
            pc2.createAnswer().then(function(description) {
    
                console.log("p2 createAnswer", description);
                pc2.setLocalDescription(description);
               // 实际通信中, p2 应该把 answer 通过Signal服务器发给p1
                pc1.setRemoteDescription(description);
            });
        });
    }
    
    function hangupAction() {
        localStream.getTracks().forEach(track => track.stop());
        pc1.close();
        pc2.close();
        pc1 = null;
        pc2 = null;
        hangupButton.disabled = true;
        callButton.disabled = true;
        startButton.disabled = false;
    }
    

    加入socket.io

    展开全文
  • 1 WebRTC入门 本章目的: (1)了解什么WebRTC (2)掌握WebRTC通话原理 (3)学完该课程的收获 1.1 什么是WebRTC WebRTC(Web Real-Time Communication)是 Google于2010以6829万美元从 Global IP Solutions ...
  • webrtc系列1—— webrtc入门知识

    千次阅读 2019-08-20 13:22:11
    什么是WebRTC2. WebRTC框架介绍详细组件介绍3. 模块细致讲解国内方案厂商WebRTC发展前景文章借鉴: 1. 什么是WebRTC WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页...
  • webrtc-978-1-7821-6630-6 WebRTC 入门
  • webrtc入门与实战视频培训课程是通过作者多年经验总结出的一套webrtc入门教程,学完此课程,你能搭建出一套android互通或者web互通或者android对web互通的webrtc服务器,此课程由浅入深讲解了从编译到完整搭建一套...
  • WebRTC入门学习之初识WebRTC

    千次阅读 2017-01-02 13:33:16
    引言:  先声明本人只是小小实习生一枚,若有不正确的,希望大家帮忙指正。 一、WebRTC基本架构   ... 图一 WebRTC总体架构,摘自百度百科 ... 先说说WebRTC大致的实现思路:我们创建的web app,然后在app
  • WebRTC入门

    2019-11-21 22:01:39
    什么是WebRTC? 众所周知,浏览器本身不支持相互之间直接建立信道进行通信,都是通过服务器进行中转。比如现在有两个客户端,甲和乙,他们俩想要通信,首先需要甲和服务器、乙和服务器之间建立信道。甲给乙发送消息...
  • WebRTC各种资料集合javascript frameworksVideo chat:https://github.com/andyet/SimpleWebRTC https://github.com/priologic/easyrtc ... https...
  • [WebRTC] WebRTC 入门教程 (英文版)

    千次下载 热门讨论 2014-03-02 08:12:56
    [Packt Publishing] WebRTC 入门教程 (英文版) [Packt Publishing] Getting Started with WebRTC (E-Book) ☆ 图书概要:☆ Explore WebRTC for real-time peer-to-peer communication Overview Set up video ...
  • 而第2条中的NAT这个概念,我们之前在iOS即时通讯,从入门到“放弃”? ,中也提到过,不过那个时候我们是为了应对NAT超时,所造成的TCP连接中断。在这里我们就不展开去讲了,感兴趣的可以看看:NAT百科 这里我简要...
  • WebRTC实现了基于网页的视频会议,标准是WHATWG 协议,目的是通过浏览器提供简单的javascript就可以达到实时通讯(Real-Time Communications (RTC))能力。 WebRTC(Web Real-Time Communication)项目的最终目的...
  • 1. WebRTC入门 本章目的: 了解什么WebRTC 掌握WebRTC通话原理 学完该课程后的收获 1.1 什么是WebRTC WebRTC(Web Real-Time Communication)是 Google于2010以6829万美元从 Global IP Solutions 公司...
  • WebRTC 入门教程(二)| WebRTC信令控制与STUN/TURN服务器搭建 四月 4, 2019 作者:李超,音视频技术专家。本文首发于RTC 开发者社区,欢迎在社区留言与作者交流。...
  • WebRTC 简单入门

    2021-04-12 19:45:09
    目前资料不算少,不过确实也不多,而且理论偏多,新手入门其实还是有点压力的。 这边推荐几个资料和视频。 MDN 文档 记得出问题看看文档先 WebRTC samples 没有思路的时候记得看看 哔哩哔哩 - 一只斌 这个b站up,...
  • Web服务器选型 Nodejs Nginx Apache Web服务工作原理 Nodejs工作原理 JavaScript解析 Nodejs时间处理
  • webrtc 入门第二章 音视频录制 一、介绍 1、媒体录制原理 ​ 在很多场景中回放音视频资源的需求是非常重要的例如会议,直播授课等。任何媒体形式的表情都可进行录制,如 ,,等。其中内容更加自由用户的任何2d,3d操作...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,322
精华内容 928
关键字:

webrtc入门

友情链接: Studentsystem.zip