読者です 読者をやめる 読者になる 読者になる

Webinar

Web関連情報のウェブセミナー

WebRTCを使ったライブ配信デモサイトを作ってみました(2/2) 受信者編

f:id:t-webber:20150315000105p:plain

本記事は、「WebRTCを使ったライブ配信デモサイトを作ってみました」の後編です。

前編はこちら。

WebRTCを使ったライブ配信デモサイトを作ってみました(1/2) 配信者編 - Webinar

 

ライブ配信デモサイト 受信者編

では今回は受信者編のサンプルサイトです。

今回も結構長いコードなので先に構築のポイントです。構築のポイントとしては配信者と同じですね。配信者編と合わせて、ぜひ自分のサイトで構築してみてください。

  • x.x.x.xは自分のWebサーバのIPアドレスと読み替えてください。
  • API名のブラウザ差分を吸収するためにadapter.jsを自サーバに配置。
  • シグナリングはsocket.io(Websocket)経由で実施するので、socket.io環境も構築する。
  • stunサーバはGoogleのもの(stun.l.google.com)を使った。

 

 

<!DOCTYPE html>
<html>
<head>
<title>broadcast watch</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script src="http://x.x.x.x/adapter.js"></script>
</head>
<body>
<button type="button" onclick="sendRequest();" style="font-size: 3em; WIDTH: 100%; HEIGHT: 100px">Request</button>
<button type="button" onclick="hangUp();">Hang Up</button>
<br />
<div style="position: relative;">
<video id="remote-video" autoplay style="width: 100%; height: 100%; border: 1px solid black;"></video>
</div>

<!---- socket ※自分のシグナリングサーバーに合わせて変更してください ------>
<script src="http://x.x.x.x:9001/socket.io/socket.io.js"></script>

<script>
//var localVideo = document.getElementById('local-video');
var remoteVideo = document.getElementById('remote-video');
//var localStream = null;
var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':true, 'OfferToReceiveVideo':true }};

function detachVideo(id) {
if (id) {
var conn = getConnection(id);
if (conn) {
remoteVideo.pause();
remoteVideo.src = "";
}
}
else {
// force detach
remoteVideo.pause();
remoteVideo.src = "";
}
}

function resizeRemoteVideo() {
console.log('--resize--');
var top_margin = 40;
var left_margin = 20;
var video_margin = 10;

var new_width = window.innerWidth - left_margin - video_margin;
var new_height = window.innerHeight - top_margin - video_margin;
remoteVideo.style.width = new_width + 'px';
remoteVideo.style.height = new_height + 'px';
remoteVideo.style.top = top_margin + 'px';
remoteVideo.style.left = left_margin + 'px';
}
document.body.onresize = resizeRemoteVideo;
resizeRemoteVideo();

// -------------- multi connections --------------------
var MAX_CONNECTION_COUNT = 1;
var connections = {}; // Connection hash
function Connection() { // Connection Class
var self = this;
var id = ""; // socket.id of partner
var peerconnection = null; // RTCPeerConnection instance
}

function getConnection(id) {
var con = null;
con = connections[id];
return con;
}

function addConnection(id, connection) {
connections[id] = connection;
}

function getConnectionCount() {
var count = 0;
for (var id in connections) {
count++;
}

console.log('getConnectionCount=' + count);
return count;
}

function isConnectPossible() {
if (getConnectionCount() < MAX_CONNECTION_COUNT)
return true;
else
return false;
}

function getConnectionIndex(id_to_lookup) {
var index = 0;
for (var id in connections) {
if (id == id_to_lookup) {
return index;
}

index++;
}

// not found
return -1;
}

function deleteConnection(id) {
delete connections[id];
}

function stopAllConnections() {
for (var id in connections) {
var conn = connections[id];
conn.peerconnection.close();
conn.peerconnection = null;
delete connections[id];
}
}

function stopConnection(id) {
var conn = connections[id];
if(conn) {
console.log('stop and delete connection with id=' + id);
conn.peerconnection.close();
conn.peerconnection = null;
delete connections[id];
}
else {
console.log('try to stop connection, but not found id=' + id);
}
}

function isPeerStarted() {
if (getConnectionCount() > 0) {
return true;
}
else {
return false;
}
}


// ---- socket ------
// create socket
var socketReady = false;
var port = 9001;
var socket = io.connect('http://x.x.x.x:' + port + '/'); // ※自分のシグナリングサーバーに合わせて変更してください

// socket: channel connected
socket.on('connect', onOpened)
.on('message', onMessage)
.on('user disconnected', onUserDisconnect);

function onOpened(evt) {
console.log('socket opened.');
socketReady = true;

var roomname = getRoomName(); // 会議室名を取得する
socket.emit('enter', roomname);
console.log('enter to ' + roomname);
}

// socket: accept connection request
function onMessage(evt) {
var id = evt.from;
var target = evt.sendto;
var conn = getConnection(id);

console.log('onMessage() evt.type='+ evt.type);

if (evt.type === 'talk_ready') {
if (conn) {
return; // already connected
}

if (isConnectPossible()) {
socket.json.send({type: "talk_request", sendto: id });
}
else {
console.warn('max connections. so ignore call');
}
return;
}
else if (evt.type === 'offer') {
console.log("Received offer, set offer, sending answer....")
onOffer(evt);
}
else if (evt.type === 'candidate' && isPeerStarted()) {
console.log('Received ICE candidate...');
onCandidate(evt);
}
else if (evt.type === 'end_talk') {
console.log("got talker bye.");
detachVideo(id); // force detach video
stopConnection(id);
}

}

function onUserDisconnect(evt) {
console.log("disconnected");
if (evt) {
detachVideo(evt.id); // force detach video
stopConnection(evt.id);
}
}

function getRoomName() { // たとえば、 URLに ?roomname とする
var url = document.location.href;
var args = url.split('?');
if (args.length > 1) {
var room = args[1];
if (room != "") {
return room;
}
}
return "_defaultroom";
}


function onOffer(evt) {
console.log("Received offer...")
console.log(evt);
setOffer(evt);
sendAnswer(evt);
}

function onCandidate(evt) {
var id = evt.from;
var conn = getConnection(id);
if (! conn) {
console.error('peerConnection not exist!');
return;
}

var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate});
console.log("Received Candidate...")
console.log(candidate);
conn.peerconnection.addIceCandidate(candidate);
}

function sendSDP(sdp) {
var text = JSON.stringify(sdp);
console.log("---sending sdp text ---");
console.log(text);

// send via socket
socket.json.send(sdp);
}

function sendCandidate(candidate) {
var text = JSON.stringify(candidate);
console.log("---sending candidate text ---");
console.log(text);

// send via socket
socket.json.send(candidate);
}

// ---------------------- connection handling -----------------------
function prepareNewConnection(id) {
var pc_config = {"iceServers":[ {"url":"stun:stun.l.google.com:19302"} ]};
var peer = null;
try {
//peer = new webkitRTCPeerConnection(pc_config);
peer = new RTCPeerConnection(pc_config);
} catch (e) {
console.log("Failed to create PeerConnection, exception: " + e.message);
}
var conn = new Connection();
conn.id = id;
conn.peerconnection = peer;
peer.id = id;
addConnection(id, conn);

// send any ice candidates to the other peer
peer.onicecandidate = function (evt) {
if (evt.candidate) {
console.log(evt.candidate);
sendCandidate({type: "candidate",
sendto: conn.id,
sdpMLineIndex: evt.candidate.sdpMLineIndex,
sdpMid: evt.candidate.sdpMid,
candidate: evt.candidate.candidate});
} else {
console.log("on ice event. phase=" + evt.eventPhase);
}
};

//console.log('Adding local stream...');
//peer.addStream(localStream);

peer.addEventListener("addstream", onRemoteStreamAdded, false);
peer.addEventListener("removestream", onRemoteStreamRemoved, false)

// when remote adds a stream, hand it on to the local video element
function onRemoteStreamAdded(event) {
console.log("Added remote stream");
//attachVideo(this.id, event.stream);
//remoteVideo.src = window.webkitURL.createObjectURL(event.stream);
attachMediaStream(remoteVideo, event.stream);
}

// when remote removes a stream, remove it from the local video element
function onRemoteStreamRemoved(event) {
console.log("Remove remote stream");
detachVideo(this.id);
}

return conn;
}

function setOffer(evt) {
var id = evt.from;
var conn = getConnection(id);
if (! conn) {
conn = prepareNewConnection(id);
conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt));
}
else {
console.error('peerConnection alreay exist!');
}
}

function sendAnswer(evt) {
console.log('sending Answer. Creating remote session description...' );
var id = evt.from;
var conn = getConnection(id);
if (! conn) {
console.error('peerConnection not exist!');
return;
}

conn.peerconnection.createAnswer(function (sessionDescription) {
// in case of success
conn.peerconnection.setLocalDescription(sessionDescription);
sessionDescription.sendto = id;
sendSDP(sessionDescription);
}, function () { // in case of error
console.log("Create Answer failed");
}, mediaConstraints);
}

function sendRequest() {
if (! socketReady) {
alert("Socket is not connected to server. Please reload and try again.");
return;
}

// call others, in same room
console.log("send request in same room, ask for offer");
socket.json.send({type: "talk_request"});

}

// stop the connection upon user request
function hangUp() {
console.log("Hang up.");
socket.json.send({type: "bye"});
detachVideo(null);
stopAllConnections();
}


</script>
</body>
</html>