WebRTC---網路實時通訊

發表於2019-02-22

目錄

重要說明:本教程已經搬遷,此處不再維護,請訪問新網址:wangdoc.com/javascript

概述

WebRTC是“網路實時通訊”(Web Real Time Communication)的縮寫。它最初是為了解決瀏覽器上視訊通話而提出的,即兩個瀏覽器之間直接進行視訊和音訊的通訊,不經過伺服器。後來發展到除了音訊和視訊,還可以傳輸文字和其他資料。

Google是WebRTC的主要支持者和開發者,它最初在Gmail上推出了視訊聊天,後來在2011年推出了Hangouts,語序在瀏覽器中打電話。它推動了WebRTC標準的確立。

WebRTC主要讓瀏覽器具備三個作用。

  • 獲取音訊和視訊
  • 進行音訊和視訊通訊
  • 進行任意資料的通訊

WebRTC共分成三個API,分別對應上面三個作用。

  • MediaStream (又稱getUserMedia)
  • RTCPeerConnection
  • RTCDataChannel

getUserMedia

概述

navigator.getUserMedia方法目前主要用於,在瀏覽器中獲取音訊(通過麥克風)和視訊(通過攝像頭),將來可以用於獲取任意資料流,比如光碟和感測器。

下面的程式碼用於檢查瀏覽器是否支援getUserMedia方法。

navigator.getUserMedia  = navigator.getUserMedia ||
                          navigator.webkitGetUserMedia ||
                          navigator.mozGetUserMedia ||
                          navigator.msGetUserMedia;

if (navigator.getUserMedia) {
    // 支援
} else {
    // 不支援
}複製程式碼

Chrome 21, Opera 18和Firefox 17,支援該方法。目前,IE還不支援,上面程式碼中的msGetUserMedia,只是為了確保將來的相容。

getUserMedia方法接受三個引數。

navigator.getUserMedia({
    video: true, 
    audio: true
}, onSuccess, onError);複製程式碼

getUserMedia的第一個引數是一個物件,表示要獲取哪些多媒體裝置,上面的程式碼表示獲取攝像頭和麥克風;onSuccess是一個回撥函式,在獲取多媒體裝置成功時呼叫;onError也是一個回撥函式,在取多媒體裝置失敗時呼叫。

下面是一個例子。

var constraints = {video: true};

function onSuccess(stream) {
  var video = document.querySelector("video");
  video.src = window.URL.createObjectURL(stream);
}

function onError(error) {
  console.log("navigator.getUserMedia error: ", error);
}

navigator.getUserMedia(constraints, onSuccess, onError);

複製程式碼

如果網頁使用了getUserMedia方法,瀏覽器就會詢問使用者,是否同意瀏覽器呼叫麥克風或攝像頭。如果使用者同意,就呼叫回撥函式onSuccess;如果使用者拒絕,就呼叫回撥函式onError。

onSuccess回撥函式的引數是一個資料流物件stream。stream.getAudioTracks方法和stream.getVideoTracks方法,分別返回一個陣列,其成員是資料流包含的音軌和視軌(track)。使用的聲音源和攝影頭的數量,決定音軌和視軌的數量。比如,如果只使用一個攝像頭獲取視訊,且不獲取音訊,那麼視軌的數量為1,音軌的數量為0。每個音軌和視軌,有一個kind屬性,表示種類(video或者audio),和一個label屬性(比如FaceTime HD Camera (Built-in))。

onError回撥函式接受一個Error物件作為引數。Error物件的code屬性有如下取值,說明錯誤的型別。

  • PERMISSION_DENIED:使用者拒絕提供資訊。
  • NOT_SUPPORTED_ERROR:瀏覽器不支援硬體裝置。
  • MANDATORY_UNSATISFIED_ERROR:無法發現指定的硬體裝置。

範例:獲取攝像頭

下面通過getUserMedia方法,將攝像頭拍攝的影象展示在網頁上。

首先,需要先在網頁上放置一個video元素。影象就展示在這個元素中。

<video id="webcam"></video>複製程式碼

然後,用程式碼獲取這個元素。

function onSuccess(stream) {
    var video = document.getElementById('webcam');
}複製程式碼

接著,將這個元素的src屬性繫結資料流,攝影頭拍攝的影象就可以顯示了。

function onSuccess(stream) {
    var video = document.getElementById('webcam');
    if (window.URL) {
	    video.src = window.URL.createObjectURL(stream);
	} else {
		video.src = stream;
	}

	video.autoplay = true; 
	// 或者 video.play();
}

if (navigator.getUserMedia) {
	navigator.getUserMedia({video:true}, onSuccess);
} else {
	document.getElementById('webcam').src = 'somevideo.mp4';
}複製程式碼

在Chrome和Opera中,URL.createObjectURL方法將媒體資料流(MediaStream)轉為一個二進位制物件的URL(Blob URL),該URL可以作為video元素的src屬性的值。 在Firefox中,媒體資料流可以直接作為src屬性的值。Chrome和Opera還允許getUserMedia獲取的音訊資料,直接作為audio或者video元素的值,也就是說如果還獲取了音訊,上面程式碼播放出來的視訊是有聲音的。

獲取攝像頭的主要用途之一,是讓使用者使用攝影頭為自己拍照。Canvas API有一個ctx.drawImage(video, 0, 0)方法,可以將視訊的一個幀轉為canvas元素。這使得截圖變得非常容易。

<video autoplay></video>
<img src="">
<canvas style="display:none;"></canvas>

<script>
  var video = document.querySelector('video');
  var canvas = document.querySelector('canvas');
  var ctx = canvas.getContext('2d');
  var localMediaStream = null;

  function snapshot() {
    if (localMediaStream) {
      ctx.drawImage(video, 0, 0);
      // “image/webp”對Chrome有效,
      // 其他瀏覽器自動降為image/png
      document.querySelector('img').src = canvas.toDataURL('image/webp');
    }
  }

  video.addEventListener('click', snapshot, false);

  navigator.getUserMedia({video: true}, function(stream) {
    video.src = window.URL.createObjectURL(stream);
    localMediaStream = stream;
  }, errorCallback);
</script>

複製程式碼

範例:捕獲麥克風聲音

通過瀏覽器捕獲聲音,需要藉助Web Audio API。

window.AudioContext = window.AudioContext ||
                      window.webkitAudioContext;

var context = new AudioContext();

function onSuccess(stream) {
	var audioInput = context.createMediaStreamSource(stream);
	audioInput.connect(context.destination);
}

navigator.getUserMedia({audio:true}, onSuccess);

複製程式碼

捕獲的限定條件

getUserMedia方法的第一個引數,除了指定捕獲物件之外,還可以指定一些限制條件,比如限定只能錄製高清(或者VGA標準)的視訊。

var hdConstraints = {
  video: {
    mandatory: {
      minWidth: 1280,
      minHeight: 720
    }
  }
};

navigator.getUserMedia(hdConstraints, onSuccess, onError);

var vgaConstraints = {
  video: {
    mandatory: {
      maxWidth: 640,
      maxHeight: 360
    }
  }
};

navigator.getUserMedia(vgaConstraints, onSuccess, onError);

複製程式碼

MediaStreamTrack.getSources()

如果本機有多個攝像頭/麥克風,這時就需要使用MediaStreamTrack.getSources方法指定,到底使用哪一個攝像頭/麥克風。

MediaStreamTrack.getSources(function(sourceInfos) {
  var audioSource = null;
  var videoSource = null;

  for (var i = 0; i != sourceInfos.length; ++i) {
    var sourceInfo = sourceInfos[i];
    if (sourceInfo.kind === 'audio') {
      console.log(sourceInfo.id, sourceInfo.label || 'microphone');

      audioSource = sourceInfo.id;
    } else if (sourceInfo.kind === 'video') {
      console.log(sourceInfo.id, sourceInfo.label || 'camera');

      videoSource = sourceInfo.id;
    } else {
      console.log('Some other kind of source: ', sourceInfo);
    }
  }

  sourceSelected(audioSource, videoSource);
});

function sourceSelected(audioSource, videoSource) {
  var constraints = {
    audio: {
      optional: [{sourceId: audioSource}]
    },
    video: {
      optional: [{sourceId: videoSource}]
    }
  };

  navigator.getUserMedia(constraints, onSuccess, onError);
}

複製程式碼

上面程式碼表示,MediaStreamTrack.getSources方法的回撥函式,可以得到一個本機的攝像頭和麥克風的列表,然後指定使用最後一個攝像頭和麥克風。

RTCPeerConnectionl,RTCDataChannel

RTCPeerConnectionl

