Last active
January 19, 2017 20:00
-
-
Save RJ722/37332d55a8b54a7a166b9fffff5cb43c to your computer and use it in GitHub Desktop.
changes in livecam module for windows support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Original File: https://github.com/sepehr-laal/livecam/blob/master/livecam.js | |
// Revised, because it was not working on widows, so I just wanted the author to know wethet it was me or something else is broken. | |
/*! | |
* @class GstLaunch | |
* @brief Class that encapsulates "gst-launch" executable. | |
*/ | |
function GstLaunch() { | |
const gst_launch_executable = 'gst-launch-1.0'; | |
const gst_launch_versionarg = '--version'; | |
const SpawnSync = require('child_process').spawnSync; | |
const Spawn = require('child_process').spawn; | |
const Assert = require('assert'); | |
const Path = require('path'); | |
const OS = require('os'); | |
const FS = require('fs'); | |
/*! | |
* @fn getPath | |
* @brief Returns path to gst-launch or undefined on error | |
*/ | |
var getPath = function() { | |
var detected_path = undefined; | |
if (OS.platform() == 'win32') { | |
// On Windows, GStreamer MSI installer defines the following | |
// environment variables. | |
const detected_path_x64 = process.env.GSTREAMER_1_0_ROOT_X86_64; | |
const detected_path_x32 = process.env.GSTREAMER_1_0_ROOT_X86; | |
if (detected_path_x64 || detected_path_x32) { | |
// If both variables are present, favor the architecture | |
// of GStreamer which is the same as Node.js runtime. | |
if (detected_path_x64 && detected_path_x32) { | |
if (process.arch == 'x64') | |
detected_path = detected_path_x64; | |
else if (process.arch == 'x32') | |
detected_path = detected_path_x32; | |
} else { | |
detected_path = detected_path_x64 || detected_path_x32; | |
} | |
} | |
if (detected_path) { | |
detected_path = Path.join( | |
detected_path, | |
'bin', | |
(gst_launch_executable + '.exe')); | |
try { FS.accessSync(detected_path, FS.F_OK); } | |
catch (e) { detected_path = undefined; } | |
} | |
else { | |
// Look for GStreamer on PATH | |
var path_dirs = process.env.PATH.split(';'); | |
for (var index = 0; index < path_dirs.length; ++index) { | |
try { | |
var base = Path.normalize(path_dirs[index]); | |
var bin = Path.join( | |
base, | |
(gst_launch_executable + '.exe')); | |
FS.accessSync(bin, FS.F_OK); | |
detected_path = bin; | |
} catch (e) { /* no-op */ } | |
} | |
} | |
} | |
else if (OS.platform() == 'linux') { | |
// Look for GStreamer on PATH | |
var path_dirs = process.env.PATH.split(':'); | |
for (var index = 0; index < path_dirs.length; ++index) { | |
try { | |
var base = Path.normalize(path_dirs[index]); | |
var bin = Path.join( | |
base, | |
gst_launch_executable); | |
FS.accessSync(bin, FS.F_OK); | |
detected_path = bin; | |
} catch (e) { /* no-op */ } | |
} | |
} | |
return detected_path; | |
} | |
/*! | |
* @fn getVersion | |
* @brief Returns version string of GStreamer on this machine by | |
* invoking the gst-launch executable or 'undefined' on failure. | |
*/ | |
var getVersion = function() { | |
var version_str = '1.11.1'; | |
return version_str; | |
} | |
/*! | |
* @fn isAvailable | |
* @brief Answers true if gst-launch executable is available | |
*/ | |
var isAvailable = function() { | |
return getVersion()!= undefined; | |
} | |
/*! | |
* @fn spawnPipeline | |
* @brief Spawns a GStreamer pipeline using gst-launch | |
* @return A Node <child-process> of the launched pipeline | |
* @see To construct a correct pipeline arg, consult the link below: | |
* https://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/chapter-programs.html | |
* @usage spawnPipeline('videotestsrc ! autovideosink') | |
*/ | |
var spawnPipeline = function(pipeline) { | |
Assert.ok(typeof(pipeline), 'string'); | |
Assert.ok(isAvailable(), "gst-launch is not available."); | |
var gst_launch_path = getPath(); | |
Assert.ok(typeof(gst_launch_path), 'string'); | |
return Spawn(gst_launch_path, pipeline.split(' ')); | |
} | |
return { | |
'getPath' : getPath, | |
'getVersion' : getVersion, | |
'isAvailable' : isAvailable, | |
'spawnPipeline' : spawnPipeline | |
} | |
} | |
/*! | |
* @class GstLiveCamServer | |
* @brief Encapsulates a GStreamer pipeline to broadcast default webcam. | |
*/ | |
function GstLiveCamServer(config) { | |
const Assert = require('assert'); | |
const OS = require('os'); | |
Assert.ok(OS.platform() == 'win32' || OS.platform() == 'linux', | |
"livecam module supports Windows and Linux for broadcasting."); | |
config = config || {}; | |
Assert.ok(typeof(config), 'object'); | |
const fake = config.fake || false; | |
const width = config.width || 0; | |
const height = config.height || 0; | |
const framerate = config.framerate || 0; | |
const grayscale = config.grayscale || false; | |
Assert.ok(typeof(fake), 'boolean'); | |
Assert.ok(typeof(width), 'number'); | |
Assert.ok(typeof(height), 'number'); | |
Assert.ok(typeof(framerate), 'number'); | |
Assert.ok(typeof(grayscale), 'boolean'); | |
var gst_multipart_boundary = '--videoboundary'; | |
var gst_video_src = ''; | |
if (!fake) { | |
if (OS.platform() == 'win32') | |
gst_video_src = 'ksvideosrc ! decodebin'; | |
else if (OS.platform() == 'linux') | |
gst_video_src = 'v4l2src ! decodebin'; | |
else | |
gst_video_src = 'videotestsrc'; | |
} | |
else { | |
gst_video_src = 'videotestsrc'; | |
} | |
if (width > 0 || height > 0) { | |
gst_video_src += ' ! videoscale ! video/x-raw,width=' + parseInt(width) + ',height=' + parseInt(height); | |
} | |
if (framerate > 0) { | |
gst_video_src += ' ! videorate ! video/x-raw,framerate=' + parseInt(framerate) + '/1'; | |
} | |
if (grayscale) { | |
gst_video_src += ' ! videobalance saturation=0.0 ! videoconvert'; | |
} | |
/*! | |
* @fn start | |
* @brief Starts a GStreamer pipeline that broadcasts the default | |
* webcam over the given TCP address and port. | |
* @return A Node <child-process> of the launched pipeline | |
*/ | |
var start = function(tcp_addr, tcp_port) { | |
Assert.ok(typeof(tcp_addr), 'string'); | |
Assert.ok(typeof(tcp_port), 'number'); | |
const cam_pipeline = gst_video_src + ' ! jpegenc ! multipartmux boundary="' | |
+ gst_multipart_boundary + '" ! tcpserversink host=' + tcp_addr + ' port=' + tcp_port; | |
var gst_launch = new GstLaunch(); | |
if (gst_launch.isAvailable()) { | |
console.log('GstLaunch found: ' + gst_launch.getPath()); | |
console.log('GStreamer version: ' + gst_launch.getVersion()); | |
console.log('GStreamer pipeline: ' + cam_pipeline); | |
return gst_launch.spawnPipeline(cam_pipeline); | |
} else { | |
throw new Error('GstLaunch not found.'); | |
} | |
} | |
return { | |
'start' : start | |
} | |
} | |
/*! | |
* @class SocketCamWrapper | |
* @brief A wrapper that re-broadcasts GStreamer's webcam TCP packets in | |
* Socket.IO events. This way browsers can fetch and understand webcam | |
* video frames. | |
* @credit http://stackoverflow.com/a/23605892/388751 | |
*/ | |
function SocketCamWrapper( | |
gst_tcp_addr, | |
gst_tcp_port, | |
broadcast_tcp_addr, | |
broadcast_tcp_port) { | |
const Net = require('net'); | |
const Http = require('http'); | |
const Dicer = require('dicer'); | |
const Assert = require('assert'); | |
const SocketIO = require('socket.io'); | |
const gst_multipart_boundary = '--videoboundary'; | |
/*! | |
* @fn wrap | |
* @brief wraps a TCP server previously started by GstLiveCamServer. | |
*/ | |
var wrap = function(gst_tcp_addr, | |
gst_tcp_port, | |
broadcast_tcp_addr, | |
broadcast_tcp_port) { | |
Assert.ok(typeof(gst_tcp_addr), 'string'); | |
Assert.ok(typeof(gst_tcp_port), 'number'); | |
Assert.ok(typeof(broadcast_tcp_addr), 'string'); | |
Assert.ok(typeof(broadcast_tcp_port), 'number'); | |
var socket = Net.Socket(); | |
socket.connect(gst_tcp_port, gst_tcp_addr, function() { | |
var io = SocketIO.listen( | |
Http.createServer() | |
.listen(broadcast_tcp_port, broadcast_tcp_addr)); | |
var dicer = new Dicer({ boundary: gst_multipart_boundary }); | |
dicer.on('part', function(part) { | |
var frameEncoded = ''; | |
part.setEncoding('base64'); | |
part.on('data', function(data) { frameEncoded += data; }); | |
part.on('end', function() { io.sockets.emit('image', frameEncoded); }); | |
}); | |
dicer.on('finish', function() { | |
console.log('Dicer finished: ' + broadcast_tcp_addr + ':' + broadcast_tcp_port); | |
}); | |
socket.on('close', function() { | |
console.log('Socket closed: ' + broadcast_tcp_addr + ':' + broadcast_tcp_port); | |
}); | |
socket.pipe(dicer); | |
}); | |
} | |
return { | |
'wrap' : wrap | |
} | |
} | |
/*! | |
* @class LiveCamUI | |
* @brief serves a minimal UI to view the webcam broadcast. | |
*/ | |
function LiveCamUI() { | |
const Http = require('http'); | |
const Assert = require('assert'); | |
const template = (function(){/* | |
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>livecam UI</title> | |
<script type="text/javascript" src="https://cdn.socket.io/socket.io-1.4.5.js"></script> | |
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css"> | |
<style type="text/css">html,body,.feed,.feed img{width:100%;height:100%;}</style> | |
</head> | |
<body> | |
<div class="feed"><img id="video" src="" /></div> | |
<script> | |
var webcam_addr = "@WEBCAM_ADDR@"; | |
var webcam_port = "@WEBCAM_PORT@"; | |
var webcam_host = $(".feed img"); | |
var socket = io.connect('http://' + webcam_addr + ':' + webcam_port); | |
socket.on('image', function (data) { | |
webcam_host.attr("src", "data:image/jpeg;base64," + data ); | |
}); | |
</script> | |
</body> | |
</html> | |
*/}).toString().match(/\/\*\s*([\s\S]*?)\s*\*\//m)[1]; | |
var server = undefined; | |
var serve = function(ui_addr, ui_port, webcam_addr, webcam_port) { | |
Assert.ok(typeof(ui_addr), 'object'); | |
Assert.ok(typeof(ui_port), 'number'); | |
Assert.ok(typeof(webcam_addr), 'object'); | |
Assert.ok(typeof(webcam_port), 'number'); | |
close(); | |
server = Http.createServer(function(request, response) { | |
response.writeHead(200, {"Content-Type": "text/html"}); | |
response.write(template | |
.replace('@WEBCAM_ADDR@', webcam_addr) | |
.replace('@WEBCAM_PORT@', webcam_port)); | |
response.end(); | |
}); | |
server.listen(ui_port, ui_addr); | |
console.log('Open http://' + ui_addr + ':' + ui_port + '/ in your browser!'); | |
} | |
var close = function() { | |
if (server) { | |
server.close(); | |
server = undefined; | |
} | |
} | |
return { | |
'serve' : serve, | |
'close' : close | |
} | |
} | |
/*! | |
* @class LiveCam | |
* @brief starts a livecam server at given config params | |
* @note config can have the following options: | |
* config.gst_tcp_addr --> where GStreamer TCP socket host is | |
* [optional] [default: 127.0.0.1] | |
* config.gst_tcp_port --> where GStreamer TCP socket port is | |
* [optional] [default: 10000] | |
* config.ui_addr --> where minimal UI host is | |
* [optional] [default: 127.0.0.1] | |
* config.ui_port --> where minimal UI port is | |
* [optional] [default: 11000] | |
* config.broadcast_addr --> where Socket IO host is (browser-visible) | |
* [optional] [default: 127.0.0.1] | |
* config.broadcast_port --> where Socket IO port is (browser-visible) | |
* [optional] [default: 12000] | |
* config.start --> event emitted when streaming is started | |
* [optional] [default: null] | |
*/ | |
function LiveCam(config) { | |
const Assert = require('assert'); | |
config = config || {}; | |
Assert.ok(typeof(config), 'object'); | |
const gst_tcp_addr = config.gst_addr || "127.0.0.1"; | |
const gst_tcp_port = config.gst_port || 10000; | |
const ui_addr = config.ui_addr || "127.0.0.1"; | |
const ui_port = config.ui_port || 11000; | |
const broadcast_addr = config.broadcast_addr || "127.0.0.1"; | |
const broadcast_port = config.broadcast_port || 12000; | |
const start = config.start; | |
const webcam = config.webcam || {}; | |
if (start) Assert.ok(typeof(start), 'function'); | |
if (broadcast_port) Assert.ok(typeof(broadcast_port), 'number'); | |
if (broadcast_addr) Assert.ok(typeof(broadcast_addr), 'string'); | |
if (ui_port) Assert.ok(typeof(ui_port), 'number'); | |
if (ui_addr) Assert.ok(typeof(ui_addr), 'string'); | |
if (gst_tcp_port) Assert.ok(typeof(gst_tcp_port), 'number'); | |
if (gst_tcp_addr) Assert.ok(typeof(gst_tcp_addr), 'string'); | |
if (webcam) Assert.ok(typeof(webcam), 'object'); | |
if (!(new GstLaunch()).isAvailable()) | |
{ | |
console.log("=================================================="); | |
console.log("Unable to locate gst-launch executable."); | |
console.log("Look at https://github.com/sepehr-laal/livecam"); | |
console.log("You are most likely missing the GStreamer runtime."); | |
console.log("=================================================="); | |
throw new Error('Unable to broadcast.'); | |
} | |
console.log("LiveCam parameters:", { | |
'broadcast_addr' : broadcast_addr, | |
'broadcast_port' : broadcast_port, | |
'ui_addr' : ui_addr, | |
'ui_port' : ui_port, | |
'gst_tcp_addr' : gst_tcp_addr, | |
'gst_tcp_port' : gst_tcp_port | |
}); | |
var broadcast = function() { | |
var gst_cam_ui = new LiveCamUI(); | |
var gst_cam_wrap = new SocketCamWrapper(); | |
var gst_cam_server = new GstLiveCamServer(webcam); | |
var gst_cam_process = gst_cam_server.start(gst_tcp_addr, gst_tcp_port); | |
gst_cam_process.stdout.on('data', function(data) { | |
console.log(data.toString()); | |
// This catches GStreamer when pipeline goes into PLAYING state | |
if(data.toString().includes('Setting pipeline to PLAYING') > 0) { | |
gst_cam_wrap.wrap(gst_tcp_addr, gst_tcp_port, broadcast_addr, broadcast_port); | |
gst_cam_ui.serve(ui_addr, ui_port, broadcast_addr, broadcast_port); | |
gst_cam_ui.close(); | |
if (start) start(); | |
} | |
}); | |
gst_cam_process.stderr.on('data', function(data) { console.log(data.toString()); gst_cam_ui.close(); }); | |
gst_cam_process.on('error', function(err) { console.log("Webcam server error: " + err); gst_cam_ui.close(); }); | |
gst_cam_process.on('exit', function(code) { console.log("Webcam server exited: " + code); gst_cam_ui.close(); }); | |
} | |
return { | |
'broadcast' : broadcast | |
} | |
} | |
module.exports = LiveCam; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var ip = '127.0.0.1'; | |
const LiveCam = require('livecam'); | |
const webcam_server = new LiveCam | |
({ | |
// address and port of the webcam UI | |
'ui_addr' : ip, | |
'ui_port' : 11000, | |
// address and port of the webcam Socket.IO server | |
// this server broadcasts GStreamer's video frames | |
// for consumption in browser side. | |
'broadcast_addr' : ip, | |
'broadcast_port' : 12000, | |
// address and port of GStreamer's tcp sink | |
'gst_tcp_addr' : ip, | |
'gst_tcp_port' : 10000, | |
// callback function called when server starts | |
'start' : function() { | |
console.log('WebCam server started!'); | |
}, | |
// webcam object holds configuration of webcam frames | |
'webcam' : { | |
// should frames be converted to grayscale (default : false) | |
'grayscale' : false, | |
// should width of the frame be resized (default : 0) | |
// provide 0 to match webcam input | |
'width' : 0, | |
// should height of the frame be resized (default : 0) | |
// provide 0 to match webcam input | |
'height' : 0, | |
// should a fake source be used instead of an actual webcam | |
// suitable for debugging and development (default : false) | |
'fake' : false, | |
// framerate of the feed (default : 0) | |
// provide 0 to match webcam input | |
'framerate' : 25 | |
} | |
}); | |
webcam_server.broadcast(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment