My applet connects to FMS4.5 using RTMFP. I open one NetConnection and stream video from the server to the applet using a NetStream. When i want to execute a function on either the applet or the server i use a NetConnection.call();
My question is does AS3 do anything internally to make sure the call over UDP happens or is there a risk of UDP loss and the function never happening when it is called?
Assuming there might be times the UDP traffic is lost i made a responder retry onStatus. Does this code look like it would actually do the job or is it overkill?
// This is code on FMS to call a function in the applet.
var networkRetryMax = 30;
if (something == true) doSomethingInApplet(client, 0);
function doSomethingInApplet (client, retryCount) {
if (retryCount < networkRetryMax) {
retryCount++;
client.call("funcNameInApplet", new doSomethingInAppletResponder(client, retryCount), "foobar");
}
return;
}
function doSomethingInAppletResponder (client, retryCount) {
this.onResult = function() {...}
this.onStatus = function() {doSomethingInApplet(client, retryCount);}
return;
}
And...
// This is code in the applet.
// I return true to give it something to onResult or onStatus.
public static function funcNameInApplet(result:String):Boolean {
trace("Result: " + result);
return true;
}
So would this ensure the function is called over UDP / RTMFP?
Or is .call() reliability handled internally and this is unnecessary?
Related
OBJECTIVE:
I'm building a FPV robot, I want to control it with a with a webbrowser over a local wi-fi connection.
I'm using a raspberry pi 3B+ with Raspbian Stretch. I built my own motor control and power regulator hat.
After lots of research testing, I decided to use node.JS as http server and socket.io to provide a low latency bidirectional communication with my robot. This stack achieve about 7ms of latency.
Picture of the robot
PROBLEM:
I need to stream low latency video from an USB camera attached to the RPI to the browser. My target is to achieve at least 640x480 resolution at 10FPS with 50ms of latency or better. I'm happy sacrificing visual fedelity to get a quicker response from my robot.
If possible I would like to stream in UDP to improve the reliability of the stream.
If possible I would like to stream a video that modern webbrowsers can natively decode. I'd like to use a H264 codec and the HTML5 video tag.
I can fall back to use a javascript player if there is no other option.
WHAT I TRIED:
I did an extensive research and tried many tools.
Amongst other, I tried VLC, mjpg streamer, gstreamer and raspivid. A few times I got to a stream the webbrowser could view, but at best I got a latency of 700ms at 320x240. Very very far from my target.
Currently I'm looking into WebRTC solutions.
QUESTION:
I'd like suggestions for NODE.JS packages or other solutions to provide a UDP H264 video stream that can be decoded by an HTML5 video tag with a target latency of 50ms.
Thanks
UPDATE:
Thanks for your answers! I'll keep updating this question and I'll post the solution once it works.
PUSH INDIVIDUAL FRAMES
I tried a different approach by pushing individual 200KB 640x480 jpg frame through websocket and I got a latency of about 190ms. I can probably do a lot better by reusing objects but I'm putting this attempt in hold for now.
UPDATE2:
While researching WebRTC I found a stack that looked easy enough.
Server side it uses V4L2 as driver, FFMPEG to transcode into an MPEG1 http stream with TS encapsulation locally, node js to flip the stream into a websocket.
Client side there is a javascript that decode the MPEG1 TS stream and paint a canvas object into the HTML page.
It achieves 640x480#20FPS with 240mS of latency.
Good enough for an MVP, but I'll keep working to get it down.
Code in the answer.
I adapted code from here and integrated it with an http server and socket.io controls:
https://github.com/phoboslab/jsmpeg
Server:
V4L2 -> FFMPEG (MPEG1 TS) -> NODE HTTP Server -> NODE Websocket broadcast
Client:
Websocket -> Javascript (Decode MPEG1 TS and paint to html canvas) -> Html Canvas
This stack achieve 640x480#20FPS with 240ms of latency. Still far from my target but good enough as MVP. The controls in both directions have a latency of 7ms, which is excellent.
This stack is held back by the transcoding and decoding stage, and the RPI gets really hot. The transport of raw data through websocket looks good, I'm going to profile the latency of each steps in the future.
Execution:
pi#MazeRunner:~ $ node node.js &
pi#MazeRunner:~ $ ffmpeg -f v4l2 -framerate 20 -video_size 640x480 -i /dev/video0 -f mpegts -codec:v mpeg1video -s 640x480 -b:v 600k -bf 0 http://localhost:8080/mystream
Server side NODE.JS
//operating system library. Used to get local IP address
var os = require("os");
//file system library. Used to load file stored inside back end server (https://nodejs.org/api/fs.html)
var fs = require("fs");
//http system library. Handles basic html requests
var http = require("http").createServer(http_handler);
//url library. Used to process html url requests
var url = require("url");
//Websocket
var io = require("socket.io")(http);
//Websocket used to stream video
var websocket = require("ws");
//-----------------------------------------------------------------------------------
// CONFIGURATION
//-----------------------------------------------------------------------------------
//Port the server will listen to
var server_port = 8080;
var websocket_stream_port = 8082;
//Path of the http and css files for the http server
var file_index_name = "index.html";
var file_css_name = "style.css";
var file_jsplayer_name = "jsmpeg.min.js";
//Http and css files loaded into memory for fast access
var file_index;
var file_css;
var file_jsplayer;
//Name of the local video stream
var stream_name = "mystream";
//-----------------------------------------------------------------------------------
// DETECT SERVER OWN IP
//-----------------------------------------------------------------------------------
//If just one interface, store the server IP Here
var server_ip;
//Get local IP address of the server
//https://stackoverflow.com/questions/3653065/get-local-ip-address-in-node-js
var ifaces = os.networkInterfaces();
Object.keys(ifaces).forEach
(
function (ifname)
{
var alias = 0;
ifaces[ifname].forEach
(
function (iface)
{
if ('IPv4' !== iface.family || iface.internal !== false)
{
// skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
return;
}
if (alias >= 1)
{
// this single interface has multiple ipv4 addresses
console.log('INFO: Server interface ' +alias +' - ' + ifname + ':' + alias, iface.address);
}
else
{
server_ip = iface.address;
// this interface has only one ipv4 adress
console.log('INFO: Server interface - ' +ifname, iface.address);
}
++alias;
}
);
}
);
//-----------------------------------------------------------------------------------
// HTTP SERVER
//-----------------------------------------------------------------------------------
// Fetch and serves local files to client
//Create http server and listen to the given port
http.listen
(
server_port,
function( )
{
console.log('INFO: ' +server_ip +' listening to html requests on port ' +server_port);
//Pre-load http, css and js files into memory to improve http request latency
file_index = load_file( file_index_name );
file_css = load_file( file_css_name );
file_jsplayer = load_file( file_jsplayer_name );
}
);
//-----------------------------------------------------------------------------------
// HTTP REQUESTS HANDLER
//-----------------------------------------------------------------------------------
// Answer to client http requests. Serve http, css and js files
function http_handler(req, res)
{
//If client asks for root
if (req.url == '/')
{
//Request main page
res.writeHead( 200, {"Content-Type": detect_content(file_index_name),"Content-Length":file_index.length} );
res.write(file_index);
res.end();
console.log("INFO: Serving file: " +req.url);
}
//If client asks for css file
else if (req.url == ("/" +file_css_name))
{
//Request main page
res.writeHead( 200, {"Content-Type": detect_content(file_css_name),"Content-Length" :file_css.length} );
res.write(file_css);
res.end();
console.log("INFO: Serving file: " +req.url);
}
//If client asks for css file
else if (req.url == ("/" +file_jsplayer_name))
{
//Request main page
res.writeHead( 200, {"Content-Type": detect_content(file_jsplayer_name),"Content-Length" :file_jsplayer.length} );
res.write(file_jsplayer);
res.end();
console.log("INFO: Serving file: " +req.url);
}
//Listening to the port the stream from ffmpeg will flow into
else if (req.url = "/mystream")
{
res.connection.setTimeout(0);
console.log( "Stream Connected: " +req.socket.remoteAddress + ":" +req.socket.remotePort );
req.on
(
"data",
function(data)
{
streaming_websocket.broadcast(data);
/*
if (req.socket.recording)
{
req.socket.recording.write(data);
}
*/
//console.log("broadcast: ", data.length);
}
);
req.on
(
"end",
function()
{
console.log("local stream has ended");
if (req.socket.recording)
{
req.socket.recording.close();
}
}
);
}
//If client asks for an unhandled path
else
{
res.end();
console.log("ERR: Invalid file request" +req.url);
}
}
//-----------------------------------------------------------------------------------
// WEBSOCKET SERVER: CONTROL/FEEDBACK REQUESTS
//-----------------------------------------------------------------------------------
// Handle websocket connection to the client
io.on
(
"connection",
function (socket)
{
console.log("connecting...");
socket.emit("welcome", { payload: "Server says hello" });
//Periodically send the current server time to the client in string form
setInterval
(
function()
{
socket.emit("server_time", { server_time: get_server_time() });
},
//Send every 333ms
333
);
socket.on
(
"myclick",
function (data)
{
timestamp_ms = get_timestamp_ms();
socket.emit("profile_ping", { timestamp: timestamp_ms });
console.log("button event: " +" client says: " +data.payload);
}
);
//"ArrowLeft"
socket.on
(
"keyboard",
function (data)
{
timestamp_ms = get_timestamp_ms();
socket.emit("profile_ping", { timestamp: timestamp_ms });
console.log("keyboard event: " +" client says: " +data.payload);
}
);
//profile packets from the client are answer that allows to compute roundway trip time
socket.on
(
"profile_pong",
function (data)
{
timestamp_ms_pong = get_timestamp_ms();
timestamp_ms_ping = data.timestamp;
console.log("Pong received. Round trip time[ms]: " +(timestamp_ms_pong -timestamp_ms_ping));
}
);
}
);
//-----------------------------------------------------------------------------------
// WEBSOCKET SERVER: STREAMING VIDEO
//-----------------------------------------------------------------------------------
// Websocket Server
var streaming_websocket = new websocket.Server({port: websocket_stream_port, perMessageDeflate: false});
streaming_websocket.connectionCount = 0;
streaming_websocket.on
(
"connection",
function(socket, upgradeReq)
{
streaming_websocket.connectionCount++;
console.log
(
'New websocket Connection: ',
(upgradeReq || socket.upgradeReq).socket.remoteAddress,
(upgradeReq || socket.upgradeReq).headers['user-agent'],
'('+streaming_websocket.connectionCount+" total)"
);
socket.on
(
'close',
function(code, message)
{
streaming_websocket.connectionCount--;
console.log('Disconnected websocket ('+streaming_websocket.connectionCount+' total)');
}
);
}
);
streaming_websocket.broadcast = function(data)
{
streaming_websocket.clients.forEach
(
function each(client)
{
if (client.readyState === websocket.OPEN)
{
client.send(data);
}
}
);
};
//-----------------------------------------------------------------------------------
// FUNCTIONS
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
// SERVER DATE&TIME
//-----------------------------------------------------------------------------------
// Get server time in string form
function get_server_time()
{
my_date = new Date();
return my_date.toUTCString();
}
//-----------------------------------------------------------------------------------
// TIMESTAMP
//-----------------------------------------------------------------------------------
// Profile performance in ms
function get_timestamp_ms()
{
my_date = new Date();
return 1000.0* my_date.getSeconds() +my_date.getMilliseconds()
}
//-----------------------------------------------------------------------------------
// FILE LOADER
//-----------------------------------------------------------------------------------
// Load files into memory for improved latency
function load_file( file_name )
{
var file_tmp;
var file_path = __dirname +"/" +file_name;
//HTML index file
try
{
file_tmp = fs.readFileSync( file_path );
}
catch (err)
{
console.log("ERR: " +err.code +" failed to load: " +file_path);
throw err;
}
console.log("INFO: " +file_path +" has been loaded into memory");
return file_tmp;
}
//-----------------------------------------------------------------------------------
// CONTENT TYPE DETECTOR
//-----------------------------------------------------------------------------------
// Return the right content type to give correct information to the client browser
function detect_content( file_name )
{
if (file_name.includes(".html"))
{
return "text/html";
}
else if (file_name.includes(".css"))
{
return "text/css";
}
else if (file_name.includes(".js"))
{
return "application/javascript";
}
else
{
throw "invalid extension";
}
}
Client Side html
<!DOCTYPE html>
<meta charset="utf-8"/>
<html>
<head>
<title>Maze Runner</title>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var host_ip = document.location.hostname;
console.log("connecting to host: ", host_ip);
//Get references to the html controls
textbox_input1 = window.document.getElementById("my_text_box")
//Connect to the server via websocket
var mysocket = io("http://" +host_ip +":8080");
//Long lived frame object
var last_frame;
//-----------------------------------------
// CONNESSION ACKNOWLEDGE
//-----------------------------------------
// Link is initiated by the client
// Server sends a welcome message when link is estabilished
// Server could send an auth token to keep track of individual clients and login data
mysocket.on
(
"welcome",
(message) =>
{
console.log("Server websocket connession acknoweldged... " +message.payload);
}
)
//-----------------------------------------
// SERVER->CLIENT CONTROLS
//-----------------------------------------
// Server can send an async message to dinamically update the page without reloading
// This is an example message with the server local date and time in string form
mysocket.on
(
"server_time",
(message) =>
{
fill_label( message.server_time );
console.log("Server sent his local time... " +message.server_time);
}
)
function fill_label( payload )
{
textbox_input1.value=payload;
}
//-----------------------------------------
// CLIENT->SERVER CONTROLS
//-----------------------------------------
// Controls inside the webpage can emit async events to the server
// In this example I have a push button and I catch keyboard strokes
//Handler for a pushbutton
function socket_button_handler()
{
mysocket.emit("myclick", { payload: "button was clicked" });
console.log("Button was clicked...");
}
//Listen for keystrokes
window.document.addEventListener
(
"keypress",
function onEvent(event)
{
//Inform the server that a key has been pressed
mysocket.emit("keyboard", { payload: event.key });
console.log("Key press...");
}
);
//-----------------------------------------
// PING-PONG
//-----------------------------------------
// Server sends ping messages with a timestamp
// Client answers with pongs to allow server to profile latency of the channel
//profile messages means the server wants to compute roundway trip
mysocket.on
(
"profile_ping",
(message) =>
{
//Answer back with the received timestamp so that server can compute roundway trip
mysocket.emit("profile_pong", { timestamp: message.timestamp });
console.log( "server wants a pong. server absolute timestamp[ms]: " +message.timestamp );
}
);
</script>
</head>
<body>
<h1>Html+Css Server +low latency Websocket server</h1>
<!-- button control with socket emitter as handler -->
<p> This button will emit a websocket event. The server will be informed in real time of the event. </p>
<button id="my_button" type="button" onclick="socket_button_handler()">Websocket Button!</button>
<!-- input text control -->
<p> This input can be filled through websockets directly by the server in real time </p>
<input id="my_text_box" type="text" value="" size="40">
<!-- canvas object, it's painted by the javascript video decoder -->
<p> This canvas is painted by the javascript player and shows the live stream.'</p>
<canvas id="video-canvas" width=640 height=480></canvas>
<!-- Javascript video decoder, take in a data stream from a websocket and paint on a canvas -->
<script type="text/javascript" src="jsmpeg.min.js"></script>
<script type="text/javascript">
var mycanvas = document.getElementById("video-canvas");
var url = "ws://" + host_ip +":8082/";
var player = new JSMpeg.Player(url, {canvas: mycanvas});
</script>
</body>
</html>
Javascript Player
You can get the javascript player I used from here:
https://github.com/phoboslab/jsmpeg/blob/master/jsmpeg.min.js
I’d like suggestions for NODE.JS packages or other solutions to provide a UDP H264 video stream that can be decoded by an HTML5 video tag with a target latency of 50ms.
That’s Almost certainly not possible in that configuration.
If you drop the video tag requirement, and use just straight WebRTC in the browser, you may be able to get down to about 150ms.
I am using a the timer trigger background task API in windows phone 8.1.
I am just trying to understand the basics here of this API.
I have an application that registers timer trigger background task. Every 15 minutes it triggers the event. I understand that the entry point to the application has this function called RUN.
I am trying to upload a simple picture using the background transfer service API. Since this API is async, I am using BackgroundTaskDeferral to make sure that the background task API adheres to the Async operations.
Here's what I noticed, when you have the upload as a separate function and call it in the RUN method, it shuts down in about 10 seconds. But if you have the whole code in the RUN function itself, it will going on for about 10 minutes.
Is there a way I can override this functionality ? or any idea why this is happening ?
public async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral _deferral = taskInstance.GetDeferral();
//accessMediaUpload();
StorageFolder picfolder = KnownFolders.CameraRoll;
var x = await picfolder.GetFilesAsync();
var enumerator = x.GetEnumerator();
Debug.WriteLine("Number of files are: " + x.Count);
while (enumerator.MoveNext())
{
var file = enumerator.Current;
BackgroundUploader uploader = new BackgroundUploader();
uploader.SetRequestHeader("Filename", file.Name);
UploadOperation upload = uploader.CreateUpload(uri, file);
await upload.StartAsync();
// Get the HTTP response to see the upload result
ResponseInformation response = upload.GetResponseInformation();
if (response.StatusCode == 200)
{
Debug.WriteLine(file.Name + " Uplaoded");
}
}
_deferral.Complete();
}
The code as shown above is the RUN method which is the entry point into the background task. here I have a commented function called accessMediaUpload(). This function contains nothing but the code as shown above to upload the files to a server.
If take away the inline upload code and just uncomment accessMediaUpload(), the background task will run only for a few seconds. but the code above runs fine.
I've not tried the code, as I don't have a working example right now. But from what I've understood from MSDN, you should get Defferal when you plan to run async task and call Complete() after you finish it.
BackgroundTaskDeferral _deferral;
public async void Run(IBackgroundTaskInstance taskInstance)
{
_deferral = taskInstance.GetDeferral();
accessMediaUpload();
}
private async Task accessMediaUpload()
{
StorageFolder picfolder = KnownFolders.CameraRoll;
var x = await picfolder.GetFilesAsync();
var enumerator = x.GetEnumerator();
Debug.WriteLine("Number of files are: " + x.Count);
while (enumerator.MoveNext())
{
var file = enumerator.Current;
BackgroundUploader uploader = new BackgroundUploader();
uploader.SetRequestHeader("Filename", file.Name);
UploadOperation upload = uploader.CreateUpload(uri, file);
await upload.StartAsync();
// Get the HTTP response to see the upload result
ResponseInformation response = upload.GetResponseInformation();
if (response.StatusCode == 200)
{
Debug.WriteLine(file.Name + " Uplaoded");
}
}
_deferral.Complete();
}
Few remarks:
note that IMO you should put your _deferral.Complete(); into finally block of try-catch-finally - just to ensure that it is called even there was an exception. As said at MSDN
Be careful always to complete all obtained background task deferrals. The system does not suspend or terminate the background host process until all of its pending background task deferrals are complete. Leaving background task deferrals outstanding interferes with the system's ability to manage process lifetimes and will cause resources to leak.
the method above should be probably improved as you could get Defferal for each Task separately (if you have more than one) and call Complete at its end. This should allow you to run multiple Tasks and finish the whole BackgroundTask when all asynchronous Tasks are finished (called Complete())
I am implementing of server-sent event(HTML5) in my project without using node.js, It is just simple webpage(another JSP page) call and i get response from it. But when i get response from it but none of method/function(onopen,onmessage or onerror) is execute...
Client Code:
if (!!window.EventSource) {
var source=new EventSource("kitAvailCall.jsp");
source.onopen = function(){
alert("Kit is not available");
source.close();
};
source.onmessage=function(event){
console.log(event.data);
alert("Kit is not available");
}
source.onerror =function(){
console.log("EventSOurce: Getting error call");
alert("Kit is not available");
}
}
Server-side code:
try{
while(true) {
Thread.sleep(15000);
String IpAddress = (String) session.getAttribute("IPName");
boolean bool;
if(IpAddress != null && ((new Date()).getTime() - session.getLastAccessedTime())/1000 > 28){
bool = sample.pingToKit((String) session.getAttribute("IPName"));
System.out.println("Long polling request: "+bool);
//if bool is false then i want to quit loop and back to browser
if(bool == false){
response.setHeader("Content-Type","text/event-stream");
response.setHeader("Cache-Control", "no-cache");
out.print("data: " + bool);
out.flush();
break;
}
}
}
}catch(Exception e){
System.out.println("Going bad CONN:"+ e);
}
I don't know much of java, but i could conjecture that you might be listening to the same controller/jsp servlet(whatever routes data in java) which you made to send as streaming.
Listen on a page other than kitavailcall.jsp. This script is meant only for streaming, use another view/html page other than kitavailcall.jsp
The thing you have to understand is, SSE is a concurrent process, you create another page and run on that page with the javascript(client code) included in that page. And your server should support multi-threading as well.
In the netframework v2, the socket use the Invoke (BeginRead) method to call a method (example ReceiveMsg) as below:
client = new TcpClient();
client.Connect(SERVERIP, PORTNO);
data = new byte[client.ReceiveBufferSize];
SendMessage("hello\n");
client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(client.ReceiveBufferSize), ReceiveMsg, null);
Will this method ReceiveMsg of socket always be in "Auto Mode" such that it will standby to receive the message broadcast by SocketServer?
or It is using System.Threading.Thread.Sleep() to make ReceiveMsg method always active so that it will be in ready mode to respond to when SocketServer broadcast message?
How to do this in Netframework v4 or 4.5 socket for this ReceiveMsg method as BeginRead() is no longer required.
Thanks.
Below is the code for socket in netframework v4 :http://msdn.microsoft.com/en-us/library/hh202858(v=vs.92).aspx (for WP mango/ Wp8)
Client.Connect();
Client.Send();
Client.Receive();
public string Receive()
{
//-- receive the reply from server
string response = "Receiving Operation Timeout";
// We are receiving over an established socket connection
if (_socket != null)
{
// Create SocketAsyncEventArgs context object
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = _socket.RemoteEndPoint;
// Setup the buffer to receive the data
socketEventArg.SetBuffer(new Byte[MAX_BUFFER_SIZE], 0, MAX_BUFFER_SIZE);
// Inline event handler for the Completed event.
// Note: This even handler was implemented inline in order to make this method self-contained.
socketEventArg.Completed += new EventHandler(delegate(object s, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// Retrieve the data from the buffer
response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
response = response.Trim('\0');
}
else
{
response = e.SocketError.ToString();
}
_clientDone.Set();
});
// Sets the state of the event to nonsignaled, causing threads to block
_clientDone.Reset();
// Make an asynchronous Receive request over the socket
_socket.ReceiveAsync(socketEventArg);
// Block the UI thread for a maximum of TIMEOUT_MILLISECONDS seconds.
// If no response comes back within this time then proceed
_clientDone.WaitOne(TIMEOUT_MILLISECONDS);
}
else
{
response = "Socket is not initialized";
}
return response;
}
Anyone knows what the status of Web Worker support in NodeJS is? I found a two year old implementation, node-webworkers, but it didn't run with the current build of NodeJS.
Now there is https://github.com/audreyt/node-webworker-threads which appears to be actively maintained.
Worker Threads reached stable status in 12 LTS. Usage example
const {
Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
if (isMainThread) {
module.exports = function parseJSAsync(script) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: script
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
};
} else {
const { parse } = require('some-js-parsing-library');
const script = workerData;
parentPort.postMessage(parse(script));
}
You can use the child processes, they solve similar problems.
You can look at the specifics of the HTML5 WebWorker source.
With a little care, you can 'redress' the WebWorker to fit as a Node.js worker, by adding a prelude that may look something like this:
const { parentPort } = require('worker_threads')
global.postMessage = function(msg){
parentPort.postMessage(msg)
}
var handler
global.addEventListener = function(kind, callback){
handler = callback
}
parentPort.on('message', msg => {
handler(msg)
})
The specific HTML5 worker added a message event handler using addEventListener, so I registered such a function in global and saved the handler. I also had to supply a postMessage implementation. Finally I registered a Node.js message handler that invokes the HTML5 handler.
Everything works perfectly. No need for any special dependency, just looking at the HTML5 worker code and identify the points where it deals with messages.