RTCPeerConnection的作用是在瀏覽器之間建立資料的“點對點”(peer to peer)通訊,也就是將瀏覽器獲取的麥克風或攝像頭資料,傳播給另一個瀏覽器。這裡麵包含了很多複雜的工作,比如訊號處理、多媒體編碼/解碼、點對點通訊、資料安全、頻寬管理等等。

不同客戶端之間的音訊/視訊傳遞,是不用通過伺服器的。但是,兩個客戶端之間建立聯絡,需要通過伺服器。伺服器主要轉遞兩種資料。

  • 通訊內容的後設資料:開啟/關閉對話(session)的命令、媒體檔案的後設資料(編碼格式、媒體型別和頻寬)等。
  • 網路通訊的後設資料:IP地址、NAT網路地址翻譯和防火牆等。

WebRTC協議沒有規定與伺服器的通訊方式,因此可以採用各種方式,比如WebSocket。通過伺服器,兩個客戶端按照Session Description Protocol(SDP協議)交換雙方的後設資料。

下面是一個示例。

var signalingChannel = createSignalingChannel();
var pc;
var configuration = ...;

// run start(true) to initiate a call
function start(isCaller) {
    pc = new RTCPeerConnection(configuration);

    // send any ice candidates to the other peer
    pc.onicecandidate = function (evt) {
        signalingChannel.send(JSON.stringify({ "candidate": evt.candidate }));
    };

    // once remote stream arrives, show it in the remote video element
    pc.onaddstream = function (evt) {
        remoteView.src = URL.createObjectURL(evt.stream);
    };

    // get the local stream, show it in the local video element and send it
    navigator.getUserMedia({ "audio": true, "video": true }, function (stream) {
        selfView.src = URL.createObjectURL(stream);
        pc.addStream(stream);

        if (isCaller)
            pc.createOffer(gotDescription);
        else
            pc.createAnswer(pc.remoteDescription, gotDescription);

        function gotDescription(desc) {
            pc.setLocalDescription(desc);
            signalingChannel.send(JSON.stringify({ "sdp": desc }));
        }
    });
}

signalingChannel.onmessage = function (evt) {
    if (!pc)
        start(false);

    var signal = JSON.parse(evt.data);
    if (signal.sdp)
        pc.setRemoteDescription(new RTCSessionDescription(signal.sdp));
    else
        pc.addIceCandidate(new RTCIceCandidate(signal.candidate));
};

複製程式碼

RTCPeerConnection帶有瀏覽器字首,Chrome瀏覽器中為webkitRTCPeerConnection,Firefox瀏覽器中為mozRTCPeerConnection。Google維護一個函式庫adapter.js,用來抽象掉瀏覽器之間的差異。

RTCDataChannel

RTCDataChannel的作用是在點對點之間,傳播任意資料。它的API與WebSockets的API相同。

下面是一個示例。

var pc = new webkitRTCPeerConnection(servers,
  {optional: [{RtpDataChannels: true}]});

pc.ondatachannel = function(event) {
  receiveChannel = event.channel;
  receiveChannel.onmessage = function(event){
    document.querySelector("div#receive").innerHTML = event.data;
  };
};

sendChannel = pc.createDataChannel("sendDataChannel", {reliable: false});

document.querySelector("button#send").onclick = function (){
  var data = document.querySelector("textarea#send").value;
  sendChannel.send(data);
};

複製程式碼

Chrome 25、Opera 18和Firefox 22支援RTCDataChannel。

外部函式庫

由於這兩個API比較複雜,一般採用外部函式庫進行操作。目前,視訊聊天的函式庫有SimpleWebRTCeasyRTCwebRTC.io,點對點通訊的函式庫有PeerJSSharefest

下面是SimpleWebRTC的示例。

var webrtc = new WebRTC({
  localVideoEl: 'localVideo',
  remoteVideosEl: 'remoteVideos',
  autoRequestMedia: true
});

webrtc.on('readyToCall', function () {
    webrtc.joinRoom('My room name');
});

複製程式碼

下面是PeerJS的示例。

var peer = new Peer('someid', {key: 'apikey'});
peer.on('connection', function(conn) {
  conn.on('data', function(data){
    // Will print 'hi!'
    console.log(data);
  });
});

// Connecting peer
var peer = new Peer('anotherid', {key: 'apikey'});
var conn = peer.connect('someid');
conn.on('open', function(){
  conn.send('hi!');
});

複製程式碼

參考連結


相關文章