美文网首页
WebRTC学习(三)

WebRTC学习(三)

作者: 强某某 | 来源:发表于2020-04-08 13:30 被阅读0次

SDP规范

  • 会话层(全局的,类似于全局变量)
  • 媒体层(局部的,类似于局部变量)

会话层

  • 会话的名称与目的
  • 会话的存活时间
  • 会话中包括多个媒体信息

SDP媒体信息

  • 媒体格式
  • 传输协议
  • 传输IP和端口(价值不大,因为webrtc中会使用ice中收集的IP和端口)
  • 媒体负载类型

SDP格式

  • 由多个<type>=<value>组成
  • 一个会话级描述
  • 多个媒体级描述

SDP结构

  • Session Description
  • Time Description
  • Media Description

Session Description

  • v=(protocol version)
  • 0=(owner/create and session identifiter)
  • s=(session name)
  • c=*(conn info - optional if included at session-level)
  • a=*(zero or more session attribute lines)

Time Description

  • t=(time the session is active)
  • r=*(zero or more repeat times)

Media Description

  • m=(media name and transport address)
  • c=*(conn info - optional if included at session-level)
  • b=*(bandwidth information)
  • a=*(zero or more session attribute lines)

字段含义

  • Version必选

v=0 SDP的版本号,不包括次版本号

  • Session Name 必选

s=<session name> 会话名,s=- 表示忽略会话名

  • Origion/Owner 必选

o=<username><session id ><version><network type><address type><address>
例子:0=- 701812417903490901324124 2 IN IP4 127.0.0.1

  • Connection Data可选

c=<network type><address type><connection address>
例子: c=IN IP4 0.0.0.0

  • Media Announcements 必选

m=<media><port><transport><fmt/payload type list>
例子: m= audio 1024 UDP/TLS/RTP/SAVF 111103 104 9 0 8 106 105 13 126

  • Suggested Attributes 可选

a=<TYPE>或a=<TYPE>:<VALUES>

例子: a=framerate:《帧速率》

  • rtpmap 可选

a=rtpmap:<fmt/payload type><encoding name>/<clockrate>[/</encodingparameters>]
例子: a=rtpmap:103 ISAC/16000

  • fmtp可选

a=fmtp:<format/payload type> parameters
例子:a=fmtp:103 apt=106

1.png

STUN/TURN服务器选型

  • rfc5766-turn-server:谷歌开源,但是比较老了
  • coTurn :rfc的升级版,比较活跃,选这个
  • ResTurn :也是比较流行的,但是比较老

coTurn服务器搭建与部署

  • 下载coTurn
  • ./configure --prefix=/usr/local/turn
  • 编译 make&&make install

一、下载corturn

//将安装的文件克隆到本地
git clone https://github.com/coturn/coturn
//安装依赖libevent-dev
sudo apt-get install libevent-dev
//进入corturn执行下面的代码。注意此处后面的路径是安装的路径,可以自选;执行成功如下
 ./configure --prefix=/usr/local/coturn
2.png

执行成功之后查看makefile

ls -alt Makefile


3.png

二、使用make工具,可以使用多线程进行并行的编译(一般后面是内核的两倍,例如:电脑内核为6,这里-j后面为12)
make -j 12


4.png

三、最后install

sudo make install

5.png

安装完毕,在/usr/local/corturn下面就可以看到安装的corturn了 /bin下面就是可执行的服务 /etc下面就是一些配置 /include下面是一些头文件 /lib下面是一些库文件

coTurn服务器配置

6.png

修改turnserver.conf.default文件

smileyqp@smileyqp:/usr/local/coturn/etc$ sudo vim turnserver.conf.default 
//简单设置几个参数
turnserver.conf.default 
user=smileyqp:123456
listening-port=3478

环境变量中添加coturn

sudo vim ~/.bashrc 
export PATH=/usr/local/coturn/bin
source  ~/.bashrc

开启服务

//如果没添加到环境变量种种就用,加了就直接turnserver
./bin/turnserver -c ./etc/turnserver.conf.default
//查看turn服务是否启动
ps -ef | grep turn

PTCPeerConnection

7.png 8.png 9.png 10.png 11.png 12.png 13.png

客户端信令消息

  • message中分为三个:offer、answer、candidate


    14.png
15.png 16.png 17.png

音视频实时互动信令服务器

'use strict'

var log4js = require('log4js');
var http = require('http');
var https = require('https');
var fs = require('fs');
var socketIo = require('socket.io');

var express = require('express');
var serveIndex = require('serve-index');

var USERCOUNT = 3;

