WebRTCのフルネームは次のとおりです。WebReal - TimeCommunication。これは、オーディオとビデオをキャプチャするWeb端末の機能を解決し、ピアツーピア(つまり、ブラウザ間)のビデオインタラクションを提供することです。実際、内訳は3つの部分で構成されているように見えます。
MediaStream:オーディオおよびビデオストリームの
キャプチャRTCPeerConnection:オーディオおよびビデオストリームの送信(通常はピアツーピアシナリオで
使用)RTCDataChannel:オーディオおよびビデオバイナリデータのアップロードに使用(通常はストリームのアップロードに使用)
MediaStreamは、カメラ、マイク、または画面の画像を取得するために使用されます。
var constraints = {
audio: true,
video: true
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(console.log())
これは非同期メソッドです。コールバック関数handleSuccessは、デバイスが正常に取得された後に実行されます。この関数のパラメーターは、オーディオおよびビデオストリームです。オーディオおよびビデオストリームは、コールバック関数で再生するためにビデオタグに入れられます。
function handleSuccess(stream) {
document.getElementById("video").srcObject = stream
}
これは、ブラウザで再生するためのオーディオおよびビデオ画像を取得するための最も基本的な方法です。
RTCPeerConnectionは、UDPオーディオおよびビデオ送信の接続を確立するために使用されます(STUNサーバーの場合)。STUNファイアウォールに障害が発生した場合は、TCP送信が必要です(転送のリレーとしてTURNサーバーが必要です)。もちろん、これらの操作は必須ではありません。自分で実行してください。webtrcのAPIが実装されています。つまりICEです。
ICE:率直に言って、ICEは、サポートできる送信方法の選択を支援します(複数ある場合があり、すべてが反対側に送信され、反対側が送信に応じて最適な送信方法を選択します)サポートされている方法。ネットワークネゴシエーション)。ただし、ICEのSTUNサーバーアドレスとTURNのサーバーアドレスを構成する必要があります。
ネットワークネゴシエーションに加えて、メディアネゴシエーションSDPもあります。メディアネゴシエーションは、サポートされているビデオコーデック機能、メディア形式、およびその他の情報について両当事者に通知することです。このプロセスのwebrtcAPIもカプセル化されています。例としてポイントツーポイント送信を取り上げます。パーティAは独自のSDP情報を取得し、独自のローカルSDPを設定してから、エンドB(このプロセスではエンドA)に送信します。イニシエーターとして機能し、オファーを送信します)、エンドBはエンドAからSPD(オファー)を受信し、エンドBは反対側のSPDを保存し、エンドBは独自のSDPを取得し、独自のローカルSPDを保存し、エンドBエンドAのオファーに応答します(つまり、エンドB自身のSDPこの応答が回答であるとエンドAに通知します。これは実際にはSDPです)。エンドAがエンドBから応答を受信すると、エンドAはエンドBのSDPを保存します。
これまでのところ、メディアの交渉は完了しています。
上記の2つのプロセスは順不同ですが、どちらもダイアログ送信の中間サーバーとしてWebSocketを必要とします。実装する方法はたくさんあります。Pythonを使用してdjango + channelsを実装してwebsocketを実装します。
フロントエンドコードは次のとおりです(ロジックコントロールを書きすぎないでください。そのため、最初にレシーバーを開いて、WebSocketがイニシエーターの情報を送信するときに部屋にレシーバーがあることを確認する必要があります)。
私のwebsocketサービスは(チャットルームで使用される)独自のメッセージを自分自身に転送するため、ここに判断が追加されます。判断が行われない場合、webrtcは自分自身とネゴシエートします。
受信終了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>接收端</title>
</head>
<body>
<video id="video" autoplay style="height: 600px;width: 800px" muted></video>
<video id="video2" autoplay style="height: 600px;width: 800px"></video>
<script>
//生成唯一id,用于websocket判断是否是自己
//生成唯一ID
function guid() {
return Number(Math.random().toString().substr(3, 3) + Date.now()).toString(36);
}
var id = guid();
//首先建立websocket连接
socket = new WebSocket("ws://这里就用自己的websocket服务器地址/");
socket.onopen = onOpen;
socket.onclose = onClose;
// 连接成功
function onOpen() {
console.log("websocket连接成功")
}
function onClose() {
console.log("websocket已经断开")
}
function sendCandidate(ICE) {
socket.send(JSON.stringify({
'message': {
'ICE': ICE, 'ID': id}
}))
console.log("B端发送ICE到服务器")
}
function sendAnswer(answer) {
socket.send(JSON.stringify({
'message': {
'SDP': answer, 'ID': id}
}))
console.log("B端收到A端offer(SDP),回发answer(SDP)成功")
}
socket.onmessage = function (e) {
const data = JSON.parse(e.data).message;
if (data.ID != id) {
try {
var ice = data.ICE
remoteConnection.addIceCandidate(new RTCIceCandidate(ice))
console.log("收到远端ICE候选,并添加")
} catch (e) {
var sdp = data.SDP
let desc = new RTCSessionDescription(sdp)
remoteConnection.setRemoteDescription(desc).then(function () {
remoteConnection.createAnswer().then((answer) => {
remoteConnection.setLocalDescription(answer).then(sendAnswer(answer))
})
})
}
}
}
var iceServer = {
iceServer: [
{
url: "stun:stun.l.google.com:19302"},//谷歌的公共服务器
]
}
var remoteConnection
var constraints = {
audio: true,
video: true
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(console.log())
function handleSuccess(stream) {
document.getElementById("video").srcObject = stream
remoteConnection = new RTCPeerConnection(iceServer)
remoteConnection.addStream(stream)
remoteConnection.onaddstream = function (e) {
console.log('获得发起方的视频流' + e.stream)
document.getElementById("video2").srcObject = e.stream
}
remoteConnection.onicecandidate = function (event) {
if (event.candidate) {
sendCandidate(event.candidate)
}
}
}
</script>
</body>
</html>
イニシエータ
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>发起端</title>
</head>
<body>
<video id="video" autoplay style="height: 600px;width: 800px" muted></video>
<video id="video2" autoplay style="height: 600px;width: 800px"></video>
<script>
//生成唯一id,用于websocket判断是否是自己
//生成唯一ID
function guid() {
return Number(Math.random().toString().substr(3, 3) + Date.now()).toString(36);
}
var id = guid();
//首先建立websocket连接
socket = new WebSocket("ws://这里就用自己的websocket服务器地址/");
socket.onopen = onOpen;
socket.onclose = onClose;
// 连接成功
function onOpen() {
console.log("websocket连接成功")
}
// 关闭连接
function onClose() {
console.log("websocket已经断开")
}
//发送ICE
function sendCandidate(ICE) {
//获取到本地ice之后发送到websocket
socket.send(JSON.stringify({
'message': {
'ICE': ICE, 'ID': id}
}))
console.log("A端发送ICE到服务器")
}
//发送SDP
function sendOffer(offer) {
//设置完本地的SDP之后发送到websocket
socket.send(JSON.stringify({
'message': {
'SDP': offer, 'ID': id}
}))
console.log("A端发送offer(SDP)到服务器")
}
// 收到消息,判断消息类型是ICE还是SDP
socket.onmessage = function (e) {
const data = JSON.parse(e.data).message;
if (data.ID != id) {
try {
var ice = data.ICE
localConnection.addIceCandidate(new RTCIceCandidate(ice))
} catch (e) {
var sdp = data.SDP
let desc = new RTCSessionDescription(sdp) //构建SDP对象
localConnection.setRemoteDescription(desc).then(() => {
//设置远端SDP
console.log('端对端连接成功')
})
}
}
}
var iceServer = {
iceServer: [
{
url: "stun:stun.l.google.com:19302"},//谷歌的公共服务器
]
}
var localConnection
var constraints = {
audio: true,
video: true
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(console.log())
function handleSuccess(stream) {
document.getElementById("video").srcObject = stream
localConnection = new RTCPeerConnection(iceServer) //获取连接对象 ,应填写stun服务器
localConnection.addStream(stream) //将本地流添加进连接对象
localConnection.onaddstream = function (e) {
//获得对方流触发函数
console.log('获得应答方的视频流' + e.stream)
document.getElementById("video2").srcObject = e.stream
}
localConnection.onicecandidate = function (event) {
// 用来寻找合适的ICE,获得合适的ICE时触发
if (event.candidate) {
sendCandidate(event.candidate) // 通过websocket将ICE发送给端,自己实现的函数
console.log(event.candidate)
}
}
localConnection.createOffer().then((offer) => {
//创建offer 成功之后更改与对端关联的本地描述(SDP),包括本端属性包括媒体格式
localConnection.setLocalDescription(offer).then(sendOffer(offer)) // 之后发送SDP信息到对端,sendOffer自己实现
})
}
</script>
</body>
</html>
このプロセスは、写真で説明するのに非常に直感的です。
私が使用した例では、STUNサーバーは無料です。TURNサーバーが必要な場合は、coturnを使用して実装できます(coturnにはSTUNとTURNの実装が含まれます)。