webrtc实现局域网通话(三)

前言

先回顾一下上两篇的内容。第一篇,是调用摄像头采集视频的案例,首先创建了node.js 项目,然后用getUserMedia()这个东西获取MediaStream,然后调用video.srcObject = mediasteam,浏览器就显示采集内容了。第二篇,是单机单个页面呼叫的案例,主要目的是梳理信令转发流程,核心点在于RTCPeerConnection()这个迷人的对象。
本篇将引入socket.io,实现两台电脑之间的呼叫的案例。
说明:我现实情况是笔记本有摄像头,而台式机没有,因此效果是笔记本采集视频,台式机上线后,笔记本呼叫台式机,然后台式机显示笔记本端采集的视频。

好,下面进入实际案例

客户端

创建node.js项目,在项目文件下创建alice.html(呼叫者页面)文件,代码如下

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>alice</title>
        <link rel="stylesheet" href="css/main.css">
    </head>
    <body>
        <div class="container">
            <h1>ailce</h1>
            <hr>
            <div class="video_container" align="center">
                <video id="local_video"  poster="img/video_fill.jpg" autoplay muted></video>
            </div>
            <hr>
            <button id="startButton">获取本地视频</button>
            <button id="callButton">呼叫bob</button>
            <button id="hangupButton">挂断</button>
            <script src="/socket.io/socket.io.js"></script>
            <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
            <script src="js/alice.js"></script>
        </div>
    </body>
</html>

创建js文件夹,在其文件夹下创建alice.js文件,打开文件键入如下代码:

'use strict'

var localVideo = document.getElementById('local_video');

var startButton = document.getElementById('startButton');
var callButton = document.getElementById('callButton');
var hangupButton = document.getElementById('hangupButton');

var pc;
var localStream;
var socket = io.connect();

var config = {
    'iceServers': [{
        'urls': 'stun:stun.l.google.com:19302'
    }]
};

const offerOptions = {
    offerToReceiveVideo: 1,
    offerToReceiveAudio: 1
};

callButton.disabled = true;
hangupButton.disabled = true;

startButton.addEventListener('click', startAction);
callButton.addEventListener('click', callAction);
hangupButton.addEventListener('click', hangupAction);

function startAction() {
    navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(function (mediastream) {
        localStream = mediastream;
        localVideo.srcObject = mediastream;
        startButton.disabled = true;
    }).catch(function (e) {
        console.log(JSON.stringify(e));
    });
}

socket.on('create', function (room, id) {
    console.log('alice创建聊天房间');
    console.log(room + id);
});

socket.on('call', function () {
    callButton.disabled = false;
});

socket.on('signal', function (message) {
    if (pc !== 'undefined') {
        pc.setRemoteDescription(new RTCSessionDescription(message));
        console.log('remote answer');
    }
});

socket.on('ice', function (message) {
    if (pc !== 'undefined') {
        pc.addIceCandidate(new RTCIceCandidate(message));
        console.log('become candidate');
    }
});

socket.emit('create or join', 'room');

function callAction() {
    callButton.disabled = true;
    hangupButton.disabled = false;
    pc = new RTCPeerConnection(config);
    localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
    pc.createOffer(offerOptions).then(function (offer) {
        pc.setLocalDescription(offer);
        socket.emit('signal', offer);
    });
    pc.addEventListener('icecandidate', function (event) {
        var iceCandidate = event.candidate;
        if (iceCandidate) {
            socket.emit('ice', iceCandidate);
        }
    });
}

function hangupAction() {

    localStream.getTracks().forEach(track => track.stop());
    pc.close();
    pc = null;
    hangupButton.disabled = true;
    callButton.disabled = true;
    startButton.disabled = false;

}

创建bob.html文件(被呼叫端页面),编写如下代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>对方的视频</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="css/main.css">
    </head>
    <body>
        <div class="container">
            <h1>对方的视频</h1>
            <hr>
            <div class="video_container" align="center">
                <video id="remote_video" poster="img/video_fill.jpg" controls autoplay></video>
            </div>
            <hr>
            <script src="/socket.io/socket.io.js"></script>
            <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
            <script src="js/bob.js"></script>
        </div>
    </body>