log4js.configure({
    appenders: {
        file: {
            type: 'file',
            filename: 'app.log',
            layout: {
                type: 'pattern',
                pattern: '%r %p - %m',
            }
        }
    },
    categories: {
       default: {
          appenders: ['file'],
          level: 'debug'
       }
    }
});

var logger = log4js.getLogger();

var app = express();
app.use(serveIndex('./public'));
app.use(express.static('./public'));



//http server
var http_server = http.createServer(app);
http_server.listen(80, '0.0.0.0');

var options = {
    key : fs.readFileSync('./cert/1557605_www.learningrtc.cn.key'),
    cert: fs.readFileSync('./cert/1557605_www.learningrtc.cn.pem')
}

//https server
var https_server = https.createServer(options, app);
var io = socketIo.listen(https_server);

io.sockets.on('connection', (socket)=> {

    socket.on('message', (room, data)=>{
        socket.to(room).emit('message',room, data);
    });

    socket.on('join', (room)=>{
        socket.join(room);
        var myRoom = io.sockets.adapter.rooms[room]; 
        var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
        logger.debug('the user number of room is: ' + users);

        if(users < USERCOUNT){
            socket.emit('joined', room, socket.id); //发给除自己之外的房间内的所有人
            if(users > 1){
                socket.to(room).emit('otherjoin', room, socket.id);
            }
        
        }else{
            socket.leave(room); 
            socket.emit('full', room, socket.id);
        }
        //socket.emit('joined', room, socket.id); //发给自己
        //socket.broadcast.emit('joined', room, socket.id); //发给除自己之外的这个节点上的所有人
        //io.in(room).emit('joined', room, socket.id); //发给房间内的所有人
    });

    socket.on('leave', (room)=>{
        var myRoom = io.sockets.adapter.rooms[room]; 
        var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
        logger.debug('the user number of room is: ' + (users-1));
        //socket.emit('leaved', room, socket.id);
        //socket.broadcast.emit('leaved', room, socket.id);
        socket.to(room).emit('bye', room, socket.id);
        socket.emit('leaved', room, socket.id);
        //io.in(room).emit('leaved', room, socket.id);
    });

});

https_server.listen(443, '0.0.0.0');

客户状态机及处理逻辑

18.png 19.png 20.png 21.png 22.png 23.png
//main.js
'use strict'


var start = document.querySelector('button#start');
var restart = document.querySelector('button#restart');

var pc1 = new RTCPeerConnection();
var pc2 = new RTCPeerConnection();

function handleError(err){

    console.log('Failed to create offer', err);
}

function getPc1Answer(desc){

    console.log('getPc1Answer', desc.sdp);
    pc2.setLocalDescription(desc);
    pc1.setRemoteDescription(desc);

    /*
    pc2.createOffer({offerToRecieveAudio:1, offerToReceiveVideo:1})
        .then(getPc2Offer)
        .catch(handleError);
        */
}

function getPc1Offer(desc){

    console.log('getPc1Offer', desc.sdp);
    pc1.setLocalDescription(desc);
    pc2.setRemoteDescription(desc);
    pc2.createAnswer().then(getPc1Answer).catch(handleError);

}

function getPc2Answer(desc){
    console.log('getPc2Answer');

    pc1.setLocalDescription(desc);
    pc2.setRemoteDescription(desc);
}

function getPc2Offer(desc){

    console.log('getPc2Offer');
    pc2.setLocalDescription(desc);
    pc1.setRemoteDescription(desc);
    pc1.createAnswer().then(getPc2Answer).catch(handleError);

}

function startTest(){


    pc1.createOffer({offerToReceiveAudio:1, offerToRecieveVideo:1})
        .then(getPc1Offer)
        .catch(handleError);




}

function getMediaStream(stream){

    stream.getTracks().forEach((track) => {
        pc1.addTrack(track, stream);    
    });

    var offerConstraints = {
        offerToReceiveAudio: 1,
        offerToRecieveVideo: 1,
        iceRestart:false 
    }

    pc1.createOffer(offerConstraints)
        .then(getPc1Offer)
        .catch(handleError);

}

function startICE(){

    var constraints = {
        audio: true,
        video: true
    }

    navigator.mediaDevices.getUserMedia(constraints)
                .then(getMediaStream)
                .catch(handleError);
}

start.onclick = startTest;
restart.onclick = startICE;
<html>
    <head>
        <title>
            test createOffer from different client
        </title>
    </head>

    <body>
        <button id="start">Start</button>
        <button id="restart">reStart ICE</button>
        <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
        <script src="js/main.js"></script>
    </body>
