RTCDataChannel с Google Channel API
Я пытаюсь следовать этому примеру Дэна Ристика для RTCDataChannel
браузер p2p связь с Google Channel API для сигнализации. Кажется, что он молча терпит неудачу - я не могу получить RTCDataChannel.onopen
, RTCPeerConnection.onicecandidate
, или же RTCPeerConnection.ondatachannel
события, чтобы стрелять.
Клиент JS / HTML:
<html>
<head>
<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="/_ah/channel/jsapi"></script>
<script>
$(document).ready(function(){
var IS_CHROME = !!window.webkitRTCPeerConnection,
RTCPeerConnection = window.webkitRTCPeerConnection || mozRTCPeerConnection,
RTCIceCandidate = window.RTCIceCandidate || RTCSessionDescription,
RTCSessionDescription = window.RTCSessionDescription || mozRTCSessionDescription,
SESSION_ID = "12345",
weAreHost,
optionalRtpDataChannels = {
optional: [{RtpDataChannels: true}]
},
mediaConstraints = {
optional: [],
mandatory: {
OfferToReceiveAudio: false, // Hmm!!
OfferToReceiveVideo: false // Hmm!!
}
};
// Signaling Channel Object
function SignalingChannel(peerConnection) {
// Setup the signaling channel here
this.peerConnection = peerConnection;
}
function setChannelEvents(dataChannel) {
dataChannel.onmessage = function (event) {
console.log("I got data channel message: ", event.data);
}
dataChannel.onopen = function (event) {
dataChannel.send("RTCDataChannel Open!");
}
dataChannel.error = function(event) {
console.log("data channel error:", event)
}
}
SignalingChannel.prototype.send = function(message) {
console.log("signal send:", message);
var url = "/api/signal/send/";
url += weAreHost ? "client"+SESSION_ID : "host"+SESSION_ID;
$.ajax({
type: "PUT",
url: url,
contentType: "application/json",
data: JSON.stringify(message)
});
};
SignalingChannel.prototype.onmessage = function(message) {
console.log("signal receive:", message);
// If we get a sdp we have to sign and return it
if (message.sdp != null) {
var that = this;
this.peerConnection.setRemoteDescription(new RTCSessionDescription(message), function () {
that.peerConnection.createAnswer(function (description) {
that.send(description);
}, null, mediaConstraints);
});
} else {
this.peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
}
};
function initiateConnection(input) {
weAreHost = input;
// setup signaling mechanism with Google Channel API
var url = "/api/signal/init/";
url += weAreHost ? "host"+SESSION_ID : "client"+SESSION_ID;
$.post(url, "", function(response){
var channel = new goog.appengine.Channel(response.token);
var socket = channel.open();
socket.onerror = function(){console.log(arguments);};
socket.onclose = function(){console.log(arguments);};
var closeSocket = function() {
if(socket) return socket.close();
else return "google socket does not exist"
}
$(window).unload(closeSocket);
window.onbeforeunload = closeSocket;
socket.onopen = function() {
console.log("google socket opened");
// Create a peer connection object
var connection = new RTCPeerConnection({
iceServers: [
{ 'url': (IS_CHROME ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121') }
]
}, optionalRtpDataChannels);
// Initiate a signaling channel between two users
var signalingChannel = new SignalingChannel(connection);
connection.onicecandidate = function (event) {
console.log("onicecandidate:", event);
if (!event || !event.candidate) return;
signalingChannel.send({candidate:event.candidate});
};
// Effectively set SignalingChannel as google channel socket inbound event handler
socket.onmessage = function(input) {
console.log("received from google:", input);
var message = $.parseJSON(input.data);
signalingChannel.onmessage(message);
};
// Only one client should initiate the connection, the other client should wait.
if(weAreHost) {
connection.ondatachannel = function(event) {
setChannelEvents(event.channel);
}
} else {
// Create client RTCDataChannel
var clientChannel = connection.createDataChannel("my_label", {reliable: false});
setChannelEvents(clientChannel);
connection.createOffer(function (description) {
signalingChannel.send(description);
}, function(error){
console.log(error);
}, mediaConstraints);
}
};
}, "json");
};
// Create a button on the page so only one client initiates the connection.
$("#i-am-host").click(function() {
initiateConnection(true);
});
$("#i-am-client").click(function() {
initiateConnection(false);
});
});
</script>
</head>
<body>
<p id="i-am-host" style="background-color: green;">I AM HOST</p>
<p id="i-am-client" style="background-color: blue;">I AM CLIENT</p>
</body>
</html>
App Engine Python:
from google.appengine.api import channel
from django.shortcuts import render
from django.http import HttpResponse
import json
def init(request, browser_id):
token = channel.create_channel(browser_id);
return HttpResponse(json.dumps({'token':token}))
def send(request, browser_id):
channel.send_message(browser_id, request.body)
return HttpResponse()
Консоли браузера:
[HOST]
received from google:
Object {data: "{"sdp":"v=0\r\no=- 6804947085651458452 2 IN IP4 12…5000 webrtc-datachannel 1024\r\n","type":"offer"}"}
test.html:34
signal receive:
Object {sdp: "v=0
↵o=- 6804947085651458452 2 IN IP4 127.0.0.1
↵s…id:data
↵a=sctpmap:5000 webrtc-datachannel 1024
↵", type: "offer"}
test.html:22
signal send:
RTCSessionDescription {sdp: "v=0
↵o=- 600524556593905006 2 IN IP4 127.0.0.1
↵s=…id:data
↵a=sctpmap:5000 webrtc-datachannel 1024
↵", type: "answer"}
[CLIENT]
signal send:
RTCSessionDescription {sdp: "v=0
↵o=- 6804947085651458452 2 IN IP4 127.0.0.1
↵s…id:data
↵a=sctpmap:5000 webrtc-datachannel 1024
↵", type: "offer"}
test.html:82
received from google:
Object {data: "{"sdp":"v=0\r\no=- 600524556593905006 2 IN IP4 127…000 webrtc-datachannel 1024\r\n","type":"answer"}"}
test.html:34
signal receive: Object {sdp: "v=0
↵o=- 600524556593905006 2 IN IP4 127.0.0.1
↵s=…id:data
↵a=sctpmap:5000 webrtc-datachannel 1024
↵", type: "answer"}
3 ответа
Firefox не поддерживает (и никогда не будет) поддерживать RtpDataChannels. Он поддерживает только спецификации (и более продвинутые) каналы данных SCTP. Снятие необязательного ограничения должно переключить вас на них, не требуя других изменений.
Несколько странно, что в вашем SDP есть строки sctp. Пример Google на http://googlechrome.github.io/webrtc/samples/web/content/datachannel/ не используется при использовании каналов данных rtp.
Для начала, вам не хватает peerConnection.onDataChannel
на другой стороне создания канала данных,
код будет что-то вроде:
answerer.ondatachannel = function (event) {
answererDataChannel = event.channel;
answererDataChannel.binaryType = 'blob';
setChannelEvents(answererDataChannel, 'answerer');
};
...
function setChannelEvents(channel, channelNameForConsoleOutput) {
channel.onmessage = function (event) {
console.debug(channelNameForConsoleOutput, 'received a message:', event.data);
};
channel.onopen = function () {
channel.send('first text message over SCTP data ports');
};
}
для полного кода, вы можете проверить эту ссылку
После многих часов попыток я смог заставить свой оригинальный пример работать на Chrome 40.0.2214.93 и Opera 27.0 (последний на момент написания статьи). почему "onicecandidate" не работает? очень помог. Мне не повезло заставить его работать на Firefox:
<html>
<head>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script type="text/javascript" src="/_ah/channel/jsapi"></script>
<script>
$(document).ready(function(){
var IS_CHROME = !!window.webkitRTCPeerConnection,
RTCPeerConnection = window.webkitRTCPeerConnection || mozRTCPeerConnection,
RTCIceCandidate = window.RTCIceCandidate || RTCSessionDescription,
RTCSessionDescription = window.RTCSessionDescription || mozRTCSessionDescription,
SESSION_ID = "123456",
weAreHost,
optionalRtpDataChannels = {
optional: [{RtpDataChannels: true}]
},
mediaConstraints = {
optional: [],
mandatory: {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
}
};
// Signaling Channel Object
function SignalingChannel(peerConnection) {
// Setup the signaling channel here
this.peerConnection = peerConnection;
}
function setChannelEvents(dataChannel) {
dataChannel.onmessage = function (event) {
console.log("I got data channel message: ", event.data);
}
dataChannel.onopen = function (event) {
dataChannel.send("######### SUCCESS ######### RTCDataChannel Open!");
}
dataChannel.error = function(event) {
console.log("data channel error:", event)
}
}
function error(e) {
console.log(arguments);
throw new Error(e);
}
SignalingChannel.prototype.send = function(message) {
//console.log("signal send:", message);
var url = "/api/signal/send/";
url += weAreHost ? "client"+SESSION_ID : "host"+SESSION_ID;
$.ajax({
type: "PUT",
url: url,
contentType: "application/json",
data: JSON.stringify(message)
});
};
SignalingChannel.prototype.onmessage = function(message) {
//console.log("signal receive:", message);
var self = this;
if(message.type && message.type === "offer") {
var offer = new RTCSessionDescription(message);
this.peerConnection.setRemoteDescription(offer, function() {
self.peerConnection.createAnswer(function(answer) {
self.peerConnection.setLocalDescription(answer, function() {
self.send(answer);
}, error);
}, error, mediaConstraints);
});
} else if(message.type && message.type === "answer") {
var answer = new RTCSessionDescription(message);
this.peerConnection.setRemoteDescription(answer, function(){
}, error);
} else {
this.peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
}
};
function initiateConnection(input) {
weAreHost = input;
// setup signaling mechanism with Google Channel API
var url = "/api/signal/init/";
url += weAreHost ? "host"+SESSION_ID : "client"+SESSION_ID;
$.post(url, "", function(response){
var channel = new goog.appengine.Channel(response.token);
var socket = channel.open();
socket.onerror = error;
var closeSocket = function() {
if(socket) return socket.close();
else return "google socket does not exist"
}
$(window).unload(closeSocket);
window.onbeforeunload = closeSocket;
socket.onopen = function() {
console.log("google socket opened");
// Create a peer connection object
var connection = new RTCPeerConnection({
iceServers: [
{ 'url': (IS_CHROME ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121') }
]
}, optionalRtpDataChannels);
// Initiate a signaling channel between two users
var signalingChannel = new SignalingChannel(connection);
connection.onicecandidate = function (event) {
//console.log("onicecandidate:", event);
if (!event || !event.candidate) return;
signalingChannel.send({candidate:event.candidate});
};
// Effectively set SignalingChannel as google channel socket inbound event handler
socket.onmessage = function(input) {
//console.log("received from google:", input);
var message = $.parseJSON(input.data);
signalingChannel.onmessage(message);
};
// Only one client should initiate the connection, the other client should wait
if(weAreHost) {
connection.ondatachannel = function(event) {
setChannelEvents(event.channel);
}
} else {
// Create client RTCDataChannel
var clientChannel = connection.createDataChannel("my_label", {reliable: false});
setChannelEvents(clientChannel);
// create offer and send to host
connection.createOffer(function (offer) {
connection.setLocalDescription(offer, function() {
signalingChannel.send(offer);
}, error);
}, error, mediaConstraints);
}
};
}, "json").fail(error);
};
// Create a button on the page so only one client initiates the connection.
$("#i-am-host").click(function() {
initiateConnection(true);
});
$("#i-am-client").click(function() {
initiateConnection(false);
});
});
</script>
</head>
<body>
<p id="i-am-host" style="background-color: green;">I AM HOST</p>
<p id="i-am-client" style="background-color: blue;">I AM CLIENT</p>
<br>
<p id="print">PRINT SIGNALING STATE<p>
</body>
</html>
Я решил использовать PeerJS с бесплатным сервером Heroku для передачи сигналов. На мой взгляд, WebRTC сейчас слишком нестабилен, чтобы использовать его напрямую. Следующие работы на Chrome/FF/Opera:
<html>
<head>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="http://cdn.peerjs.com/0.3.9/peer.js"></script>
<script>
$(document).ready(function(){
var SESSION_ID = "1234";
// Create a button on the page so only one client initiates the connection.
$("#i-am-host").click(function() {
var host = new Peer('host'+SESSION_ID, {host: 'my-peerjs.herokuapp.com', port: 80});
host.on("connection", function(conn) {
conn.on('data', function(data) {
console.log(data);
});
});
});
$("#i-am-client").click(function() {
var client = new Peer('client'+SESSION_ID, {host: 'my-peerjs.herokuapp.com', port: 80});
var conn = client.connect('host'+SESSION_ID);
conn.on("open", function(){
conn.send("SUCCESS!!");
});
});
});
</script>
</head>
<body>
<p id="i-am-host" style="background-color: green;">I AM HOST</p>
<p id="i-am-client" style="background-color: blue;">I AM CLIENT</p>
</body>
</html>