I have been developing a nodejs server to provide server-side-events for a new website I am developing in HTML5.
When I telnet to the server it works correctly, sending me the required HTTP response headers followed by a stream of events that i am presently generating every 2 or 3 seconds just to prove it works.
I have tried the latest version of FireFox, Chrome and Opera and they create the EventSource object and connect to the nodejs server OK but none of the browsers generate any of the events, including the onopen, onmessage and onerror.
However, if I stop my nodejs server, terminating the connection from the browsers, they all suddenly dispatch all the messages and all my events are shown. The browsers then all try to reconnect to the server as per spec.
I am hosting everything on a webserver. nothing is running in local files.
I have read everything I can find online, including books I've purchased and nothing indicates any such problem. Is there something Im missing?
A sample server implementation
var http = require('http');
var requests = [];
var server = http.Server(function(req, res) {
var clientIP = req.socket.remoteAddress;
var clientPort = req.socket.remotePort;
res.on('close', function() {
console.log("client " + clientIP + ":" + clientPort + " died");
for(var i=requests.length -1; i>=0; i--) {
if ((requests[i].ip == clientIP) && (requests[i].port == clientPort)) {
requests.splice(i, 1);
}
}
});
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'});
requests.push({ip:clientIP, port:clientPort, res:res});
res.write(": connected.\n\n");
});
server.listen(8080);
setInterval(function test() {
broadcast('poll', "test message");
}, 2000);
function broadcast(rtype, msg) {
var lines = msg.split("\n");
for(var i=requests.length -1; i>=0; i--) {
requests[i].res.write("event: " + rtype + "\n");
for(var j=0; j<lines.length; j++) {
if (lines[j]) {
requests[i].res.write("data: " + lines[j] + "\n");
}
}
requests[i].res.write("\n");
}
}
A sample html page
<!DOCTYPE html>
<html>
<head>
<title>SSE Test</title>
<meta charset="utf-8" />
<script language="JavaScript">
function init() {
if(typeof(EventSource)!=="undefined") {
var log = document.getElementById('log');
if (log) {
log.innerHTML = "EventSource() testing begins..<br>";
}
var svrEvents = new EventSource('/sse');
svrEvents.onopen = function() {
connectionOpen(true);
}
svrEvents.onerror = function() {
connectionOpen(false);
}
svrEvents.addEventListener('poll', displayPoll, false); // display multi choice and send back answer
svrEvents.onmessage = function(event) {
var log = document.getElementById('log');
if (log) {
log.innerHTML += 'message: ' + event.data + "<br>";
}
// absorb any other messages
}
} else {
var log = document.getElementById('log');
if (log) {
log.innerHTML = "EventSource() not supported<br>";
}
}
}
function connectionOpen(status) {
var log = document.getElementById('log');
if (log) {
log.innerHTML += 'connected: ' + status + "<br>";
}
}
function displayPoll(event) {
var html = event.data;
var log = document.getElementById('log');
if (log) {
log.innerHTML += 'poll: ' + html + "<br>";
}
}
</script>
</head>
<body onLoad="init()">
<div id="log">testing...</div>
</body>
</html>
These examples are basic but of the same variety as every other demo i've seen in books and online. The eventSource only seems to be working if I end a client connection or terminate the server but this would be polling instead of SSE and I particularly want to use SSE.
Interestingly, demos, such as thouse from html5rock also seem to not quite work as expected when I use them online..
cracked it! :)
Thanks to some help from Tom Kersten who helped me with testing. Turns out the code isnt the problem.
Be warned.. if your client uses any kind of anti-virus software which intercepts web requests, it may cause problems here. In this case, Sophos Endpoint Security, which provides enterprise grade anti-virus and firewall protection has a feature called web protection. Within this features is an option to scan downloads; it seems that the SSE connection is treated as a download and thus not released to the browser until the connection is closed and the stream received to scan. Disabling this option cures the problem. I have submitted a bug report but other anti-virus systems may do the same.
thanks for your suggestions and help everyone :)
http://www.w3.org/TR/eventsource/#parsing-an-event-stream
Since connections established to remote servers for such resources are
expected to be long-lived, UAs should ensure that appropriate
buffering is used. In particular, while line buffering with lines are
defined to end with a single U+000A LINE FEED (LF) character is safe,
block buffering or line buffering with different expected line endings
can cause delays in event dispatch.
Try to play with line endings ("\r\n" instead of "\n").
http://www.w3.org/TR/eventsource/#notes
Authors are also cautioned that HTTP chunking can have unexpected
negative effects on the reliability of this protocol. Where possible,
chunking should be disabled for serving event streams unless the rate
of messages is high enough for this not to matter.
I modified your server-side script, which 'seems' partly works for Chrome.
But the connection break for every 2 broadcast & only 1 can be shown on client.
Firefox works for 1st broadcast and stop by this error:
Error: The connection to /sse was interrupted while the page was loading.
And Chrome will try to reconnect and received 3rd broadcast.
I think it's related to firewall setting too but can't explain why sometime will works.
Note: For event listener of response (line 10), 'close' & 'end' have different result,
You can try it and my result is [close: 1 success/2 broadcast] & [end: 1 success/8 broadcast]
var http = require('http'), fs = require('fs'), requests = [];
var server = http.Server(function(req, res) {
var clientIP = req.socket.remoteAddress;
var clientPort = req.socket.remotePort;
if (req.url == '/sse') {
var allClient="";for(var i=0;i<requests.length;i++){allClient+=requests[i].ip+":"+requests[i].port+";";}
if(allClient.indexOf(clientIP+":"+clientPort)<0){
requests.push({ip:clientIP, port:clientPort, res:res});
res.on('close', function() {
console.log("client " + clientIP + ":" + clientPort + " died");
for(var i=requests.length -1; i>=0; i--) {
if ((requests[i].ip == clientIP) && (requests[i].port == clientPort)) {
requests.splice(i, 1);
}
}
});
}
}else{
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(fs.readFileSync('./test.html'));
res.end();
}
});
server.listen(80);
setInterval(function test() {
broadcast('poll', "test message");
}, 500);
var broadcastCount=0;
function broadcast(rtype, msg) {
if(!requests.length)return;
broadcastCount++;
var lines = msg.split("\n");
for(var i = requests.length - 1; i >= 0; i--) {
requests[i].res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
requests[i].res.write("event: " + rtype + "\n");
for(var j = 0; j < lines.length; j++) {
if(lines[j]) {
requests[i].res.write("data: " + lines[j] + "\n");
}
}
requests[i].res.write("data: Count\: " + broadcastCount + "\n");
requests[i].res.write("\n");
}
console.log("Broadcasted " + broadcastCount + " times to " + requests.length + " user(s).");
}
Related
Background
I am building a local area network, WebRTC baby monitor with a raspberry pi camera module and USB microphone. The stream is synthesized with GStreamer and im using Janus Gateway to facilitate the WebRTC connection between a web browser and the Pi. The webpage and Javascript is a stripped down version of the streaming demo provided by Meetecho.
This is my first time using many of these technologies and am a bit in over my head with troubleshooting at the moment. I'm having trouble figuring out why the web page works in Chrome and Safari, but does not work in Firefox.
It works fine on Chrome and Safari:
In Firefox, the WebRTC connection seems to be successfully established and maintained (based on comparisons of the network traffic and console output between Chrome and Firefox), but the page seems to get caught up somewhere along the way:
Comparing the consoles
When comparing the console outputs of Chrome and Firefox, they are identical until this point where both consoles report Uncaught (in promise) DOMException: but for possibly different reasons?
Firefox says Uncaught (in promise) DOMException: The fetching process for the media resource was aborted by the user agent at the user's request.
Chrome says Uncaught (in promise) DOMException: The play() request was interrupted by a new load request.
Are these the same errors with different "Hints"? Or are they actually different errors due to some underlying difference between the browsers?
Immediately after this error, Firefox diverges from Chrome by reporting Remote track removed.
Im unsure if I am doing something silly in the JS to cause this or if there is some nuance about Firefox that I am missing.
Other details that might be helpful?
Below is part of the html (index.html) and javascript (janus_stream.js) for the page, the pastebin link contains the whole janus_stream.js.
// We make use of this 'server' variable to provide the address of the
// REST Janus API. By default, in this example we assume that Janus is
// co-located with the web server hosting the HTML pages but listening
// on a different port (8088, the default for HTTP in Janus), which is
// why we make use of the 'window.location.hostname' base address. Since
// Janus can also do HTTPS, and considering we don't really want to make
// use of HTTP for Janus if your demos are served on HTTPS, we also rely
// on the 'window.location.protocol' prefix to build the variable, in
// particular to also change the port used to contact Janus (8088 for
// HTTP and 8089 for HTTPS, if enabled).
// In case you place Janus behind an Apache frontend (as we did on the
// online demos at http://janus.conf.meetecho.com) you can just use a
// relative path for the variable, e.g.:
//
// var server = "/janus";
//
// which will take care of this on its own.
//
//
// If you want to use the WebSockets frontend to Janus, instead, you'll
// have to pass a different kind of address, e.g.:
//
// var server = "ws://" + window.location.hostname + ":8188";
//
// Of course this assumes that support for WebSockets has been built in
// when compiling the server. WebSockets support has not been tested
// as much as the REST API, so handle with care!
//
//
// If you have multiple options available, and want to let the library
// autodetect the best way to contact your server (or pool of servers),
// you can also pass an array of servers, e.g., to provide alternative
// means of access (e.g., try WebSockets first and, if that fails, fall
// back to plain HTTP) or just have failover servers:
//
// var server = [
// "ws://" + window.location.hostname + ":8188",
// "/janus"
// ];
//
// This will tell the library to try connecting to each of the servers
// in the presented order. The first working server will be used for
// the whole session.
//
var server = null;
if(window.location.protocol === 'http:')
server = "http://" + window.location.hostname + ":8088/janus";
else
server = "https://" + window.location.hostname + ":8089/janus";
var janus = null;
var streaming = null;
var opaqueId = "streamingtest-"+Janus.randomString(12);
var bitrateTimer = null;
var spinner = true;
var simulcastStarted = false, svcStarted = false;
var selectedStream = null;
$(document).ready(function() {
// Initialize the library (all console debuggers enabled)
Janus.init({debug: "all", callback: function() {
// Use a button to start the demo
//$('#start').one('click', function() {
//$(this).attr('disabled', true).unbind('click');
// Make sure the browser supports WebRTC
if(!Janus.isWebrtcSupported()) {
bootbox.alert("No WebRTC support... ");
return;
}
// Create session
janus = new Janus(
{
server: server,
success: function() {
// Attach to Streaming plugin
janus.attach(
{
plugin: "janus.plugin.streaming",
opaqueId: opaqueId,
success: function(pluginHandle) {
$('#details').remove();
streaming = pluginHandle;
Janus.log("Plugin attached! (" + streaming.getPlugin() + ", id=" + streaming.getId() + ")");
// Setup streaming session
$('#update-streams').click(updateStreamsList);
updateStreamsList();
$('#start').removeAttr('disabled').html("Stop")
.click(function() {
$(this).attr('disabled', true);
clearInterval(bitrateTimer);
janus.destroy();
$('#streamslist').attr('disabled', true);
$('#watch').attr('disabled', true).unbind('click');
$('#start').attr('disabled', true).html("Bye").unbind('click');
});
},
error: function(error) {
Janus.error(" -- Error attaching plugin... ", error);
bootbox.alert("Error attaching plugin... " + error);
},
iceState: function(state) {
Janus.log("ICE state changed to " + state);
},
webrtcState: function(on) {
Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
},
onmessage: function(msg, jsep) {
Janus.debug(" ::: Got a message :::", msg);
var result = msg["result"];
if(result) {
if(result["status"]) {
var status = result["status"];
if(status === 'starting')
$('#status').removeClass('hide').text("Starting, please wait...").show();
else if(status === 'started')
$('#status').removeClass('hide').text("Started").show();
else if(status === 'stopped')
stopStream();
} else if(msg["streaming"] === "event") {
// Is simulcast in place?
var substream = result["substream"];
var temporal = result["temporal"];
if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
if(!simulcastStarted) {
simulcastStarted = true;
addSimulcastButtons(temporal !== null && temporal !== undefined);
}
// We just received notice that there's been a switch, update the buttons
updateSimulcastButtons(substream, temporal);
}
// Is VP9/SVC in place?
var spatial = result["spatial_layer"];
temporal = result["temporal_layer"];
if((spatial !== null && spatial !== undefined) || (temporal !== null && temporal !== undefined)) {
if(!svcStarted) {
svcStarted = true;
addSvcButtons();
}
// We just received notice that there's been a switch, update the buttons
updateSvcButtons(spatial, temporal);
}
}
} else if(msg["error"]) {
bootbox.alert(msg["error"]);
stopStream();
return;
}
if(jsep) {
Janus.debug("Handling SDP as well...", jsep);
var stereo = (jsep.sdp.indexOf("stereo=1") !== -1);
// Offer from the plugin, let's answer
streaming.createAnswer(
{
jsep: jsep,
// We want recvonly audio/video and, if negotiated, datachannels
media: { audioSend: false, videoSend: false, data: true },
customizeSdp: function(jsep) {
if(stereo && jsep.sdp.indexOf("stereo=1") == -1) {
// Make sure that our offer contains stereo too
jsep.sdp = jsep.sdp.replace("useinbandfec=1", "useinbandfec=1;stereo=1");
}
},
success: function(jsep) {
Janus.debug("Got SDP!", jsep);
var body = { request: "start" };
streaming.send({ message: body, jsep: jsep });
$('#watch').html("Stop").removeAttr('disabled').click(stopStream);
},
error: function(error) {
Janus.error("WebRTC error:", error);
bootbox.alert("WebRTC error... " + error.message);
}
});
}
},
onremotestream: function(stream) {
Janus.debug(" ::: Got a remote stream :::", stream);
var addButtons = false;
if($('#remotevideo').length === 1) {
addButtons = true;
//$('#stream').append('<video class="rounded centered hide" id="remotevideo" width="100%" height="100%" playsinline/>');
$('#remotevideo').get(0).volume = 0;
// Show the stream and hide the spinner when we get a playing event
$("#remotevideo").bind("playing", function () {
$('#waitingvideo').remove();
if(this.videoWidth)
$('#remotevideo').removeClass('hide').show();
if(spinner)
spinner.stop();
spinner = null;
var videoTracks = stream.getVideoTracks();
if(!videoTracks || videoTracks.length === 0)
return;
var width = this.videoWidth;
var height = this.videoHeight;
$('#curres').removeClass('hide').text(width+'x'+height).show();
if(Janus.webRTCAdapter.browserDetails.browser === "firefox") {
// Firefox Stable has a bug: width and height are not immediately available after a playing
setTimeout(function() {
var width = $("#remotevideo").get(0).videoWidth;
var height = $("#remotevideo").get(0).videoHeight;
$('#curres').removeClass('hide').text(width+'x'+height).show();
}, 2000);
}
});
}
Janus.attachMediaStream($('#remotevideo').get(0), stream);
$("#remotevideo").get(0).play();
$("#remotevideo").get(0).volume = 1;
var videoTracks = stream.getVideoTracks();
if(!videoTracks || videoTracks.length === 0) {
// No remote video
$('#remotevideo').hide();
if($('#stream .no-video-container').length === 0) {
$('#stream').append(
'<div class="no-video-container">' +
'<i class="fa fa-video-camera fa-5 no-video-icon"></i>' +
'<span class="no-video-text">No remote video available</span>' +
'</div>');
}
} else {
$('#stream .no-video-container').remove();
$('#remotevideo').removeClass('hide').show();
}
if(!addButtons)
return;
if(videoTracks && videoTracks.length &&
(Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
Janus.webRTCAdapter.browserDetails.browser === "firefox" ||
Janus.webRTCAdapter.browserDetails.browser === "safari")) {
$('#curbitrate').removeClass('hide').show();
bitrateTimer = setInterval(function() {
// Display updated bitrate, if supported
var bitrate = streaming.getBitrate();
$('#curbitrate').text(bitrate);
// Check if the resolution changed too
var width = $("#remotevideo").get(0).videoWidth;
var height = $("#remotevideo").get(0).videoHeight;
if(width > 0 && height > 0)
$('#curres').removeClass('hide').text(width+'x'+height).show();
}, 1000);
}
},
ondataopen: function(data) {
Janus.log("The DataChannel is available!");
$('#waitingvideo').remove();
$('#stream').append(
'<input class="form-control" type="text" id="datarecv" disabled></input>'
);
if(spinner)
spinner.stop();
spinner = null;
},
ondata: function(data) {
Janus.debug("We got data from the DataChannel!", data);
$('#datarecv').val(data);
},
oncleanup: function() {
Janus.log(" ::: Got a cleanup notification :::");
$('#waitingvideo').remove();
$('#remotevideo').remove();
$('#datarecv').remove();
$('.no-video-container').remove();
$('#bitrate').attr('disabled', true);
$('#bitrateset').html('Bandwidth<span class="caret"></span>');
$('#curbitrate').hide();
if(bitrateTimer)
clearInterval(bitrateTimer);
bitrateTimer = null;
$('#curres').hide();
$('#simulcast').remove();
$('#metadata').empty();
$('#info').addClass('hide').hide();
simulcastStarted = false;
}
});
},
error: function(error) {
Janus.error(error);
bootbox.alert(error, function() {
window.location.reload();
});
},
destroyed: function() {
window.location.reload();
}
});
//});
}});
});
function updateStreamsList() {
$('#update-streams').unbind('click').addClass('fa-spin');
var body = { request: "list" };
Janus.debug("Sending message:", body);
streaming.send({ message: body, success: function(result) {
setTimeout(function() {
$('#update-streams').removeClass('fa-spin').click(updateStreamsList);
}, 500);
if(!result) {
bootbox.alert("Got no response to our query for available streams");
return;
}
if(result["list"]) {
$('#streams').removeClass('hide').show();
$('#streamslist').empty();
$('#watch').attr('disabled', true).unbind('click');
var list = result["list"];
Janus.log("Got a list of available streams");
if(list && Array.isArray(list)) {
list.sort(function(a, b) {
if(!a || a.id < (b ? b.id : 0))
return -1;
if(!b || b.id < (a ? a.id : 0))
return 1;
return 0;
});
}
Janus.debug(list);
for(var mp in list) {
Janus.debug(" >> [" + list[mp]["id"] + "] " + list[mp]["description"] + " (" + list[mp]["type"] + ")");
$('#streamslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + list[mp]["description"] + " (" + list[mp]["type"] + ")" + "</a></li>");
}
$('#streamslist a').unbind('click').click(function() {
selectedStream = $(this).attr("id");
$('#streamset').html($(this).html()).parent().removeClass('open');
return false;
});
$('#watch').removeAttr('disabled').unbind('click').click(startStream);
}
}});
}
function getStreamInfo() {
$('#metadata').empty();
$('#info').addClass('hide').hide();
if(!selectedStream)
return;
// Send a request for more info on the mountpoint we subscribed to
var body = { request: "info", id: parseInt(selectedStream) || selectedStream };
streaming.send({ message: body, success: function(result) {
if(result && result.info && result.info.metadata) {
$('#metadata').html(result.info.metadata);
$('#info').removeClass('hide').show();
}
}});
}
function startStream() {
selectedStream = "1"
Janus.log("Selected video id #" + selectedStream);
if(!selectedStream) {
bootbox.alert("Select a stream from the list");
return;
}
$('#streamset').attr('disabled', true);
$('#streamslist').attr('disabled', true);
$('#watch').attr('disabled', true).unbind('click');
var body = { request: "watch", id: parseInt(selectedStream) || selectedStream};
streaming.send({ message: body });
// No remote video yet
$('#stream').append('<video class="rounded centered" id="waitingvideo" width="100%" height="100%" />');
if(spinner == null) {
var target = document.getElementById('stream');
spinner = new Spinner({top:100}).spin(target);
} else {
spinner.spin();
}
// Get some more info for the mountpoint to display, if any
getStreamInfo();
}
function stopStream() {
$('#watch').attr('disabled', true).unbind('click');
var body = { request: "stop" };
streaming.send({ message: body });
streaming.hangup();
$('#streamset').removeAttr('disabled');
$('#streamslist').removeAttr('disabled');
$('#watch').html("Watch or Listen").removeAttr('disabled').unbind('click').click(startStream);
$('#status').empty().hide();
$('#bitrate').attr('disabled', true);
$('#bitrateset').html('Bandwidth<span class="caret"></span>');
$('#curbitrate').hide();
if(bitrateTimer)
clearInterval(bitrateTimer);
bitrateTimer = null;
$('#curres').empty().hide();
$('#simulcast').remove();
simulcastStarted = false;
}
.......
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>BabyPi Cam</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/7.4.0/adapter.min.js" ></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js"></script>
<script type="text/javascript" src="janus.js" ></script>
<script type="text/javascript" src="janus_stream.js"></script>
<script>
$(function() {
$(".navbar-static-top").load("navbar.html", function() {
$(".navbar-static-top li.dropdown").addClass("active");
$(".navbar-static-top a[href='streamingtest.html']").parent().addClass("active");
});
$(".footer").load("footer.html");
});
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.4.0/cerulean/bootstrap.min.css" type="text/css"/>
<link rel="stylesheet" href="css/demo.css" type="text/css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" type="text/css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css"/>
</head>
<body>
<div class="container">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">BabyPi Live Stream
<span class="label label-info" id="status"></span>
<span class="label label-primary" id="curres"></span>
<span class="label label-info" id="curbitrate" styple="display:inline;"></span>
</h3>
</div>
<div class="panel-body" id="stream">
<video class="rounded centered" id="remotevideo" width="100%" height="100%" playsinline controls muted></video>
</div>
</div>
</div>
</div>
</body>
</html>
I am also using the janus.js API provided Meetecho
The Questions
What am I doing wrong that prevents this from working in Firefox?
What can the diverging console outputs tell me about what I am doing wrong?
Do you have any suggestions on where to look / what to try to get this working in Firefox?
Any pointers or ideas are greatly appreciated! Please let me know if I can provide other information.
Thank you!
Update: Theory / Possible answer?
In an attempt to address the Uncaught (in promise) DOMException: The fetching process for the media resource was aborted by the user agent at the user's request. error, I changed video.play() to video.load(). This addressed the error, but the same Remote track removed and "No remote video" behavior persists.
In the meantime I may have discovered the more fundamental issue: The video stream from the Pi is H264, and from what I can tell, Firefox does not support this format? Perhaps this is the reason that I am having issues with firefox?
Can any of you confirm or deny this as the true issue?
The issue is related to H264 incompatibility, but after seeing this thread I realized i was a victim of the same issue.
I needed to update one line in my janus.plugin.streaming.jcfg file so that it looks like this:
RPI3: {
type = "rtp"
id = 1
description = "Raspberry Pi 3 Infrared Camera Module stream"
video = true
videoport = 5001
videopt = 96
videortpmap = "H264/90000"
videofmtp = "profile-level-id=42e01f;packetization-mode=1"
audio = true
audioport = 5002
audiopt = 111
audiortpmap = "opus/48000/2"
}
Previously I was using this "incomplete" line which was causing the issue:
...
videofmtp = "packetization-mode=1"
...
Apparently this enables the correct H264 "profile" that can work with Firefox's OpenH264 plugin. Now i am able to view the stream with both chrome and firefox!
I am currently working on fetching/scraping all the images being received on requesting a URL.
The problem i am facing is the response changes after few tries or is very inconsistent even for the same URL using the phantomjs.
I have tried clearing cache multiple times and at different location in my code but the the request numbers dont appear to be the same.
o/p
1st call to URL
19 images and total off 49 responses from server
2nd call
14 images and 48 responses from server
3rd call
14 images and 38 responses from server
Output for execution twice
phantomjs-2.1.1-windows
running a java-script code using the phantomjs command
I am very new to Javascript, i have manged to write the following till now
var page = require('webpage').create();
var fs = require('fs');
var url = "https://..........";
page.settings.clearMemoryCaches = true;
page.clearMemoryCache();
page.clearCookies();
page.viewportSize = {width: 1280, height: 1024};
var imageCounter = 0;
var responseCounter = 0;
page.open(url, function (status) {
console.log(status + '*/*/*/*/*/*/**/*/*/*/*/*/*/*/*/*/*/*/*/*/')
if(status=='success'){
console.log('The entire page is loaded.............################');
console.log('\n' + imageCounter + '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n');
console.log('\n' + responseCounter + '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n');
imageCounter = 0;
page.clearMemoryCache();
page.clearCookies();
page.close();
phantom.exit();
}
});
page.onResourceReceived = function(response) {
if(response.stage == "start"){
responseCounter++;
var respType = response.contentType;
if(respType.indexOf("image")==0){
imageCounter++;
//console.log('Content-Type : ' + response.contentType)
//console.log('Status : ' + response.status)
//console.log('Image Size in byte : ' + response.bodySize)
//console.log('Image Url : ' + response.url)
//console.log('\n');
console.log(imageCounter + '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n');
}
}
};
I want to get consistent response for the images at least, I am really confused how does phantom cache this kind of resources on second attempt.
The idea is to allow me to press a button on the HTML page to execute a command to copy and delete all photos on cameras with feedback showing at the beginning and ending of the execution.
At the moment, after clicking the "Get Images From Camera", the textarea is showing this text:
Executed command: \copyImages
Result is as below: Copying images from
both cameras...\n
And it goes on to copy and delete all images like I want. But at the end of this process, nothing is returned back to the screen, so the user has no idea what happens. The nature of callback in Node js makes it too confusing for me to figure out how to do this.
P.S. I've tried all I know before I come here to get your help. So know that any suggestions are very appreciated!
So, my question is how do I change the codes below so that I could
display a message to show the user that the copying is completed successfully like:
Please wait for the copying to complete...
Completed!
Below are the HTML markups
<button id="copyImages" type="button" class="button">Get Images From Camera</button>
<textarea id="output" readonly></textarea>
Here is the Javascript event handling:
copyImages.onclick = function() {
dest = '/copyImages';
writeToOutput(dest);
}
function writeToOutput(dest) {
$.get(dest, null, function(data) {
resultText += "Executed command: "+dest+"\n"
+"Result is as below: \n"+data;
$("#output").val(resultText);
}, "text");
return true;
}
These functions below are for setting up a Node App server using express module to listen to anything the HTML page passes to it. They are run on a different device.
expressServer.listen( expressPort, function() {
console.log('expressServer listening at *:%d', expressPort );
});
// allow CORS on the express server
expressServer.use(function(req, res, next) {
// enable cross original resource sharing to allow html page to access commands
res.header("Access-Control-Allow-Origin", "*");
// return to the console the URL that is being accesssed, leaving for clarity
console.log("\n"+req.url);
next();
});
expressServer.get('/copyImages', function (req, res) {
// user accesses /copyImages and the copyImages function is called
copyImages(function(result) {
res.end(result + "\n");
});
});
Copy images from Theta S Camera to Raspberry Pi and delete those from the cameras
var resultCopyImages = "";
copyImages = function (callback) {
resultCopyImages = "Copying images from both cameras...\n";
for (var i = 0; i < camArray.length; i++) {
copyOneCamImages(i, callback);
}
return (callback(resultCopyImages));
//how to return multiple messages?
}
copyOneCamImages = function (camID, callback) {
d.on('error', function(err){
console.log('There was an error copying the images');
return(callback('There was an error running a function, please make sure all cameras are connected and restart the server'));
})
d.run(function(){
var imageFolder = baseImageFolder + camID;
// if the directory does not exist, make it
if (!fs.existsSync(imageFolder)) {
fs.mkdirSync(imageFolder);
console.log("no 'images' folder found, so a new one has been created!");
}
// initialise total images, approximate time
var totalImages = 0;
var approxTime = 0;
// get the first image and do not include thumbnail
var entryCount = 1;
var includeThumb = false;
var filename;
var fileuri;
// get the total amount of images
camArray[camID].oscClient.listImages(entryCount, includeThumb)
.then(function (res) {
totalImages = res.results.totalEntries;
approxTime = totalImages * 5;
resultCopyImages = '';
resultCopyImages = 'Camera ' + (camID + 1) + ': Copying a total of: ' + totalImages + ' images'
+ '\nTo folder: ' + imageFolder
+ '\nThis process will take approximately: ' + approxTime + ' seconds \n';
console.log(resultCopyImages);
callback(resultCopyImages);
});
// copy a single image, with the same name and put it in images folder
camArray[camID].oscClient.listImages(entryCount, includeThumb)
.then(function (res) {
filename = imageFolder + '/' + res.results.entries[0].name;
fileuri = res.results.entries[0].uri;
imagesLeft = res.results.totalEntries;
// gets the image data
camArray[camID].oscClient.getImage(res.results.entries[0].uri)
.then(function (res) {
var imgData = res;
fs.writeFile(filename, imgData);
camArray[camID].oscClient.delete(fileuri).then(function () {
if (imagesLeft != 0) {
// callback to itself to continue copying if images are left
callback(copyOneCamImages(camID, callback));
//????????????????????????????????????????????????????????????????????????????
//if(imagesLeft==1) return(callback("Finished copying"));
}/* else {
resultCopyImages = "Finshed copying image.\n";
console.log(resultCopyImages);
}
else if
return(callback(resultCopyImages));
}*/
});
});
});
})
}
So far there is no real answer to the question I asked so we have concluded the project and skipped the feature. However, it's just the matter of mastering the REST API and the asynchronous functions in NodeJs. The project is expected to continue for a next version sometime next year.
My issue of concern is to find out the possible mistakes in my code which might hamper the working of opentok services to run smoothly(without any error) in my code. There something might be going wrong with my code. Please examine How Am I ending any video chat through my code.And other codes might have been written incorrectly
The library version I'm using is this
<script type="text/javascript" src="http://static.opentok.com/webrtc/v2.2/js/TB.min.js" ></script>
I'm using dot net sdk to generate sessionId and tokens on server side
I have published my application online , and it runs well 30 % time but 70% time it throws errors like sessionInfoError or many other errors
Api key secret and other settings aremade in web.config file like this
<appSettings>
<add key="opentok_key" value="******"/>
<add key="opentok_secret" value="***********************"/>
<add key="opentok_server" value="https://api.opentok.com"/>
<add key="opentok_token_sentinel" value="T1=="/>
<add key="opentok_sdk_version" value="tbdotnet"/>
Rest of the code and functions written with the help of tokbox documentation are like this
var sessionId;
var token;
var apiKey = "*******";
var publisher_connections = {};
var publisher;
var session;
var Id;
var streamedTime;
var hours;
var minutes;
var seconds;
function a() {
sessionId = document.getElementById('<%= hdn.ClientID%>').value;
token = document.getElementById('<%= hdn1.ClientID%>').value;
session = TB.initSession(sessionId);
session.addEventListener("sessionConnected", sessionConnectedHandler);
session.addEventListener('sessionDisconnected', sessionDisconnectedHandler);
session.addEventListener("streamCreated", streamCreatedHandler);
session.addEventListener("sessionDestroyed", sessionDestroy);
session.addEventListener("signal", signalHandler);
session.addEventListener("streamDestroyed", streamDestroyedHandler);
session.addEventListener('connectionCreated', connectionCreatedHandler);
session.addEventListener('connectionDestroyed', connectionDestroyedHandler);
TB.addEventListener("exception", exceptionHandler);
TB.setLogLevel(TB.DEBUG);
session.connect(apiKey, token);
}
function sessionConnectedHandler(event) {
console.log("connected");
subscribeToStreams(event.streams);
session.publish();
}
function sessionDisconnectedHandler(event) {
alert("Session Disconnected");
for (var i = 0; i < event.streams.length; i++) {alert(event.streams[i].connection.connectionId);
delete publisher_connections[event.streams[i].connection.connectionId];
}
publisher = null;
}
function streamCreatedHandler(event) {
console.log("created");
subscribeToStreams(event.streams);
for (var i = 0; i < event.streams.length; i++) {
publisher_connections[event.streams[i].connection.connectionId] = 1;
}
}
function subscribeToStreams(streams) {
for (var i = 0; i < streams.length; i++) {
var stream = streams[i];
if (stream.connection.connectionId != session.connection.connectionId) {
var subscriber = session.subscribe(stream);
if (stream.connection.data == "accept") {
alert(stream.connection.data + " Joined You");
startTimer();
}
else {
alert(stream.connection.data + " Joined You");
UpdateInitializedTime();
startTimer();
}
}
}
}
function exceptionHandler(event) {
alert(event.message);
}
function sessionDestroy(event) {
session.disconnect();
alert("Session Destroyed");
}
}
function streamDestroyedHandler(event) {
for (var i = 0; i < event.streams.length; i++) {
delete publisher_connections[event.streams[i].connection.connectionId];
//alert("Someone left you");
}
}
function connectionDestroyedHandler(event) {
alert(event.streams[i].connection.connectionId + " left the conversation");
// This signals that connections were destroyed
}
function connectionCreatedHandler(event) {
// This signals new connections have been created.
// alert("this");
// alert(connection.data);
}
There is a setInterval function which calls itself every second and will end video chat when fixed time become 00:00:00
function timeOver(){
if (hours == 00 && minutes == 00 && seconds == 00) {
session.disconnect();
alert("Time Given For this Video Chat is Over");
}
}
I have a button for disconnecting from session
<input type="button" value="Disconnect" id="btnDisconnect" onclick="sessionDestroy()" />
it calls the sessionDestroy() function on clicking
Please examine these codes like a doctor
You code looks alright. Please keep in mind that Stack Overflow is used to ask questions and solve bugs. Using it as a place to proofread your code is not the intended idea.
link to article: http://www.html5rocks.com/en/tutorials/eventsource/basics/
The node.js SSE server is not working in that example. I end up with an open connection to /events, but no response is received by the browser.
sse-server.js
var http = require('http');
var sys = require('sys');
var fs = require('fs');
http.createServer(function(req, res) {
//debugHeaders(req);
if (req.headers.accept && req.headers.accept == 'text/event-stream') {
if (req.url == '/events') {
sendSSE(req, res);
} else {
res.writeHead(404);
res.end();
}
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(fs.readFileSync(__dirname + '/sse-node.html'));
res.end();
}
}).listen(8000);
function sendSSE(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
var id = (new Date()).toLocaleTimeString();
// Sends a SSE every 5 seconds on a single connection.
setInterval(function() {
constructSSE(res, id, (new Date()).toLocaleTimeString());
}, 5000);
constructSSE(res, id, (new Date()).toLocaleTimeString());
}
function constructSSE(res, id, data) {
res.write('id: ' + id + '\n');
res.write("data: " + data + '\n\n');
}
function debugHeaders(req) {
sys.puts('URL: ' + req.url);
for (var key in req.headers) {
sys.puts(key + ': ' + req.headers[key]);
}
sys.puts('\n\n');
}
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
var source = new EventSource('/events');
source.onmessage = function(e) {
document.body.innerHTML += e.data + '<br>';
};
</script>
</body>
</html>
The problem was with the server. In my case I was using node with IIS using the iisnode node package. To solve this, I needed to configure iisnode in the web.config like so:
<iisnode flushResponse="true" />
After this, everything worked fine. Others with a similar issue may start here, but apache and nginx may have similar configuration requirements.
Why did you comment out the res.end() at the end of the sendSSE function? That's the method that actually sends the response to the browser.
I already try that code and is working for me, as you are not using ngnix, or any other server as proxy for your node instances I would believe that the problem is with your machine, if you have firewall or anti virus running, stop it, and try again or any other software that could be intercepting yours req and res.
Make sure you have saved the HTML file with same name as described sse-node.html in same directory. Other thing might be make sure 8000 port is open in your local machine no one is using it. or change the port and re-run sse-server.js. its working for me as is.
I faced the same issue. Nothing wrong with your code just Provide Full Resource Address in your HTML File.
var source = new EventSource('http://localhost:8000/events'); //true way
instead of
var source = new EventSource('/events'); //improper way