</html>

实战demo

//main.js
'use strict'

var localVideo = document.querySelector('video#localvideo');
var remoteVideo = document.querySelector('video#remotevideo');

var btnConn =  document.querySelector('button#connserver');
var btnLeave = document.querySelector('button#leave');

var offer = document.querySelector('textarea#offer');
var answer = document.querySelector('textarea#answer');

var shareDeskBox  = document.querySelector('input#shareDesk');

var pcConfig = {
  'iceServers': [{
    'urls': 'turn:stun.al.learningrtc.cn:3478',
    'credential': "mypasswd",
    'username': "garrylea"
  }]
};

var localStream = null;
var remoteStream = null;

var pc = null;

var roomid;
var socket = null;

var offerdesc = null;
var state = 'init';

// 以下代码是从网上找的
//=========================================================================================

//如果返回的是false说明当前操作系统是手机端,如果返回的是true则说明当前的操作系统是电脑端
function IsPC() {
    var userAgentInfo = navigator.userAgent;
    var Agents = ["Android", "iPhone","SymbianOS", "Windows Phone","iPad", "iPod"];
    var flag = true;

    for (var v = 0; v < Agents.length; v++) {
        if (userAgentInfo.indexOf(Agents[v]) > 0) {
            flag = false;
            break;
        }
    }

    return flag;
}

//如果返回true 则说明是Android  false是ios
function is_android() {
    var u = navigator.userAgent, app = navigator.appVersion;
    var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //g
    var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
    if (isAndroid) {
        //这个是安卓操作系统
        return true;
    }

    if (isIOS) {
        //这个是ios操作系统
        return false;
    }
}

//获取url参数
function getQueryVariable(variable)
{
       var query = window.location.search.substring(1);
       var vars = query.split("&");
       for (var i=0;i<vars.length;i++) {
               var pair = vars[i].split("=");
               if(pair[0] == variable){return pair[1];}
       }
       return(false);
}

//=======================================================================

function sendMessage(roomid, data){

    console.log('send message to other end', roomid, data);
    if(!socket){
        console.log('socket is null');
    }
    socket.emit('message', roomid, data);
}

function conn(){

    socket = io.connect();

    socket.on('joined', (roomid, id) => {
        console.log('receive joined message!', roomid, id);
        state = 'joined'

        //如果是多人的话,第一个人不该在这里创建peerConnection
        //都等到收到一个otherjoin时再创建
        //所以,在这个消息里应该带当前房间的用户数
        //
        //create conn and bind media track
        createPeerConnection();
        bindTracks();

        btnConn.disabled = true;
        btnLeave.disabled = false;
        console.log('receive joined message, state=', state);
    });

    socket.on('otherjoin', (roomid) => {
        console.log('receive joined message:', roomid, state);

        //如果是多人的话,每上来一个人都要创建一个新的 peerConnection
        //
        if(state === 'joined_unbind'){
            createPeerConnection();
            bindTracks();
        }

        state = 'joined_conn';
        call();

        console.log('receive other_join message, state=', state);
    });

    socket.on('full', (roomid, id) => {
        console.log('receive full message', roomid, id);
        hangup();
        closeLocalMedia();
        state = 'leaved';
        console.log('receive full message, state=', state);
        alert('the room is full!');
    });

    socket.on('leaved', (roomid, id) => {
        console.log('receive leaved message', roomid, id);
        state='leaved'
        socket.disconnect();
        console.log('receive leaved message, state=', state);

        btnConn.disabled = false;
        btnLeave.disabled = true;
    });

    socket.on('bye', (room, id) => {
        console.log('receive bye message', roomid, id);
        //state = 'created';
        //当是多人通话时,应该带上当前房间的用户数
        //如果当前房间用户不小于 2, 则不用修改状态
        //并且,关闭的应该是对应用户的peerconnection
        //在客户端应该维护一张peerconnection表,它是
        //一个key:value的格式,key=userid, value=peerconnection
        state = 'joined_unbind';
        hangup();
        offer.value = '';
        answer.value = '';
        console.log('receive bye message, state=', state);
    });

    socket.on('disconnect', (socket) => {
        console.log('receive disconnect message!', roomid);
        if(!(state === 'leaved')){
            hangup();
            closeLocalMedia();

        }
        state = 'leaved';
    
    });

    socket.on('message', (roomid, data) => {
        console.log('receive message!', roomid, data);

        if(data === null || data === undefined){
            console.error('the message is invalid!');
            return; 
        }

        if(data.hasOwnProperty('type') && data.type === 'offer') {
            
            offer.value = data.sdp;

            pc.setRemoteDescription(new RTCSessionDescription(data));

            //create answer
            pc.createAnswer()
                .then(getAnswer)
                .catch(handleAnswerError);

        }else if(data.hasOwnProperty('type') && data.type == 'answer'){
            answer.value = data.sdp;
            pc.setRemoteDescription(new RTCSessionDescription(data));
        
        }else if (data.hasOwnProperty('type') && data.type === 'candidate'){
            var candidate = new RTCIceCandidate({
                sdpMLineIndex: data.label,
                candidate: data.candidate
            });
            pc.addIceCandidate(candidate);  
        
        }else{
            console.log('the message is invalid!', data);
        
        }
    
    });


    roomid = getQueryVariable('room');
    socket.emit('join', roomid);

    return true;
}

