概述:
1)媒体捕获设备包括摄像机和麦克风,还包括屏幕捕获“设备”。对于相机和麦克风,我们使用navigator.mediaDevices.getUserMedia()
捕获MediaStreams
。对于屏幕录制,我们改用navigator.mediaDevices.getDisplayMedia()
。
const constraints = {
'video': true,
'audio': true
}
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
console.log('Got MediaStream:', stream);
})
.catch(error => {
console.error('Error accessing media devices.', error);
});
function getConnectedDevices(type, callback) {
navigator.mediaDevices.enumerateDevices()
.then(devices => {
const filtered = devices.filter(device => device.kind === type);
callback(filtered);
});
}
getConnectedDevices('videoinput', cameras => console.log('Cameras found', cameras));
2)确定某个媒体流的特定轨道的实际配置,我们可以调用MediaStreamTrack.getSettings()
来返回当前应用的MediaTrackSettings
3)getDisplayMedia()
的约束与用于常规视频或音频输入的约束不同:
{
video: {
cursor: 'always' | 'motion' | 'never',
displaySurface: 'application' | 'browser' | 'monitor' | 'window'
}
}
4)启动对等连接:
async function makeCall() {
const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
if (message.answer) {
const remoteDesc = new RTCSessionDescription(message.answer);
await peerConnection.setRemoteDescription(remoteDesc);
}
});
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
signalingChannel.send({'offer': offer});
const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
if (message.offer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
signalingChannel.send({'answer': answer});
}
});
5)ICE candidate 操作:
peerConnection.addEventListener('icecandidate', event => {
if (event.candidate) {
signalingChannel.send({'new-ice-candidate': event.candidate});
}
});
// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
if (message.iceCandidate) {
try {
await peerConnection.addIceCandidate(message.iceCandidate);
} catch (e) {
console.error('Error adding received ice candidate', e);
}
}
});
RTCPeerConnection
接口代表一个由本地计算机到远端的WebRTC连接。该接口提供了创建,保持,监控,关闭连接的方法的实现。
var PeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection;
var SessionDescription = window.RTCSessionDescription
var GET_USER_MEDIA = navigator.getUserMedia
一旦收到ICE候选者,我们应该期望对等连接的状态最终将变为已连接状态。为了检测到这一点,我们在RTCPeerConnection
中添加了一个侦听器,在此我们侦听connectionstatechange
事件。
// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
if (peerConnection.connectionState === 'connected') {
// Peers connected!
}
});
远程流:
6)我们在本地RTCPeerConnection
上注册了一个侦听器,以监听track
事件。由于回放是在MediaStream
对象上完成的,因此我们首先创建一个空实例,然后在接收到它们时使用来自远程对等方的轨道进行填充。
const remoteStream = MediaStream();
const remoteVideo = document.querySelector('#remoteVideo');
remoteVideo.srcObject = remoteStream;
peerConnection.addEventListener('track', async (event) => {
remoteStream.addTrack(event.track, remoteStream);
});
7)数据通道:
远程对等方可以通过侦听RTCPeerConnection
对象上的datachannel
事件来接收数据通道。接收到的事件的类型为RTCDataChannelEvent
并包含一个channel
属性,该属性表示对等体之间连接的RTCDataChannel
。
const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();
远程对等方可以通过侦听RTCPeerConnection
对象上的datachannel
事件来接收数据通道。接收到的事件的类型为RTCDataChannelEvent
并包含一个channel
属性,该属性表示对等体之间连接的RTCDataChannel。
const peerConnection = new RTCPeerConnection(configuration);
peerConnection.addEventListener('datachannel', event => {
const dataChannel = event.channel;
});
8)打开和关闭事件:
const messageBox = document.querySelector('#messageBox');
const sendButton = document.querySelector('#sendButton');
const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();
// Enable textarea and button when opened
dataChannel.addEventListener('open', event => {
messageBox.disabled = false;
messageBox.focus();
sendButton.disabled = false;
});
// Disable input when closed
dataChannel.addEventListener('close', event => {
messageBox.disabled = false;
sendButton.disabled = false;
});
9)留言内容:
const messageBox = document.querySelector('#messageBox');
const sendButton = document.querySelector('#sendButton');
// Send a simple text message when we click the button
sendButton.addEventListener('click', event => {
const message = messageBox.textContent;
dataChannel.send(message);
}
远程对等方将通过侦听message
事件来接收在RTCDataChannel
上message
:
const incomingMessages = document.querySelector('#incomingMessages');
const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();
// Append new messages to the box of incoming messages
dataChannel.addEventListener('message', event => {
const message = event.data;
incomingMessages.textContent += message + '\n';
});
10)TURN服务器
需要服务器来中继对等方之间的流量,因为在客户端之间通常不可能使用直接套接字(除非它们驻留在同一本地网络上):
const iceConfiguration = {
iceServers: [
{
urls: 'turn:my-turn-server.mycompany.com:19403',
username: 'optional-username',
credentials: 'auth-token'
}
]
}
const peerConnection = new RTCPeerConnection(iceConfiguration);
https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API
https://webrtc.org/getting-started/data-channels