</html>

在js文件中创建bob.js文件,编写如下代码:

'use strict'

var remoteVideo = document.getElementById('remote_video');

var socket = io.connect();

var config = {
    'iceServers': [{
        'urls': 'stun:stun.l.google.com:19302'
    }]
};

var pc;

socket.emit('create or join', 'room');

socket.on('join', function (room, id) {
    console.log('bob加入房间');
});

socket.on('signal', function (message) {
    pc = new RTCPeerConnection(config);
    pc.setRemoteDescription(new RTCSessionDescription(message));
    pc.createAnswer().then(function (answer) {
        pc.setLocalDescription(answer);
        socket.emit('signal', answer);
    });

    pc.addEventListener('icecandidate', function (event) {
        var iceCandidate = event.candidate;
        if (iceCandidate) {
            socket.emit('ice', iceCandidate);
        }
    });

    pc.addEventListener('addstream', function (event) {
        remoteVideo.srcObject = event.stream;
    });
});

socket.on('ice', function (message) {
    pc.addIceCandidate(new RTCIceCandidate(message));
});

至此,呼叫端和被呼叫端代码编写完成。

信令转发服务器

引入socket.io模块(sockio.io是一个开源的及时通讯框架)

npm install socket.io

新建index.js文件,编写如下代码:

'use strict'

var express = require('express');
var app = express();

var http  = require('http').createServer(app);
var io = require('socket.io')(http);

app.use('/css',express.static('css'));
app.use('/js',express.static('js'));
app.use('/img',express.static('img'));

app.get('/',function(request,response){
    response.sendFile(__dirname +'/index.html');
});

app.get('/alice',function(request,response){
    response.sendFile(__dirname+"/alice.html")
});

app.get('/bob',function(request,response){
    response.sendFile(__dirname+"/bob.html")
});

io.on('connection',function(socket){
    console.log('有用户加入进来');
    socket.on('signal',function(message){
        socket.to('room').emit('signal',message);
    });

    socket.on('ice',function(message){
        socket.to('room').emit('ice',message);
    });

    socket.on('create or join',function(room){
        var clientsInRoom = io.sockets.adapter.rooms[room];
        var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
        console.log(numClients);
        if(numClients===0){
            socket.join(room);
            socket.emit('create', room, socket.id);
            console.log('caller joined');
        }else if(numClients===1){
            socket.join(room);
            socket.to('room').emit('call');
            console.log('callee joined');
        }
    });
});

var server = http.listen(8080,function(){
    var host = server.address().address;
    var port = server.address().port;
    console.log('listening on:http://s%:s%',host,port);
});

至此,案例代码编写完成

测试结果

命令行 启动项目,打开chrome,地址栏输入localhost:8080/alice,打开alice页面(呼叫者),点击获取本地视频按钮,显示内容如下:

alice.png

打开bob(被呼叫者)页面,显示内容如下:

bob.png

被呼叫者上线后,呼叫者页面呼叫按钮可用,


call.png

点击呼叫bob,显示效果如下:

callee.png

总结

本章完成了局域网不同设备呼叫的案例。引入了socket.io框架,为信令转发提供服务,对信令转发的认识更加清晰了。在建立RTCPeerConnection()对象的时候,配置了iceServer,学名叫stun服务,这是一个协助p2p连接的服务。搭建stun服务,开源项目有coturn。
下面将在局域网打通android手机端和笔记本之间的连接。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容

  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,855评论 0 5
  • 单机版视频呼叫 前端代码 1、新建node.js项目,在项目文件夹下新建index.html打开,编写如下代码: ...
    EarthNut阅读 3,531评论 0 4
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,456评论 0 13
  • 加密货币,特别是比特币,几乎从各个方面都得到了大量关注:规则、管理、税务、技术、产品创新等等,不胜枚举。“点对点(...
    简闻阅读 671评论 0 9
  • 咋天吃洒喝高了,睡的不醒耽误更新了。 今天更新说什么呢?就说吃酒。 昨天吃的是青水花苗尚部的嫁女酒。种在竹子里的竹...
    歪才大白话阅读 412评论 2 9