function connSignalServer(){
    
    //开启本地视频
    start();

    return true;
}

function getMediaStream(stream){

    if(localStream){
        stream.getAudioTracks().forEach((track)=>{
            localStream.addTrack(track);    
            stream.removeTrack(track);
        });
    }else{
        localStream = stream;   
    }

    localVideo.srcObject = localStream;

    //这个函数的位置特别重要,
    //一定要放到getMediaStream之后再调用
    //否则就会出现绑定失败的情况
    //
    //setup connection
    conn();

    //btnStart.disabled = true;
    //btnCall.disabled = true;
    //btnHangup.disabled = true;
}

function getDeskStream(stream){
    localStream = stream;
}

function handleError(err){
    console.error('Failed to get Media Stream!', err);
}

function shareDesk(){

    if(IsPC()){
        navigator.mediaDevices.getDisplayMedia({video: true})
            .then(getDeskStream)
            .catch(handleError);

        return true;
    }

    return false;

}

function start(){

    if(!navigator.mediaDevices ||
        !navigator.mediaDevices.getUserMedia){
        console.error('the getUserMedia is not supported!');
        return;
    }else {

        var constraints;

        if( shareDeskBox.checked && shareDesk()){

            constraints = {
                video: false,
                audio:  {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true
                }
            }

        }else{
            constraints = {
                video: true,
                audio:  {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true
                }
            }
        }

        navigator.mediaDevices.getUserMedia(constraints)
                    .then(getMediaStream)
                    .catch(handleError);
    }

}

function getRemoteStream(e){
    remoteStream = e.streams[0];
    remoteVideo.srcObject = e.streams[0];
}

function handleOfferError(err){
    console.error('Failed to create offer:', err);
}

function handleAnswerError(err){
    console.error('Failed to create answer:', err);
}

function getAnswer(desc){
    pc.setLocalDescription(desc);
    answer.value = desc.sdp;

    //send answer sdp
    sendMessage(roomid, desc);
}

function getOffer(desc){
    pc.setLocalDescription(desc);
    offer.value = desc.sdp;
    offerdesc = desc;

    //send offer sdp
    sendMessage(roomid, offerdesc); 

}

function createPeerConnection(){

    //如果是多人的话,在这里要创建一个新的连接.
    //新创建好的要放到一个map表中。
    //key=userid, value=peerconnection
    console.log('create RTCPeerConnection!');
    if(!pc){
        pc = new RTCPeerConnection(pcConfig);

        pc.onicecandidate = (e)=>{

            if(e.candidate) {
                sendMessage(roomid, {
                    type: 'candidate',
                    label:event.candidate.sdpMLineIndex, 
                    id:event.candidate.sdpMid, 
                    candidate: event.candidate.candidate
                });
            }else{
                console.log('this is the end candidate');
            }
        }

        pc.ontrack = getRemoteStream;
    }else {
        console.warning('the pc have be created!');
    }

    return; 
}

//绑定永远与 peerconnection在一起,
//所以没必要再单独做成一个函数
function bindTracks(){

    console.log('bind tracks into RTCPeerConnection!');

    if( pc === null || pc === undefined) {
        console.error('pc is null or undefined!');
        return;
    }

    if(localStream === null || localStream === undefined) {
        console.error('localstream is null or undefined!');
        return;
    }

    //add all track into peer connection
    localStream.getTracks().forEach((track)=>{
        pc.addTrack(track, localStream);    
    });

}

function call(){
    
    if(state === 'joined_conn'){

        var offerOptions = {
            offerToRecieveAudio: 1,
            offerToRecieveVideo: 1
        }

        pc.createOffer(offerOptions)
            .then(getOffer)
            .catch(handleOfferError);
    }
}

