diff --git a/lib/camera.js b/lib/camera.js index 0ef48e9..51a7c77 100644 --- a/lib/camera.js +++ b/lib/camera.js @@ -26,7 +26,6 @@ const videoUrl = port => { return `http://${ip.address()}:${port}/?action=stream`; }; - try { cp.spawnSync('kill -9 $(pgrep "mjpg_streamer")'); } catch (error) {} @@ -52,6 +51,7 @@ class Camera extends Stream { stream: null, }; + /* istanbul ignore else */ if (typeof options === 'undefined') { options = {}; } @@ -91,6 +91,7 @@ class Camera extends Stream { } // No string url provided + /* istanbul ignore else */ if (!url) { if (options.url && isValidRemoteStream(options.url)) { url = options.url; @@ -123,6 +124,7 @@ class Camera extends Stream { ]); }, stop() { + /* istanbul ignore else */ if (state.process) { state.process.kill('SIGTERM'); state.process = null; @@ -133,6 +135,11 @@ class Camera extends Stream { priv.set(this, state); Object.defineProperties(this, { + dimensions: { + get() { + return dimensions; + } + }, frame: { get() { return state.frame; @@ -209,6 +216,7 @@ class Camera extends Stream { // } write(chunk) { + /* istanbul ignore else */ if (chunk) { this.emit('data', chunk); this.emit('frame', chunk); diff --git a/lib/install.js b/lib/install.js index 1caae68..4b47b42 100644 --- a/lib/install.js +++ b/lib/install.js @@ -1,3 +1,4 @@ +'use strict'; /* All of the operations in this module are synchronous because without them, the environment cannot support @@ -19,14 +20,14 @@ | 24s !!!!!!!!!!!! | cp.spawnSync('opkg', ['install', binary]) | */ -var cp = require('child_process'); +const cp = require('child_process'); // TODO: this can only work if the Tessel is connected to the network... -// var os = require('os'); -// var wlan0 = os.os.networkInterfaces().wlan0[0]; +// const os = require('os'); +// const wlan0 = os.os.networkInterfaces().wlan0[0]; -var profile = '/etc/profile'; -var installed = { +const profile = '/etc/profile'; +const installed = { aplay: true, arecord: true, espeak: false, @@ -37,7 +38,7 @@ var installed = { }; module.exports = function(binary) { - var env = `AV_INSTALLED_${binary.toUpperCase()}`; + const env = `AV_INSTALLED_${binary.toUpperCase()}`; if (global.IS_TEST_ENV || process.arch !== 'mipsel') { return true; @@ -53,8 +54,8 @@ module.exports = function(binary) { return true; } - var which = cp.spawnSync('which', [binary]); - var install; + const which = cp.spawnSync('which', [binary]); + let install; // According to `which`, this binary does not exist if (which.status === 1) { diff --git a/lib/microphone.js b/lib/microphone.js index 5c17bc3..e56aa56 100644 --- a/lib/microphone.js +++ b/lib/microphone.js @@ -64,6 +64,7 @@ class Microphone extends Emitter { args = options; } else { // Don't need to check for null, since those are handled above + /* istanbul ignore else */ if (typeof options === 'object') { // mic.listen({ // ... @@ -73,6 +74,7 @@ class Microphone extends Emitter { const value = options[key]; let option = ''; + /* istanbul ignore else */ if (key.length === 1) { // mic.listen({ // c: 1, @@ -100,6 +102,7 @@ class Microphone extends Emitter { args = defaults.slice(); } + /* istanbul ignore else */ if (state.arecord === null) { state.isListening = true; state.currentTime = 0; @@ -115,7 +118,7 @@ class Microphone extends Emitter { }, 100); // Apply some reasonable defaults... - Object.keys(arecord.options).forEach(key => { + arecord.keys.forEach(key => { if (!args.includes(key)) { args.push(key, arecord.options[key]); } @@ -123,6 +126,7 @@ class Microphone extends Emitter { state.arecord = cp.spawn('arecord', args); + /* istanbul ignore if */ if (this.debug) { state.arecord.stderr.on('data', data => { const lines = data.toString().split('\n').filter(Boolean).map(line => line.trim()); diff --git a/lib/player.js b/lib/player.js index 967ac81..a9d35bd 100644 --- a/lib/player.js +++ b/lib/player.js @@ -8,6 +8,11 @@ const path = require('path'); // Program Specific const priv = new WeakMap(); +try { + cp.spawnSync('kill -9 $(pgrep "madplay")'); +} catch (error) {} + + function toSeconds(time) { if (typeof time === 'number') { return time; @@ -32,7 +37,7 @@ class Player extends Emitter { constructor(filename) { super(); - if (filename && !filename.endsWith('.mp3')) { + if (typeof filename === 'string' && !filename.endsWith('.mp3')) { filename = filename || ''; throw new Error( `av.Player can only playback mp3. @@ -97,6 +102,7 @@ class Player extends Emitter { time = file; } + /* istanbul ignore else */ if (typeof time !== 'undefined') { if (!/^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/.test(time)) { throw new Error( @@ -172,6 +178,7 @@ class Player extends Emitter { time = state.pauseTime; } + /* istanbul ignore else */ if (state.process === null) { if (time !== null) { @@ -206,6 +213,7 @@ class Player extends Emitter { state.process.on('exit', (code, signal) => { if (code !== null && signal === null) { + /* istanbul ignore else */ if (state.interval) { clearInterval(state.interval); } @@ -229,9 +237,11 @@ class Player extends Emitter { return this; } state.isPlaying = false; + /* istanbul ignore else */ if (state.interval) { clearInterval(state.interval); } + /* istanbul ignore else */ if (state.process) { state.process.kill('SIGTERM'); state.process = null; @@ -246,10 +256,12 @@ class Player extends Emitter { return this; } state.isPlaying = false; + /* istanbul ignore else */ if (state.interval) { clearInterval(state.interval); } state.pauseTime = state.currentTime; + /* istanbul ignore else */ if (state.process) { state.process.kill('SIGTERM'); state.process = null; diff --git a/test/.jshintrc b/test/.jshintrc index 347b887..706e293 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -14,7 +14,7 @@ "node": true, "strict": false, "esnext": true, - "unused": true, + "unused": false, "globals": { "exports": true, "Promise": true, diff --git a/test/unit/camera.js b/test/unit/camera.js index 1717608..22228be 100644 --- a/test/unit/camera.js +++ b/test/unit/camera.js @@ -6,7 +6,7 @@ exports['av.Camera'] = { setUp(done) { this.sandbox = sinon.sandbox.create(); this.emitter = new Emitter(); - this.spawn = this.sandbox.stub(cp, 'spawn', () => { + this.spawn = this.sandbox.stub(cp, 'spawn').callsFake(() => { this.emitter = new Emitter(); this.emitter.kill = this.sandbox.stub(); this.emitter.stderr = new Emitter(); @@ -14,6 +14,7 @@ exports['av.Camera'] = { return this.emitter; }); + this.wmSet = this.sandbox.spy(WeakMap.prototype, 'set'); this.write = this.sandbox.stub(Writable.prototype, 'write'); done(); @@ -42,11 +43,120 @@ exports['av.Camera'] = { test.done(); }, + optionsAreString(test) { + test.expect(1); + + const url = 'http://127.0.0.1:3000/?action=stream'; + const cam = new av.Camera(url); + + test.equal(cam.url, url); + test.done(); + }, + + optionsHaveDimensionsString(test) { + test.expect(2); + + const options = { + dimensions: '320x240', + }; + const cam = new av.Camera(options); + test.equal(cam.dimensions, options.dimensions); + test.equal(this.wmSet.lastCall.args[1].mjpg.dimensions, options.dimensions); + test.done(); + }, + + optionsHaveWidthHeight(test) { + test.expect(2); + + const options = { + width: 320, + height: 240, + }; + const cam = new av.Camera(options); + + test.equal(cam.dimensions, '320x240'); + test.equal(this.wmSet.lastCall.args[1].mjpg.dimensions, '320x240'); + test.done(); + }, + + optionsHaveFPS(test) { + test.expect(1); + + const options = { + fps: 1000, + }; + const cam = new av.Camera(options); + + test.equal(this.wmSet.lastCall.args[1].mjpg.fps, options.fps); + test.done(); + }, + + optionsHaveQuality(test) { + test.expect(1); + + const options = { + quality: 100, + }; + const cam = new av.Camera(options); + + test.equal(this.wmSet.lastCall.args[1].mjpg.quality, options.quality); + test.done(); + }, + + optionsHavePort(test) { + test.expect(1); + + const options = { + port: 1337, + }; + const cam = new av.Camera(options); + + test.equal(this.wmSet.lastCall.args[1].mjpg.port, options.port); + test.done(); + }, + + optionsHaveDevice(test) { + test.expect(1); + + const options = { + device: '/dev/video1', + }; + const cam = new av.Camera(options); + + test.equal(this.wmSet.lastCall.args[1].mjpg.device, options.device); + test.done(); + }, + + optionsHaveTimeout(test) { + test.expect(1); + + const options = { + timeout: 1, + }; + const cam = new av.Camera(options); + + test.equal(this.wmSet.lastCall.args[1].remote.timeout, options.timeout); + test.done(); + }, + + optionsHaveUrl(test) { + test.expect(1); + + const url = 'http://127.0.0.1:3000/?action=stream'; + const options = { + url, + }; + const cam = new av.Camera(options); + + test.equal(this.wmSet.lastCall.args[1].remote.url, options.url); + test.done(); + }, + captureReadable(test) { test.expect(2); - var cam = new av.Camera(); - var capture = cam.capture(); + const cam = new av.Camera(); + const capture = cam.capture(); test.equal(capture instanceof CaptureStream, true); test.equal(capture instanceof Readable, true); @@ -57,9 +167,9 @@ exports['av.Camera'] = { captureToPipe(test) { test.expect(5); - var buffer = new Buffer([0]); - var cam = new av.Camera(); - var writable = new Writable(); + const buffer = new Buffer([0]); + const cam = new av.Camera(); + const writable = new Writable(); writable.on('pipe', () => { test.ok(true); @@ -85,9 +195,9 @@ exports['av.Camera'] = { captureMultiple(test) { test.expect(8); - var buffer = new Buffer([0]); - var cam = new av.Camera(); - var writable = new Writable(); + const buffer = new Buffer([0]); + const cam = new av.Camera(); + const writable = new Writable(); this.sandbox.spy(cam, 'capture'); this.sandbox.spy(cam, 'stream'); @@ -116,7 +226,7 @@ exports['av.Camera'] = { spawned(test) { test.expect(3); - var cam = new av.Camera(); + const cam = new av.Camera(); cam.capture(); @@ -135,7 +245,7 @@ exports['av.Camera'] = { stream(test) { test.expect(1); - var cam = new av.Camera(); + const cam = new av.Camera(); cam.on('data', () => { test.ok(true); diff --git a/test/unit/microphone.js b/test/unit/microphone.js index d84c937..59f72bd 100644 --- a/test/unit/microphone.js +++ b/test/unit/microphone.js @@ -4,7 +4,7 @@ exports['av.Microphone'] = { setUp(done) { this.sandbox = sinon.sandbox.create(); this.emitter = new Emitter(); - this.spawn = this.sandbox.stub(cp, 'spawn', () => { + this.spawn = this.sandbox.stub(cp, 'spawn').callsFake(() => { this.emitter = new Emitter(); this.emitter.kill = this.sandbox.stub(); this.emitter.stderr = new Emitter(); @@ -34,6 +34,31 @@ exports['av.Microphone'] = { test.done(); }, + listening(test) { + test.expect(1); + + const buffer = new Buffer([0]); + const mic = new av.Microphone(); + + const listen = mic.listen(); + + test.equal(mic.isListening, true); + test.done(); + }, + + listenReturnsActiveCaptureStream(test) { + test.expect(2); + + const buffer = new Buffer([0]); + const mic = new av.Microphone(); + + const listen = mic.listen(); + + test.equal(mic.isListening, true); + test.equal(mic.listen(), listen); + test.done(); + }, + listenToEmitter(test) { test.expect(5); @@ -104,4 +129,36 @@ exports['av.Microphone'] = { test.done(); }, + listenOptions(test) { + test.expect(3); + + const mic = new av.Microphone(); + + mic.listen({ + '-c': 1, + '-f': 'cd', + }); + + test.equal(this.spawn.callCount, 1); + test.equal(this.spawn.lastCall.args[0], 'arecord'); + test.deepEqual(this.spawn.lastCall.args[1], ['-c', 1, '-f', 'cd']); + test.done(); + }, + + listenOptionsOneCharacter(test) { + test.expect(3); + + const mic = new av.Microphone(); + + mic.listen({ + c: 1, + f: 'cd', + }); + + test.equal(this.spawn.callCount, 1); + test.equal(this.spawn.lastCall.args[0], 'arecord'); + test.deepEqual(this.spawn.lastCall.args[1], ['-c', 1, '-f', 'cd']); + test.done(); + }, + }; diff --git a/test/unit/player.js b/test/unit/player.js index b290c2f..151f43b 100644 --- a/test/unit/player.js +++ b/test/unit/player.js @@ -8,8 +8,8 @@ exports['av.Player'] = { this.emitter = new Emitter(); this.emitter.kill = this.sandbox.stub(); this.emitter.stderr = new Emitter(); - this.spawn = this.sandbox.stub(cp, 'spawn', () => this.emitter); - this.execSync = this.sandbox.stub(cp, 'execSync', () => new Buffer(aplayListDevices)); + this.spawn = this.sandbox.stub(cp, 'spawn').callsFake(() => this.emitter); + this.execSync = this.sandbox.stub(cp, 'execSync').callsFake(() => new Buffer(aplayListDevices)); done(); }, @@ -35,7 +35,7 @@ exports['av.Player'] = { this.wmSet = this.sandbox.spy(WeakMap.prototype, 'set'); this.execSync.restore(); - this.execSync = this.sandbox.stub(cp, 'execSync', () => new Buffer(aplayListDevices.replace('card 1:', 'card 0:'))); + this.execSync = this.sandbox.stub(cp, 'execSync').callsFake(() => new Buffer(aplayListDevices.replace('card 1:', 'card 0:'))); new av.Player('foo.mp3'); @@ -56,12 +56,14 @@ exports['av.Player'] = { }, unsupportedExtention(test) { - test.expect(5); + test.expect(7); test.throws(() => new av.Player('foo.wav')); test.throws(() => new av.Player('foo.m4a')); test.throws(() => new av.Player('foo.mov')); test.throws(() => new av.Player('foo.ogg')); test.throws(() => new av.Player('foo.opus')); + test.throws(() => new av.Player('foo')); + test.throws(() => new av.Player('')); test.done(); }, @@ -84,6 +86,15 @@ exports['av.Player'] = { test.done(); }, + playAlreadyActive(test) { + test.expect(1); + const player = new av.Player('foo.mp3'); + player.play(); + + test.equal(player.play(), player); + test.done(); + }, + playError(test) { test.expect(2); @@ -168,16 +179,12 @@ exports['av.Player'] = { }, stopTwice(test) { - test.expect(2); - this.clock = this.sandbox.useFakeTimers(); + test.expect(1); const player = new av.Player('foo.mp3'); player.play(); - player.on('stop', () => { - test.equal(this.emitter.kill.callCount, 1); - test.equal(this.emitter.kill.lastCall.args[0], 'SIGTERM'); - test.done(); - }); player.stop(); + test.equal(player.stop(), player); + test.done(); }, pause(test) { @@ -446,13 +453,13 @@ exports['av.Player'] = { test.done(); }, - playFromObject(test) { + playOptions(test) { test.expect(4); const player = new av.Player(); const args = { file: 'foo.mp3', '-a': 10, - '-r': 2 + '-r': 2, }; player.play(args); @@ -462,5 +469,56 @@ exports['av.Player'] = { test.deepEqual(this.spawn.lastCall.args[1], ['foo.mp3', '-a', 10, '-r', 2, '-o', '/dev/dsp1']); test.done(); }, + + playOptionsOneCharacter(test) { + test.expect(4); + const player = new av.Player(); + const args = { + file: 'foo.mp3', + a: 10, + r: 2, + }; + player.play(args); + + test.equal(player.isPlaying, true); + test.equal(this.spawn.callCount, 1); + test.equal(this.spawn.lastCall.args[0], 'madplay'); + test.deepEqual(this.spawn.lastCall.args[1], ['foo.mp3', '-a', 10, '-r', 2, '-o', '/dev/dsp1']); + test.done(); + }, + + playOptionsNoFile(test) { + test.expect(2); + const player = new av.Player(); + const args = { + a: 10, + r: 2, + }; + player.play(args); + + test.equal(player.isPlaying, false); + test.equal(this.spawn.callCount, 0); + test.done(); + }, + + playFileAtTime(test) { + test.expect(4); + const player = new av.Player(); + player.play('foo.mp3', '00:00:10'); + + test.equal(player.isPlaying, true); + test.equal(this.spawn.callCount, 1); + test.equal(this.spawn.lastCall.args[0], 'madplay'); + test.deepEqual(this.spawn.lastCall.args[1], ['foo.mp3', '-s', 10, '-o', '/dev/dsp1']); + test.done(); + }, + + playFileAtBogusTime(test) { + test.expect(1); + const player = new av.Player(); + + test.throws(() => player.play('foo.mp3', 'bogus')); + test.done(); + }, // To Do: more detailed tests for pause time updates }; diff --git a/test/unit/speaker.js b/test/unit/speaker.js index 51fc9de..0472036 100644 --- a/test/unit/speaker.js +++ b/test/unit/speaker.js @@ -4,13 +4,13 @@ exports['av.Speaker'] = { setUp(done) { this.sandbox = sinon.sandbox.create(); this.emitter = new Emitter(); - this.spawn = this.sandbox.stub(cp, 'spawn', () => { + this.spawn = this.sandbox.stub(cp, 'spawn').callsFake(() => { this.emitter = new Emitter(); this.emitter.kill = this.sandbox.stub(); this.emitter.stderr = new Emitter(); return this.emitter; }); - this.execSync = this.sandbox.stub(cp, 'execSync', () => new Buffer(aplayListDevices)); + this.execSync = this.sandbox.stub(cp, 'execSync').callsFake(() => new Buffer(aplayListDevices)); done(); },