diff --git a/README.md b/README.md index 313f4f04..ffd3c70a 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,8 @@ var command = ffmpeg('/path/to/file.avi', { option: "value", ... }); The following options are available: * `source`: input file name or readable stream (ignored if an input file is passed to the constructor) -* `timeout`: ffmpeg timeout in seconds (defaults to no timeout) +* `timeout`: ffmpeg process timeout in seconds (defaults to no timeout) +* `terminateTimeout`: ffmpeg terminate timeout in milliseconds (defaults to 20 milliseconds) * `preset` or `presets`: directory to load module presets from (defaults to the `lib/presets` directory in fluent-ffmpeg tree) * `niceness` or `priority`: ffmpeg niceness value, between -20 and 20; ignored on Windows platforms (defaults to 0) * `logger`: logger object with `debug()`, `info()`, `warn()` and `error()` methods (defaults to no logging) diff --git a/lib/processor.js b/lib/processor.js index 36d980ad..5511064c 100644 --- a/lib/processor.js +++ b/lib/processor.js @@ -482,13 +482,14 @@ module.exports = function(proto) { self.logger.debug('Output stream closed, scheduling kill for ffmpeg process'); // Don't kill process yet, to give a chance to ffmpeg to - // terminate successfully first This is necessary because + // terminate successfully first. This is necessary because // under load, the process 'exit' event sometimes happens // after the output stream 'close' event. + const terminateTimeout = (self.options && self.options.terminateTimeout > 0) ? self.options.terminateTimeout : 20 setTimeout(function() { - emitEnd(new Error('Output stream closed')); + emitEnd(new Error('Output stream closed, try increasing terminateTimeout option (' + terminateTimeout + ' ms)')); ffmpegProc.kill(); - }, 20); + }, terminateTimeout); }); outputStream.target.on('error', function(err) { diff --git a/test/processor.test.js b/test/processor.test.js index 6b0f7a3a..299d5389 100644 --- a/test/processor.test.js +++ b/test/processor.test.js @@ -232,7 +232,6 @@ describe('Processor', function() { .on('end', function() { console.log('end was called, expected a timeout'); assert.ok(false); - done(); }) .saveToFile(testFile); }); @@ -279,6 +278,51 @@ describe('Processor', function() { .saveToFile(testFile); }); + it('should kill the process on terminate timeout (terminateTimeout option)', function(done) { + this.timeout(60000); + + var testFile = path.join(__dirname, 'assets', 'testTerminateTimeout.avi'); + this.files.push(testFile); + + const terminateTimeout = 1000 // set terminateTimeout to 1000 ms + var command = this.getCommand({ + source: this.testfilebig, + logger: testhelper.logger, + terminateTimeout: terminateTimeout + }); + + var startCalled = false; + var outputStreamClosed = false; + + // Mock output stream + var outputStream = new stream.PassThrough(); + outputStream.close = function() { + outputStreamClosed = true; + this.emit('close'); + }; + + command + .usingPreset('divx') + .output(outputStream) + .on('start', function() { + startCalled = true; + setTimeout(function() { outputStream.close(); }, 500); // close the output stream after 500ms (emit 'close' event on output stream before 'exit' event, to simulate a under load system) + }) + .on('error', function(err) { + command.kill(); + assert.ok(startCalled); + assert.ok(outputStreamClosed); + assert.equal(err.message, 'Output stream closed, try increasing terminateTimeout option (' + terminateTimeout + ' ms)'); + // Wait for kill completation before to call done() + setTimeout(function() { done(); }, 500); + }) + .on('end', function() { + console.log('end was called, expected an error'); + assert.ok(false); + }) + .saveToFile(testFile); + }); + it('should not keep node process running on completion', function(done) { var script = ` var ffmpeg = require('.'); @@ -320,7 +364,6 @@ describe('Processor', function() { .on('end', function() { console.log('end was called, expected an error'); assert.ok(false); - done(); }) .saveToFile(testFile); }); @@ -357,7 +400,6 @@ describe('Processor', function() { .on('end', function() { console.log('end was called, expected a timeout'); assert.ok(false); - done(); }) .saveToFile(testFile);