function hangup(){

    if(pc) {

        offerdesc = null;
        
        pc.close();
        pc = null;
    }

}

function closeLocalMedia(){

    if(localStream && localStream.getTracks()){
        localStream.getTracks().forEach((track)=>{
            track.stop();
        });
    }
    localStream = null;
}

function leave() {

    if(socket){
        socket.emit('leave', roomid); //notify server
    }

    hangup();
    closeLocalMedia();

    offer.value = '';
    answer.value = '';
    btnConn.disabled = false;
    btnLeave.disabled = true;
}

btnConn.onclick = connSignalServer
btnLeave.onclick = leave;

//main_signal.js
'use strict'

var localVideo = document.querySelector('video#localvideo');
var remoteVideo = document.querySelector('video#remotevideo');

var btnConn =  document.querySelector('button#connserver');
var btnLeave = document.querySelector('button#leave');

var localStream = null;

var roomid;
var socket = null;

var state = 'init';


function sendMessage(roomid, data){

    console.log('send message to other end', roomid, data);
    if(!socket){
        console.log('socket is null');
    }
    socket.emit('message', roomid, data);
}

function conn(){

    socket = io.connect();

    socket.on('joined', (roomid, id) => {
        console.log('receive joined message!', roomid, id);
        state = 'joined'

        btnConn.disabled = true;
        btnLeave.disabled = false;
        console.log('receive joined message, state=', state);
    });

    socket.on('otherjoin', (roomid) => {
        console.log('receive joined message:', roomid, state);
        state = 'joined_conn';
        console.log('receive other_join message, state=', state);
    });

    socket.on('full', (roomid, id) => {
        console.log('receive full message', roomid, id);
        state = 'leaved';
        console.log('receive full message, state=', state);
        alert('the room is full!');
    });

    socket.on('leaved', (roomid, id) => {
        console.log('receive leaved message', roomid, id);
        state='leaved'
        btnConn.disabled = false;
        btnLeave.disabled = true;
    });

    socket.on('bye', (room, id) => {
        console.log('receive bye message', roomid, id);
        state = 'joined_unbind';
        console.log('receive bye message, state=', state);
    });

    socket.on('disconnect', (socket) => {
        console.log('receive disconnect message!', roomid);
        state = 'leaved';
        console.log('receive disconnect message, state=', state);
    });

    socket.on('message', (roomid, data) => {
        console.log('receive message!', roomid, data);

        if(data === null || data === undefined){
            console.error('the message is invalid!');
            return; 
        }

        if(data.hasOwnProperty('type') && data.type === 'offer') {
            

        }else if(data.hasOwnProperty('type') && data.type == 'answer'){
        
        }else if (data.hasOwnProperty('type') && data.type === 'candidate'){
        
        }else{
            console.log('the message is invalid!', data);
        }
    
    });

    roomid = getQueryVariable('room');
    socket.emit('join', roomid);

    return true;
}

function connSignalServer(){
    
    //开启本地视频
    start();

    return true;
}

function getMediaStream(stream){

    localStream = stream;   
    localVideo.srcObject = localStream;

    conn();
}

function handleError(err){
    if(err){
        console.error('Failed to get Media Stream:', err);
    }else {
        console.error('Failed to get Media Stream!', );
    }

    return;
}


function start(){

    if(!navigator.mediaDevices ||
        !navigator.mediaDevices.getUserMedia){
        console.error('the getUserMedia is not supported!');
        return;
    }else {

        var constraints = {
            video: true,
            audio: false 
        }

        navigator.mediaDevices.getUserMedia(constraints)
                    .then(getMediaStream)
                    .catch(handleError);
    }

}

function closeLocalMedia(){

    if(localStream && localStream.getTracks()){
        localStream.getTracks().forEach((track)=>{
            track.stop();
        });
    }
    localStream = null;
}

function leave() {

    if(socket){
        socket.emit('leave', roomid); //notify server
    }

    btnConn.disabled = false;
    btnLeave.disabled = true;
}

btnConn.onclick = connSignalServer
btnLeave.onclick = leave;
//main_sdp.js
'use strict'

var localVideo = document.querySelector('video#localvideo');
var remoteVideo = document.querySelector('video#remotevideo');

var btnConn =  document.querySelector('button#connserver');
var btnLeave = document.querySelector('button#leave');

var pcConfig = {
  'iceServers': [{
    'urls': 'turn:stun.al.learningrtc.cn:3478',
    'credential': "mypasswd",
    'username': "garrylea"
  }]
};

