效果图大概是这样,因为不想曝光,放一个网图
直接上页面和源码
index.html
复制URL发送给对方,复制到浏览器即可通话
js
//产生随机数
if (!location.hash) {
location.hash = Math.floor(Math.random() * 0xFFFFFF).toString(16);
}
//获取房间号
const roomHash = location.hash.substring(1);
//放置自己的频道id
const drone = new ScaleDrone('yiS12Ts5RdNhebyM');
//房间名必须以'observable-'开头
const roomName = 'observable-' + roomHash;
//使用谷歌的stun服务,也可以自己部署
const configuration = {
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
};
let room;
let pc;
function onSuccess() {};
function onError(error) {
console.error(error);
};
drone.on('open', error => {
if (error) {
return console.error(error);
}
room = drone.subscribe(roomName);
room.on('open', error => {
if (error) {
onError(error);
}
});
//我们连接到房间后会收到一个'members'数组,代表房间里的成员
//这个时候信令服务已经就绪
room.on('members', members => {
console.log('MEMBERS', members);
// 如果是第二个进入房间的人,就会创建offer
const isOfferer = members.length === 2;
startWebRTC(isOfferer);
});
});
//通过scaledrone发送信令
function sendMessage(message) {
drone.publish({
room: roomName,
message
});
}
function startWebRTC(isOfferer) {
pc = new RTCPeerConnection(configuration);
//当本地ICE Agent需要通过信号服务器发送消息到其他端时,会触发icecandidate事件回调
pc.onicecandidate = event => {
if (event.candidate) {
sendMessage({'candidate': event.candidate});
}
};
//如果是第二个进入的人,就在negotiationneded事件后创建sdp
if (isOfferer) {
pc.onnegotiationneeded = () => {
pc.createOffer().then(localDescCreated).catch(onError);
}
}
//当远程数据流到达时将数据流装载到video中
pc.ontrack = event => {
const stream = event.streams[0];
if (!remoteVideo.srcObject || remoteVideo.srcObject.id !== stream.id) {
remoteVideo.srcObject = stream;
}
};
//获取本地媒体流
navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
}).then(stream => {
//将本地捕获的视频流装载到本地video中
localVideo.srcObject = stream;
//将本地流加入RTCPeerConnection 实例中 发送到其他端
stream.getTracks().forEach(track => pc.addTrack(track, stream));
}, onError);
//从Scaledrone监听信令数据
room.on('data', (message, client) => {
//自己发送的消息不处理
if (client.id === drone.clientId) {
return;
}
if (message.sdp) {
//设置远程sdp, 在offer 或者 answer后
pc.setRemoteDescription(new RTCSessionDescription(message.sdp), () => {
//当收到offer 后就接听
if (pc.remoteDescription.type === 'offer') {
pc.createAnswer().then(localDescCreated).catch(onError);
}
}, onError);
} else if (message.candidate) {
//增加新的 ICE canidatet 到本地的链接中
pc.addIceCandidate(
new RTCIceCandidate(message.candidate), onSuccess, onError
);
}
});
}
function localDescCreated(desc) {
pc.setLocalDescription(
desc,
() => sendMessage({'sdp': pc.localDescription}),
onError
);
}
本例子是参考webrtc-tutorial-simple-video-chat做的。
问题有两个
这个问题我用的是网上公开的stun服务器,但缺点是stun服务器响应比较慢.,如果有需求可以自己在服务器上部署一个,这就不多说了。
放一段安装和配置的说明,有兴趣的可以试一下
1. coturn 的底层网络部分依赖libevent. 所以需要先安装libevent2, 地址在此 http://libevent.org/
2. coturn的安装很简单. configure make make install 三部曲就完事了.
3. coturn的文档说明挺详细的. 但比较多. 我只是大概说明下.
更具体的说明可以看 源码目录下的README.turnserver README.turnadmin README.turnutils
在bin目录下生成六个可执行文件
turnadmin turnutils_peer turnutils_stunclient
turnserver turnutils_rfc5769check turnutils_uclient
turnserver 就是我们需要的服务器.
turnadmin 用来管理账户.
turnutils_stunclient 用于测试stun服务
turnutils_uclient 用于测试turn服务. 模拟多个UDP,TCP,TLS or DTLS 类型的客户端
example 目录主要是示范如何配置和使用turn. 包含一些测试用例.
example/etc 下是pem证书和conf配置文件
example/var/db 下是sqlite的db库. 用于示范数据库的格式.
coturn 支持三种配置. 命令行, conf文件和数据库. 数据库支持sqlite, mysql, postgresql, MongoDB, redis.
examples\scripts 下一些测试用例:
loadbalance 示范如何进行负载均衡. 设置一个master turn server 然后配置若干个slave turn server.
longtermsecure 示范如何使用long-term 验证
longtermsecuredb 与 longtermsecure 类似, 不过是从数据库配置
shorttermsecure 示范如何使用short-term验证.
restapi 示范了web方面的使用.
stun 定义了两种验证方式.
Long-Term Credential
Short-Term Credential
具体可以参考stun标准 http://tools.ietf.org/html/rfc5389#section-15.4
但是对于webrtc而言. 仅支持long-term .
http://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf
TURN REST Server API
这个PDF 描述了. turn服务器和客户端的交互流程.
Only secure origins are allowed (see: https://goo.gl/Y0ZkNV)
对,就是这个错误,一般自己的服务器都是http访问,一般浏览器不会允许不安全的网站调用摄像头,这个时候只能把访问更改为https访问。
部署https需要申请证SSL证书,这里推荐Let's Encrypt证书申请,比较好用。