使用NODE.JS和HTML5实现低延迟(50ms)视频流传输

13

目标:

我正在构建一台FPV机器人,希望通过本地Wi-Fi连接使用Web浏览器对其进行控制。

我使用的是搭载Raspbian Stretch的树莓派3B+。我自己制作了电机控制和功率调节帽。

经过大量的研究和测试,我决定使用node.JS作为HTTP服务器和socket.io来提供与机器人低延迟的双向通信。这个组合可以实现约7毫秒的延迟。

机器人图片

问题:

我需要从连接到RPI的USB摄像头实时传输低延迟的视频流至Web浏览器。我的目标是在至少10FPS下实现640x480分辨率并且延迟不超过50ms。即使为此牺牲图像质量,我也很愿意获得更快的响应速度。

如果可能的话,我想要采用UDP协议进行视频传输,以提高传输的可靠性。

如果可能的话,我希望能够使用现代Web浏览器本身就能解码的视频流。我想使用H264编解码器和HTML5视频标签。如果没有其他选择,我可以借助JavaScript播放器。

尝试过的方法:

我进行了广泛的研究并尝试了许多工具。

其中包括VLC、mjpg streamer、gstreamer和raspivid等。有几次我成功实现了Web浏览器可视化流,但最佳情况下也只能获得320x240分辨率下700毫秒的延迟。这离我的目标非常非常遥远。

目前我正在研究WebRTC解决方案。

问题:

我想寻求NODE.JS套件或其他解决方案的建议,以提供一个UDP H264视频流,该视频流可以由HTML5视频标签解码并具有50ms的目标延迟。

谢谢

更新:

感谢您的答案!我将不断更新这个问题,并在解决方案可用后发布。

推送单个帧

我尝试了一种不同的方法,通过websocket推送每个200KB的640x480 jpg帧,得到约190ms的延迟。我可以通过重用对象来做得更好,但现在我暂停了这个尝试。

更新2:

在研究WebRTC时,我找到了一个看起来很容易的技术栈。 服务器端使用V4L2作为驱动程序,FFMPEG将其转码为带有TS封装的MPEG1 http流,在本地将流传输到node js websocket中。 客户端有一个javascript代码,可以解码MPEG1 TS流,并将画布对象绘制到HTML页面中。

它能够实现640x480 @ 20FPS,并具有240毫秒的延迟。 对于MVP来说已经足够好了,但我将继续努力将其降低。 代码在答案中。


1
欢迎来到 Stack Overflow。感谢您提出结构良好的问题。 - Harijs Deksnis
确实,好问题。10fps下50ms的延迟是不可能的,很抱歉说这个。通常需要一个帧时间(100ms)来压缩和传输每个视频帧,并且需要另一个帧时间来解压它。因此最小值为200ms。即使您拥有线速编解码器,最小值也将为100ms。 - O. Jones
请查看我在此的答案:https://dev59.com/Y1oU5IYBdhLWcg3wcmq8#37475943 - Brad
3个回答

11

我从这里的代码进行了修改,并将其与http服务器和socket.io控件集成:

https://github.com/phoboslab/jsmpeg

服务器:

V4L2 -> FFMPEG(MPEG1 TS) -> NODE HTTP服务器 -> NODE Websocket广播

客户端:

Websocket -> JavaScript(解码MPEG1 TS并绘制到HTML画布上) -> HTML画布

这个堆栈实现了640x480 @ 20FPS,延迟为240ms。虽然距离我的目标还有很大差距,但作为MVP已经足够好了。双向控制的延迟为7ms,非常出色。

这个堆栈受到转码和解码阶段的限制,而且RPI变得非常热。通过websocket传输原始数据看起来不错,我将来会对每个步骤的延迟进行分析。

Profiling

执行:

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

服务器端 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://dev59.com/nHA65IYBdhLWcg3wvxaE
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";

 }
}

客户端 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播放器

你可以从这里获取我使用的Javascript播放器: https://github.com/phoboslab/jsmpeg/blob/master/jsmpeg.min.js


嗨。我有一个问题没明白。客户端和你的机器人需要在同一个WiFi连接下吗? - Miguel
这太棒了!我遇到的唯一问题是......首先,style.css没有列出来,所以我不得不在代码中删除所有对该文件的引用......其次,我必须使用nvm程序升级我的nodejs版本。我仍然在Ubuntu 18.04上,所以旧的node版本无法工作并且会出现错误。谢谢。 - don bright
请注意,我花了大约10个小时来尝试WebRTC,但仍然找不到一个好的解决方案... 但是这个方法只花了我20分钟,非常好用。 - don bright

1
我希望您能提供有关NODE.JS软件包或其他解决方案的建议,以提供一个UDP H264视频流,该流可以通过HTML5视频标记进行解码,并具有50ms的目标延迟。
在这种配置下,几乎肯定不可能实现。如果您放弃视频标记要求,并仅在浏览器中使用直接的WebRTC,则可能可以将延迟降低到约150ms。

谢谢回答。看起来很有前途,我正在研究WebRTC。 - 05032 Mendicant Bias

0
谢谢你的解决方案。我必须使用Gstreamer管道使其工作,但我不认为Gstreamer可以直接发送到http,所以我添加了一个UDP服务器。
它与相同的HTML一起工作。
这是我的Gstreamer测试管道:
gst-launch-1.0 videotestsrc ! videoconvert ! videoscale ! video/x-raw,width=800,height=600 ! avenc_mpeg1video ! mpegtsmux name=mux ! rtpmp2tpay ! udpsink host=0.0.0.0 port=8083

更新的服务器端nodeJS
//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");

var dgram = require('dgram');

//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";



// UDP server to receive GStreamer pipeline.
var udpServer = dgram.createSocket('udp4');

udpServer.on('listening', function () {
    var address = udpServer.address();
    console.log('UDP server listening on ' + address.address + ':' + address.port);
});

udpServer.on('message', function (msg, rinfo) {
    console.log('Received UDP message with ' + msg.length + ' bytes');
    streaming_websocket.broadcast(msg); // broadcast datas via websocket

});

udpServer.bind(8083);

// Ligne pour gérer les connexions WebSocket (vous pouvez garder celle-ci)
var io = require("socket.io")(udpServer);
// console.log(io);
//-----------------------------------------------------------------------------------
//  DETECT SERVER OWN IP
//-----------------------------------------------------------------------------------

//If just one interface, store the server IP Here
var server_ip;
//Get local IP address of the server
//https://dev59.com/nHA65IYBdhLWcg3wvxaE
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('test');
        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) {
                    console.log('sending datas');
                    client.send(data);
                } else {
                    console.log('.....no')
                }
            }
        );
};


//-----------------------------------------------------------------------------------
//  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";

    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接