var localStream = null;
var remoteStream = null;

var pc = null;

var roomid;
var socket = null;

var offerdesc = null;
var state = 'init';


function conn(){

    socket = io.connect();

    socket.on('joined', (roomid, id) => {
        console.log('receive joined message!', roomid, id);
        state = 'joined'

        createPeerConnection();

        btnConn.disabled = true;
        btnLeave.disabled = false;
        console.log('receive joined message, state=', state);
    });

    socket.on('otherjoin', (roomid) => {
        console.log('receive joined message:', roomid, state);

        if(state === 'joined_unbind'){
            createPeerConnection();
        }

        state = 'joined_conn';
        call();

        console.log('receive other_join message, state=', state);
    });

    socket.on('full', (roomid, id) => {
        console.log('receive full message', roomid, id);
        state = 'leaved';
        console.log('receive full message, state=', state);
        alert('the room is full!');
    });

    socket.on('leaved', (roomid, id) => {
        console.log('receive leaved message', roomid, id);
        state='leaved'
        socket.disconnect();
        console.log('receive leaved message, state=', state);

        btnConn.disabled = false;
        btnLeave.disabled = true;
    });

    socket.on('bye', (room, id) => {
        console.log('receive bye message', roomid, id);
        state = 'joined_unbind';
        hangup();
        console.log('receive bye message, state=', state);
    });

    socket.on('disconnect', (socket) => {
        console.log('receive disconnect message!', roomid);
        state = 'leaved';
    
        console.log('receive disconnect message, state=', state);
    });

    socket.on('message', (roomid, data) => {
        console.log('receive message!', roomid, data);

        if(data === null || data === undefined){
            console.error('the message is invalid!');
            return; 
        }

        if(data.hasOwnProperty('type') && data.type === 'offer') {

            pc.setRemoteDescription(new RTCSessionDescription(data));
            //create answer
            pc.createAnswer()
                .then(getAnswer)
                .catch(handleAnswerError);

        }else if(data.hasOwnProperty('type') && data.type == 'answer'){
            answer.value = data.sdp;
            pc.setRemoteDescription(new RTCSessionDescription(data));
        
        }else if (data.hasOwnProperty('type') && data.type === 'candidate'){
            var candidate = new RTCIceCandidate({
                sdpMLineIndex: data.label,
                candidate: data.candidate
            });
            pc.addIceCandidate(candidate);  
        
        }else{
            console.log('the message is invalid!', data);
        
        }
    
    });


    roomid = getQueryVariable('room');
    socket.emit('join', roomid);

    return true;
}

function connSignalServer(){
    
    //开启本地视频
    start();

    return true;
}

function getMediaStream(stream){

    localStream = stream;   
    localVideo.srcObject = localStream;

    //setup connection
    conn();
}

function handleError(err){
    if(err){
        console.error('Failed to get Media Stream!', err);  
    }else {
        console.error('Failed to get Media Stream!');
    }
}

function start(){

    if(!navigator.mediaDevices ||
        !navigator.mediaDevices.getUserMedia){
        console.error('the getUserMedia is not supported!');
        return;
    }else {

        var constraints = {
            video: true,
            audio: false
        }

        navigator.mediaDevices.getUserMedia(constraints)
                    .then(getMediaStream)
                    .catch(handleError);
    }

}

function getRemoteStream(e){
    remoteStream = e.streams[0];
    remoteVideo.srcObject = e.streams[0];
}

function sendMessage(roomid, data){

    console.log('send message to other end', roomid, data);
    if(!socket){
        console.log('socket is null');
    }
    socket.emit('message', roomid, data);
}

function handleOfferError(err){
    console.error('Failed to create offer:', err);
}

function handleAnswerError(err){
    console.error('Failed to create answer:', err);
}

function getAnswer(desc){
    pc.setLocalDescription(desc);

    //send answer sdp
    sendMessage(roomid, desc);
}

function getOffer(desc){
    pc.setLocalDescription(desc);

    sendMessage(roomid, offerdesc); 
}

function call(){
    
    if(state === 'joined_conn'){

        var offerOptions = {
            offerToRecieveAudio: 1,
            offerToRecieveVideo: 1
        }

        pc.createOffer(offerOptions)
            .then(getOffer)
            .catch(handleOfferError);
    }
}

