目录
git clone https://github.com/googlecodelabs/webrtc-web
源码的Step01跑一下,浏览器获取前置摄像头就能成功,不展示具体效果了,看看源码和一些其他的应用
源码项目所给的代码结构,多是如下图,所以常会看到js/main.js
css/main.css
这种src
<!-- core src code of index.html -->
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<!-- add video and script element in this .html file -->
<video autoplay playsinline></video>
<script src="js/main.js"></script>
</body>
/* core src code of main.css */
body {
font-family: sans-serif;
}
video {
max-width: 100%;
width: 800px;
}
html
css
作为标记型语言,了解其基本语法特征与调用(我是通过阅读DOM Sripting的前三章后比较清楚的,阅读这部分还有一个好处是,把我不理解的简洁代码到页面奇幻效果的转化,推锅给了DOM和浏览器厂商~),上面的两个代码就不难理解,着重分析下面js
代码'use strict';
// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
video: true,
};
// Video element where stream will be placed.
const localVideo = document.querySelector('video');
// Local stream that will be reproduced on the video.
let localStream;
// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
localStream = mediaStream;
localVideo.srcObject = mediaStream;
}
// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
console.log('navigator.getUserMedia error: ', error);
}
// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
gotLocalMediaStream()
处理视频流函数handleLocalMediaStreamError()
异常处理函数getUserMedia()
调用navigator.mediaDevices.getUserMedia(constraints)
/* produces a MediaStream */
.then(function(stream) {
/* use the stream */
})
.catch(function(err) {
/* handle the error */
});
先看mediaStream
的相关API
The MediaStream interface represents a stream of media content. A stream consists of several tracks such as video or audio tracks. Each track is specified as an instance of MediaStreamTrack
看代码,从整个main.js
文件中,我没有看出let localStream
有什么特殊的用途,这一行注释掉对网页也没有什么影响(也许在之后的源码中有用)
但17行的代码就相当关键了(可以把这一行的代码注释看看是个什么效果~获取了媒体流,但是网页上没有视频显示)
const localVideo = document.querySelector(‘video‘)
说起
const
只读变量querySelector(selectors)
也正是基于树形数据结构,来对document
中的 object
进行深度优先的前序遍历,来获取document
中符合selectors
的HTMLElement
并返回
The matching is done using depth-first pre-order traversal of the document‘s nodes starting with the first element in the document‘s markup and iterating through sequential nodes by order of the number of child nodes.
17行的HTMLMediaElement.srcObject 则是对‘video‘
流媒体的赋值,使页面显示video
video: true
)getUserMedia()
的理解<body>
<h1>Realtime communication with WebRTC</h1>
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>
</body>
<head>
以及调用main.css
的部分和Step-01相比几乎没有改变video
button
script
其id
& src
命名都有很好的解释说明效果,在下文对main.js
的分析中,相关内容会有更清楚的解释button
开始,这是代码183-192行// Define and add behavior to buttons.
// Define action buttons.
const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
// Set up initial action buttons status: disable call and hangup.
callButton.disabled = true;
hangupButton.disabled = true;
querySelector()
有类似功能,比较清晰// Add click event handlers for buttons.
startButton.addEventListener('click', startAction);
callButton.addEventListener('click', callAction);
hangupButton.addEventListener('click', hangupAction);
startAction()
callAction()
hangupAction()
这三个函数startAction
开始// Handles start button action: creates local MediaStream.
function startAction() {
startButton.disabled = true;
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
trace('Requesting local stream.');
}
startButton
只能click一次,之后获取getUserMedia()
mediaStreamConstraints()
函数几乎没有变化,gotLocalMediaStream()
& handleLocalMediaStreamError()
有些许变化,在19-43部分行// Define peer connections, streams and video elements.
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
let localStream;
let remoteStream;
// Define MediaStreams callbacks.
// Sets the MediaStream as the video element src.
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
localStream = mediaStream;
trace('Received local stream.');
callButton.disabled = false; // Enable call button.
}
// Handles error by logging a message to the console.
function handleLocalMediaStreamError(error) {
trace(`navigator.getUserMedia error: ${error.toString()}.`);
}
trace()
函数比较新颖,看看// Logs an action (text) and the time when it happened on the console.
function trace(text) {
text = text.trim();
const now = (window.performance.now() / 1000).toFixed(3);
console.log(now, text);
}
callAction()
,代码203-246行// Code from line 16-17
// Define initial start time of the call (defined as connection between peers).
let startTime = null;
// Code from line19-27
// Define peer connections
let localPeerConnection;
let remotePeerConnection;
// Handles call button action: creates peer connection.
function callAction() {
callButton.disabled = true; // disenable call button
hangupButton.disabled = false; // enable hangup button
trace('Starting call.');
startTime = window.performance.now(); // assign startTime with concrete time
// Get local media stream tracks.
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; // Allows for RTC server configuration.
// Create peer connections and add behavior.
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);
// Add local stream to connection and create offer to connect.
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
}
RTCPeerConnection
,解析下面所说的三个步骤,以建立连接时序展开Setting up a call between WebRTC peers involves three tasks:
- Create a RTCPeerConnection for each end of the call and, at each end, add the local stream from getUserMedia().
- Get and share network information: potential connection endpoints are known as ICE candidates.
- Get and share local and remote descriptions: metadata about local media in SDP format.
getUserMedia()
部分,不再赘述 let localPeerConnection;
const servers = null; // Allows for RTC server configuration. This is where you could specify STUN and TURN servers.
// Create peer connections and add behavior.
localPeerConnection = new RTCPeerConnection(servers);
remotePeerConnection = new RTCPeerConnection(servers);
// Add local stream to connection and create offer to connect.
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
addStream()
之后,可以认为Local & Remote Peer已经全部建好(RTCPeerConnection实例化成功,media传输也可以开始进行) localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
addEventListener()
method在button相关中已经了解,关于‘icecandidate‘
Event,看RTCPeerConnection: icecandidate event,而其中的setLocalDescription()
在下面一个section中有介绍 remotePeerConnection.addEventListener('icecandidate', handleConnection);
remotePeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
remotePeerConnection.addEventListener('addstream', gotRemoteMediaStream);
// Connects with new peer 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}.`);
}
}
// Logs changes to the connection state.
function handleConnectionChange(event) {
const peerConnection = event.target;
console.log('ICE state change event: ', event);
trace(`${getPeerName(peerConnection)} ICE state: ` +
`${peerConnection.iceConnectionState}.`);
}
// Handles remote MediaStream success by adding it as the remoteVideo src.
function gotRemoteMediaStream(event) {
const mediaStream = event.stream;
remoteVideo.srcObject = mediaStream;
remoteStream = mediaStream;
trace('Remote peer connection received remote stream.');
}
gotRemoteMediaStream()
函数,最终将Local Peer的addStream()
显示 trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
offerOptions
createdOffer()
setSessionDescriptionError()
这三个对应内容// Set up to exchange only video.
const offerOptions = {
offerToReceiveVideo: 1,
};
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
trace(`Offer from localPeerConnection:\n${description.sdp}`);
trace('localPeerConnection setLocalDescription start.');
localPeerConnection.setLocalDescription(description)
.then(() => { // The parameter list for a function with no parameters should be written with a pair of parentheses.
setLocalDescriptionSuccess(localPeerConnection);
// just logs successful info on the console
}).catch(setSessionDescriptionError);
}
// Logs error when setting session description fails.
function setSessionDescriptionError(error) {
trace(`Failed to create session description: ${error.toString()}.`);
}
createOffer()
函数setLocalDescription
,API中也没有讲的特别清楚,简单的说,可以认为这个函数经过调用后,Local Peer的offer就发送成功(可参见RTCPeerConnection.signalingState),但实际上发送的信息是什么、向谁发...等一系列问题,都是在官方教程中的源码里面未涉及的,这部分我写在了WebRTC的RTCPeerConnection()原理探析中 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);
}
setRemoteDescription
的API,Local Peer与Remote Peer之间的互相通信基本建立了hangup
button对应什么函数// Handles hangup action: ends up call, closes connections and resets peers.
function hangupAction() {
localPeerConnection.close();
remotePeerConnection.close();
localPeerConnection = null;
remotePeerConnection = null;
hangupButton.disabled = true;
callButton.disabled = false;
trace('Ending call.');
}
<textarea id="dataChannelSend" disabled
placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>
var startButton = document.querySelector('button#startButton');
var sendButton = document.querySelector('button#sendButton');
var closeButton = document.querySelector('button#closeButton');
startButton.onclick = createConnection;
sendButton.onclick = sendData;
closeButton.onclick = closeDataChannels;
var localConnection;
var remoteConnection;
var sendChannel;
var dataConstraint;
var dataChannelSend = document.querySelector('textarea#dataChannelSend');
// Offerer side
function createConnection() {
dataChannelSend.placeholder = '';
var servers = null;
pcConstraint = null;
dataConstraint = null;
trace('Using SCTP based data channels');
// For SCTP, reliable and ordered delivery is true by default.
// Add localConnection to global scope to make it visible
// from the browser console.
window.localConnection = localConnection =
new RTCPeerConnection(servers, pcConstraint); // constructor
trace('Created local peer connection object localConnection');
sendChannel = localConnection.createDataChannel('sendDataChannel',
dataConstraint);
trace('Created send data channel');
localConnection.onicecandidate = iceCallback1;
sendChannel.onopen = onSendChannelStateChange;
sendChannel.onclose = onSendChannelStateChange;
// Add remoteConnection to global scope to make it visible
// from the browser console.
window.remoteConnection = remoteConnection =
new RTCPeerConnection(servers, pcConstraint);
trace('Created remote peer connection object remoteConnection');
remoteConnection.onicecandidate = iceCallback2;
remoteConnection.ondatachannel = receiveChannelCallback;
localConnection.createOffer().then(
gotDescription1,
onCreateSessionDescriptionError
);
startButton.disabled = true;
closeButton.disabled = false;
}
function iceCallback1(event) {
trace('local ice callback');
if (event.candidate) {
remoteConnection.addIceCandidate(
event.candidate
).then(
onAddIceCandidateSuccess,
onAddIceCandidateError
);
trace('Local ICE candidate: \n' + event.candidate.candidate);
}
}
function iceCallback2(event) {
trace('remote ice callback');
if (event.candidate) {
localConnection.addIceCandidate(
event.candidate
).then(
// print out info on the console
onAddIceCandidateSuccess,
onAddIceCandidateError
);
trace('Remote ICE candidate: \n ' + event.candidate.candidate);
}
}
function onSendChannelStateChange() {
var readyState = sendChannel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState === 'open') {
dataChannelSend.disabled = false;
dataChannelSend.focus();
sendButton.disabled = false;
closeButton.disabled = false;
} else {
dataChannelSend.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
先看几个API,RTCPeerConnection.createDataChannel()
The createDataChannel() method on the RTCPeerConnection interface creates a new channel linked with the remote peer, over which any kind of data may be transmitted. This can be useful for back-channel content such as images, file transfer, text chat, game update packets, and so forth.
RTCPeerConnection.onicecandidate
This happens whenever the local ICE agent needs to deliver a message to the other peer through the signaling server. This lets the ICE agent perform negotiation with the remote peer without the browser itself needing to know any specifics about the technology being used for signaling; simply implement this method to use whatever messaging technology you choose to send the ICE candidate to the remote peer.
The read-only property candidate on the RTCIceCandidate interface returns a DOMString describing the candidate in detail. Most of the other properties of RTCIceCandidate are actually extracted from this string.
When a web site or app using RTCPeerConnection receives a new ICE candidate from the remote peer over its signaling channel, it delivers the newly-received candidate to the browser‘s ICE agent by calling RTCPeerConnection.addIceCandidate(). This adds this new remote candidate to the RTCPeerConnection‘s remote description, which describes the state of the remote end of the connection.
The RTCDataChannel.onopen property is an EventHandler which specifies a function to be called when the open event is fired; this is a simple Event which is sent when the data channel‘s underlying data transport—the link over which the RTCDataChannel‘s messages flow—is established or re-established.
The HTMLElement.focus() method sets focus on the specified element, if it can be focused. The focused element is the element which will receive keyboard and similar events by default.
createOffer()
createAnswer()
部分,在学习完RTCPeerConnection之后,是非常容易的,所以不赘述function sendData() {
var data = dataChannelSend.value;
sendChannel.send(data);
trace('Sent Data: ' + data);
}
function closeDataChannels() {
trace('Closing data channels');
sendChannel.close();
trace('Closed data channel with label: ' + sendChannel.label);
receiveChannel.close();
trace('Closed data channel with label: ' + receiveChannel.label);
localConnection.close();
remoteConnection.close();
localConnection = null;
remoteConnection = null;
trace('Closed peer connections');
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
dataChannelSend.value = '';
dataChannelReceive.value = '';
dataChannelSend.disabled = true;
disableSendButton();
enableStartButton();
}
function enableStartButton() {
startButton.disabled = false;
}
function disableSendButton() {
sendButton.disabled = true;
}
原文:https://www.cnblogs.com/yzy11235/p/11439933.html