omxdirector
Version:
Provide a nodejs interface for omxplayer (Raspberry Pi player). Support multiple file and loop.
290 lines (249 loc) • 6.83 kB
JavaScript
var spawn = require('child_process').spawn;
var util = require('util');
var path = require('path');
var fs = require('fs');
var EventEmitter = require('events').EventEmitter;
var LoopHelper = require('./loop_helper.js').LoopHelper;
var exec = require('child_process').exec;
var onlyOneProcess = require('./watchdog.js').onlyOneProcess;
/* keep compatibility with older versions of nodejs */
if (fs.existsSync) {
path.existsSync = fs.existsSync;
} else {
fs.existsSync = path.existsSync;
}
var omxdirector = function () {
var that = Object.create(EventEmitter.prototype);
var nativeLoop = false;
var handleExitHang = false;
var videoDir = './';
var videoSuffix = '';
var currentVideos = [];
var currentSettings = {};
var loopHelper = null;
var commands = {
'pause': 'p',
'quit': 'q',
'volup': '+',
'voldown': '-'
};
var omxProcess = null;
var paused = false;
var sendAction = function (action) {
if (commands[action] && omxProcess) {
try {
omxProcess.stdin.write(commands[action], function (err) {
console.log(err);
});
} catch (err) {
console.log(err);
}
}
};
var resolveFilePaths = function (videos) {
/* reset currentFiles, it will contain only valid videos */
currentVideos = [];
var ret = [];
videos.forEach(function (video) {
var realPath = path.resolve(videoDir, video + videoSuffix);
if (fs.existsSync(realPath)) {
ret.push(realPath);
currentVideos.push(video);
} else {
that.emit('error', new Error('File does not exist: ' + realPath));
}
});
return ret;
};
/*
* Get video length and check, after its duration, if
* process is hanging. Correct a bug of omxplayer
* https://github.com/popcornmix/omxplayer/issues/124
* https://github.com/popcornmix/omxplayer/issues/12
*/
var startWatchdog = function (filename, pid) {
if (!handleExitHang) {
return;
}
getVideoLength(filename, function (durationMs) {
setTimeout(function () {
if (pid === omxProcess.pid) {
omxProcess.kill('SIGKILL');
}
}, durationMs + 10000);
});
};
var open = function (videos, options) {
var settings = options || {};
currentSettings = settings;
var cmd = 'omxplayer';
var respawn = null;
var args = [];
if (settings.audioOutput && settings.audioOutput !== 'default') {
args.push('-o');
args.push(settings.audioOutput);
}
if (settings.loop === true) {
if (nativeLoop) {
args.push('--loop');
}
}
if (typeof videos === 'string') {
videos = [videos];
}
var realfiles = resolveFilePaths(videos);
if (nativeLoop) {
/* all files to omxplayer parameters */
args.push.apply(args, realfiles);
} else {
/*
* only first file to omxplayer parameter. following files are handled by
* loopHelper
*/
args.push(realfiles[0]);
}
if (!nativeLoop && ((realfiles.length > 1) || (settings.loop))) {
/* no native loop support, enable helper */
loopHelper = LoopHelper(realfiles, settings.loop);
respawn = function () {
if (!loopHelper) {
/* respawn ignored, stop requested */
that.emit('stop');
return;
}
/* change file */
var nextFile = loopHelper.getNext();
if (!nextFile) {
/* respawn ignored, loop ended */
loopHelper = null;
that.emit('stop');
} else {
/* respawn */
args[args.length - 1] = nextFile;
omxProcess = spawn(cmd, args, {
stdio: ['pipe', null, null]
});
omxProcess.once('exit', respawn);
/* check if omxplayer hangs when video should be finished */
startWatchdog(nextFile, omxProcess.pid);
}
};
omxProcess = spawn(cmd, args, {
stdio: ['pipe', null, null]
});
omxProcess.once('exit', respawn);
} else {
/* native loop support enabled or not requested */
omxProcess = spawn(cmd, args, {
stdio: ['pipe', null, null]
});
omxProcess.once('exit', function (code, signal) {
omxProcess = null;
that.emit('stop');
});
}
that.emit('load', videos, options);
};
var play = function (videos, options) {
if (omxProcess) {
if (!paused) {
return false;
}
sendAction('pause');
paused = false;
that.emit('play');
return true;
}
if (!videos) {
throw new TypeError("No files specified");
}
if (typeof videos !== 'string' && !util.isArray(videos)) {
throw new TypeError("Incorrect value for videos: " + videos);
}
open(videos, options);
that.emit('play');
return true;
};
var pause = function () {
if (paused) {
return false;
}
sendAction('pause');
paused = true;
that.emit('pause');
return true;
};
var volup = function () {
sendAction('volup');
that.emit('volup');
return true;
};
var voldown = function () {
sendAction('voldown');
that.emit('voldown');
return true;
};
var handleQuitTimeout = function (oldOmxProcess, timeout) {
var timeoutHandle = setTimeout(function () {
console.log('omxplayer still running. kill forced');
oldOmxProcess.kill('SIGTERM');
}, timeout);
oldOmxProcess.once('exit', function () {
clearTimeout(timeoutHandle);
});
};
var stop = function () {
if (!omxProcess) {
/* ignore, no omxProcess to stop */
return false;
}
loopHelper = null;
sendAction('quit');
handleQuitTimeout(omxProcess, 250);
omxProcess = null;
return true;
};
var isPlaying = function () {
return omxProcess && !paused;
};
var isLoaded = function () {
return omxProcess;
};
var getStatus = function () {
if (isLoaded()) {
return {
videos: currentVideos,
settings: currentSettings,
playing: isPlaying(),
loaded: true
};
}
return {
loaded: false
};
};
var setVideoDir = function (dir) {
videoDir = dir;
};
var setVideoSuffix = function (suffix) {
videoSuffix = suffix;
};
var enableNativeLoop = function () {
nativeLoop = true;
return that;
};
setInterval(onlyOneProcess, 5000);
that.play = play;
that.pause = pause;
that.stop = stop;
that.volup = volup;
that.voldown = voldown;
that.isPlaying = isPlaying;
that.isLoaded = isLoaded;
that.getStatus = getStatus;
that.setVideoDir = setVideoDir;
that.setVideoSuffix = setVideoSuffix;
that.enableNativeLoop = enableNativeLoop;
return that;
};
module.exports = omxdirector();