function createPeerConnection(){

    console.log('create RTCPeerConnection!');
    if(!pc){
        pc = new RTCPeerConnection(pcConfig);

        pc.onicecandidate = (e)=>{

            if(e.candidate) {
                sendMessage(roomid, {
                    type: 'candidate',
                    label:event.candidate.sdpMLineIndex, 
                    id:event.candidate.sdpMid, 
                    candidate: event.candidate.candidate
                });
            }else{
                console.log('this is the end candidate');
            }
        }

        pc.ontrack = getRemoteStream;
    }else {
        console.warning('the pc have be created!');
    }

    console.log('bind tracks into RTCPeerConnection!');

    if(localStream === null || localStream === undefined) {
        console.error('localstream is null or undefined!');
        return false;
    }

    //add all track into peer connection
    localStream.getTracks().forEach((track)=>{
        pc.addTrack(track, localStream);    
    });

    return true;    
}


function hangup(){

    if(pc) {
        pc.close();
        pc = null;
    }

}

function closeLocalMedia(){

    if(localStream && localStream.getTracks()){
        localStream.getTracks().forEach((track)=>{
            track.stop();
        });
    }
    localStream = null;
}

function leave() {

    if(socket){
        socket.emit('leave', roomid); //notify server
    }

    hangup();
    closeLocalMedia();

    btnConn.disabled = false;
    btnLeave.disabled = true;
}

btnConn.onclick = connSignalServer
btnLeave.onclick = leave;
//main_connection.js
'use strict'

var localVideo = document.querySelector('video#localvideo');
var remoteVideo = document.querySelector('video#remotevideo');

var btnConn =  document.querySelector('button#connserver');
var btnLeave = document.querySelector('button#leave');

var pcConfig = {
  'iceServers': [{
    'urls': 'turn:stun.al.learningrtc.cn:3478',
    'credential': "mypasswd",
    'username': "garrylea"
  }]
};

var localStream = null;
var remoteStream = null;

var pc = null;

var roomid;
var socket = null;

var state = 'init';

function conn(){

    socket = io.connect();

    socket.on('joined', (roomid, id) => {
        console.log('receive joined message!', roomid, id);
        state = 'joined'

        createPeerConnection();

        btnConn.disabled = true;
        btnLeave.disabled = false;
        console.log('receive joined message, state=', state);
    });

    socket.on('otherjoin', (roomid) => {
        console.log('receive joined message:', roomid, state);

        if(state === 'joined_unbind'){
            createPeerConnection();
        }

        state = 'joined_conn';
        call();

        console.log('receive other_join message, state=', state);
    });

    socket.on('full', (roomid, id) => {
        console.log('receive full message', roomid, id);
        state = 'leaved';
        console.log('receive full message, state=', state);
        alert('the room is full!');
    });

    socket.on('leaved', (roomid, id) => {
        console.log('receive leaved message', roomid, id);
        state='leaved'
        socket.disconnect();
        console.log('receive leaved message, state=', state);

        btnConn.disabled = false;
        btnLeave.disabled = true;
    });

    socket.on('bye', (room, id) => {
        console.log('receive bye message', roomid, id);
        state = 'joined_unbind';
        hangup();
        console.log('receive bye message, state=', state);
    });

    socket.on('disconnect', (socket) => {
        console.log('receive disconnect message!', roomid);
        state = 'leaved';
    
        console.log('receive disconnect message, state=', state);
    });

    socket.on('message', (roomid, data) => {
        console.log('receive message!', roomid, data);

        if(data === null || data === undefined){
            console.error('the message is invalid!');
            return; 
        }

        if(data.hasOwnProperty('type') && data.type === 'offer') {

        }else if(data.hasOwnProperty('type') && data.type == 'answer'){
        
        }else if (data.hasOwnProperty('type') && data.type === 'candidate'){
        
        }else{
            console.log('the message is invalid!', data);
        
        }
    
    });


    roomid = getQueryVariable('room');
    socket.emit('join', roomid);

    return true;
}

function connSignalServer(){
    
    //开启本地视频
    start();

    return true;
}

function getMediaStream(stream){

    localStream = stream;   
    localVideo.srcObject = localStream;

    //setup connection
    conn();
}

function handleError(err){
    if(err){
        console.error('Failed to get Media Stream!', err);  
    }else {
        console.error('Failed to get Media Stream!');
    }
}

function start(){

    if(!navigator.mediaDevices ||
        !navigator.mediaDevices.getUserMedia){
        console.error('the getUserMedia is not supported!');
        return;
    }else {

        var constraints = {
            video: true,
            audio: false
        }

        navigator.mediaDevices.getUserMedia(constraints)
                    .then(getMediaStream)
                    .catch(handleError);
    }

}

function getRemoteStream(e){
    remoteStream = e.streams[0];
    remoteVideo.srcObject = e.streams[0];
}

function createPeerConnection(){

    console.log('create RTCPeerConnection!');
    if(!pc){
        pc = new RTCPeerConnection(pcConfig);

        pc.onicecandidate = (e)=>{

            if(e.candidate) {
                sendMessage(roomid, {
                    type: 'candidate',
                    label:event.candidate.sdpMLineIndex, 
                    id:event.candidate.sdpMid, 
                    candidate: event.candidate.candidate
                });
            }else{
                console.log('this is the end candidate');
            }
        }

        pc.ontrack = getRemoteStream;
    }else {
        console.warning('the pc have be created!');
    }

    console.log('bind tracks into RTCPeerConnection!');

    if(localStream === null || localStream === undefined) {
        console.error('localstream is null or undefined!');
        return false;
    }

    //add all track into peer connection
    localStream.getTracks().forEach((track)=>{
        pc.addTrack(track, localStream);    
    });

    return true;    
}

function call(){
    
    if(state === 'joined_conn'){

        var offerOptions = {
            offerToRecieveAudio: 1,
            offerToRecieveVideo: 1
        }

        pc.createOffer(offerOptions)
            .then(getOffer)
            .catch(handleOfferError);
    }
}

function hangup(){

    if(pc) {
        pc.close();
        pc = null;
    }

}

function closeLocalMedia(){

    if(localStream && localStream.getTracks()){
        localStream.getTracks().forEach((track)=>{
            track.stop();
        });
    }
    localStream = null;
}

function sendMessage(roomid, data){

    console.log('send message to other end', roomid, data);
    if(!socket){
        console.log('socket is null');
    }
    socket.emit('message', roomid, data);
}


function leave() {

    if(socket){
        socket.emit('leave', roomid); //notify server
    }

    hangup();
    closeLocalMedia();

    btnConn.disabled = false;
    btnLeave.disabled = true;
}

btnConn.onclick = connSignalServer
btnLeave.onclick = leave;
//index.html
<html>
    <head>
        <title>really peer connection</title>
        <link rel="stylesheet" href="./css/main.css">
        <script language="javascript" type="text/javascript">
            function gotoNextPage(){
                var roomid = document.querySelector('input#room');
                if(roomid.value === null || roomid.value === ''){
                    alert('roomid is null');
                }else {
                    window.location.href="room.html?room="+ roomid.value;
                }
            }
        </script>
    </head>
    <body>
        <table align="center">
            <tr><td><div>
                    <label>roomid:</label>
                    <input type="input" id="room">
            </div></td></tr>
            <tr><td><div>
                        <button id="join" onclick="gotoNextPage()">Join</button>
            </div></td></tr>
        </table>
    </body>
</html>
//room.html
<html>
    <head>
        <title>WebRTC PeerConnection</title>
        <link href="./css/main.css" rel="stylesheet" />
    </head>

    <body>
        <div>


            <div>
                <button id="connserver">Connect Sig Server</button>
                <!--<button id="start" disabled>Start</button>  
                <button id="call" disabled>Call</button>    
                <button id="hangup" disabled>HangUp</button>    
                -->
                <button id="leave" disabled>Leave</button>  
            </div>

            <div>
                <input id="shareDesk" type="checkbox"/><label for="shareDesk">Share Desktop</label>
            </div>


            <div id="preview">
                <div >
                    <h2>Local:</h2>
                    <video id="localvideo" autoplay playsinline muted></video>
                    <h2>Offer SDP:</h2>
                    <textarea id="offer"></textarea>
                </div>
                <div>
                    <h2>Remote:</h2>
                    <video id="remotevideo" autoplay playsinline></video>
                    <h2>Answer SDP:</h2>
                    <textarea id="answer"></textarea>
                </div>
            </div>
        </div>

        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
        <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
        <script src="js/main.js"></script>
    </body>
</html>

如果远程共享桌面

//只是提几个重要api
var promise=navigator.mediaDevices.getDisplayMedia(contraints);

//constraints可选
//constraints中约束与getUserMedia函数中的基本一致
  • getDisplayMedia无法同时采集音频和桌面,一般都是采集桌面
  • 桌面采集时候分辨率是否可调整

总结

  • 网络连接要在音视频数据获取到之后,否则有可能绑定音视频失败
  • 当一段退出房间后,另一端的PeerConnection要关闭重建,否则与新用户互通时媒体协商会失败
  • 异步事件处理

相关文章

网友评论

      本文标题:WebRTC学习(三)

      本文链接:https://www.haomeiwen.com/subject/jzqtmhtx.html