Create PlayLog, move tag identification into MediaLibrary

This commit is contained in:
Annika Backstrom 2017-04-09 22:05:52 -04:00
parent 78f2802dea
commit 43ad5281e3
7 changed files with 150 additions and 73 deletions

View File

@ -1,6 +1,7 @@
"use strict"; "use strict";
const glob = require('glob'); const glob = require('glob');
const basename = require('path').basename;
class FileBackend { class FileBackend {
constructor(config) { constructor(config) {
@ -10,8 +11,9 @@ class FileBackend {
find(tag, callback) { find(tag, callback) {
glob('media/' + tag + ' - *.mp3', (err, files) => { glob('media/' + tag + ' - *.mp3', (err, files) => {
if (files.length > 0) { if (files.length > 0) {
callback(files[0]); callback(basename(files[0]));
} }
callback();
}); });
} }
} }

View File

@ -1,70 +1,34 @@
"use strict"; "use strict";
const EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
const tags = require('./tags');
class Library extends EventEmitter { class Library extends EventEmitter {
constructor(config, backend) { constructor(config, backend) {
super(); super();
this.log_size = 10;
this.config = config; this.config = config;
this.backend = backend; this.backend = backend;
this.log = [];
} }
find(tag) { find(tag) {
if (tag === this.config.stop_id) {
this.emit('action', new tags.StopCommand());
}
if (tag === this.config.pause_id) {
this.emit('action', new tags.PauseCommand());
}
this.backend.find(tag, path => { this.backend.find(tag, path => {
if (!path) { if (!path) {
this.updateLog(new NotFoundLogEntry(tag)); this.emit('action', new tags.NotFoundTag(tag));
return; return;
} }
this.updateLog(new FileLogEntry(tag, path)); this.emit('action', new tags.FileTag(tag, path));
}); });
} }
updateLog(entry) {
this.log.unshift(entry);
if (this.log.length > this.log_size) {
this.log.pop();
}
this.emit('play', entry);
}
getLastTag() {
if (this.log.length == 0) {
return;
}
return this.log[0].tag;
}
getLog() {
return this.log;
}
}
class LogEntry {
constructor(tag) {
this.tag = tag;
}
toString() {
return this.tag;
}
}
class NotFoundLogEntry extends LogEntry {
toString() {
return `Unknown media: ${this.tag}`
}
}
class FileLogEntry extends LogEntry {
constructor(tag, path) {
super(tag);
this.path = path;
}
} }
module.exports.Library = Library; module.exports.Library = Library;

View File

@ -0,0 +1,44 @@
"use strict";
const EventEmitter = require('events').EventEmitter;
const tags = require('./tags');
module.exports.PlayLog = class PlayLog extends EventEmitter {
constructor() {
super();
this.last_tag = undefined;
this.log = [];
this.log_size = 10;
}
updateLog(tag) {
this.last_tag = tag;
if (tag instanceof tags.NotFoundTag) {
this.emit('update', tag);
return;
}
this.log.unshift(tag);
if (this.log.length > this.log_size) {
this.log.pop();
}
this.emit('update', tag);
}
getLastTag() {
if (this.log.length == 0) {
return;
}
return this.log[0].tag;
}
getLog() {
return this.log;
}
}
// vim:ts=2 sw=2 et:

45
jukebox/library/tags.js Normal file
View File

@ -0,0 +1,45 @@
class Tag {
constructor(tag) {
this.tag = tag;
}
toString() {
return this.tag;
}
}
class StopCommand extends Tag {
toString() {
return "Command: STOP";
}
}
class PauseCommand extends Tag {
toString() {
return "Command: PAUSE";
}
}
class NotFoundTag extends Tag {
toString() {
return `Unknown tag: ${this.tag}`
}
}
class FileTag extends Tag {
constructor(tag, path) {
super(tag);
this.path = path;
}
toString() {
return this.path;
}
}
module.exports = {
StopCommand: StopCommand
, PauseCommand: PauseCommand
, NotFoundTag: NotFoundTag
, FileTag: FileTag
};

View File

@ -1,11 +1,14 @@
"use strict"; "use strict";
const tags = require('./library/tags');
const throttle = require('throttle-debounce/throttle'); const throttle = require('throttle-debounce/throttle');
const ChildProcessEmitter = require('./child-process').ChildProcessEmitter; const ChildProcessEmitter = require('./child-process').ChildProcessEmitter;
const DEFAULT_PAUSE_THROTTLE = 2000; const DEFAULT_PAUSE_THROTTLE = 2000;
const DEFAULT_PLAY_THROTTLE = 5000; const DEFAULT_PLAY_THROTTLE = 5000;
const DEFAULT_UNKNOWN_THROTTLE = 2000;
class MediaPlayer extends ChildProcessEmitter { class MediaPlayer extends ChildProcessEmitter {
constructor(config, logger) { constructor(config, logger) {
@ -23,6 +26,8 @@ class MediaPlayer extends ChildProcessEmitter {
this._playFile(path); this._playFile(path);
}); });
this.unknown = throttle(config.unknown_throttle || DEFAULT_UNKNOWN_THROTTLE, this._unknown);
// default to unthrottled // default to unthrottled
this.pause = this._pauseFile; this.pause = this._pauseFile;
this.playFile = this._playFile; this.playFile = this._playFile;
@ -33,16 +38,35 @@ class MediaPlayer extends ChildProcessEmitter {
this.playFile = this.playFileThrottled; this.playFile = this.playFileThrottled;
} }
stop() { stop(tag) {
this.emit('command', tag);
this.send('STOP'); this.send('STOP');
} }
_pause() { _pause(tag) {
this.emit('command', tag);
this.send('PAUSE'); this.send('PAUSE');
} }
_playFile(path) { _playFile(tag) {
this.send("LOAD " + path); this.emit('command', tag);
this.send("LOAD media/" + tag.path);
}
_unknown(tag) {
this.emit('command', tag);
}
handleTag(tag) {
if (tag instanceof tags.StopCommand) {
this.stop(tag);
} else if (tag instanceof tags.PauseCommand) {
this.pause(tag);
} else if (tag instanceof tags.FileTag) {
this.playFile(tag);
} else {
this.unknown(tag);
}
} }
} }

View File

@ -3,9 +3,9 @@
const ChildProcessEmitter = require('./child-process').ChildProcessEmitter; const ChildProcessEmitter = require('./child-process').ChildProcessEmitter;
class TagReader extends ChildProcessEmitter { class TagReader extends ChildProcessEmitter {
constructor(config, logger) { constructor(config, logger) {
super(config.tag_reader, logger); super(config.tag_reader, logger);
} }
} }
module.exports = function(config, logger) { module.exports = function(config, logger) {

View File

@ -22,6 +22,9 @@ logger.level = config.debug ? 'debug' : 'info';
const MediaPlayer = require('./jukebox/media-player')(config, logger); const MediaPlayer = require('./jukebox/media-player')(config, logger);
const TagReader = require('./jukebox/tag-reader')(config, logger); const TagReader = require('./jukebox/tag-reader')(config, logger);
const play_log = require('./jukebox/library/play-log');
const PlayLog = new play_log.PlayLog();
MediaPlayer.throttle(); MediaPlayer.throttle();
@ -36,6 +39,9 @@ function exitHandler(options, err) {
logger.debug('closing child tasks'); logger.debug('closing child tasks');
if (err) logger.debug(err.stack); if (err) logger.debug(err.stack);
// FIXME When do we actually need to call this? Adding these seems
// to have fixed a problem with orphaned children, but not we get
// two sets of "closing child tasks" messages when we shut down.
TagReader.kill(); TagReader.kill();
MediaPlayer.kill(); MediaPlayer.kill();
@ -57,28 +63,20 @@ if (config.debug) {
}); });
} }
MediaLibrary.on('play', event => { MediaPlayer.on('command', tag => {
PlayLog.updateLog(tag);
});
PlayLog.on('update', tag => {
var data = { var data = {
html: views.log.render({ play_log: MediaLibrary.getLog() }), html: views.log.render({ play_log: PlayLog.getLog() }),
tag: MediaLibrary.getLastTag() tag: tag.tag
}; };
wss.broadcast(JSON.stringify(data)); wss.broadcast(JSON.stringify(data));
}); });
MediaLibrary.on('play', event => { MediaLibrary.on('action', MediaPlayer.handleTag.bind(MediaPlayer));
if (event.tag === config.stop_id) {
MediaPlayer.stop();
}
if (event.tag === config.pause_id) {
MediaPlayer.pause();
}
if (event.path) {
MediaPlayer.playFile(event.path);
}
});
app.use(morgan('dev')) app.use(morgan('dev'))
app.use(express.static(__dirname + '/static')) app.use(express.static(__dirname + '/static'))
@ -86,9 +84,9 @@ app.use(express.static(__dirname + '/static'))
app.get('/', function (req, res, next) { app.get('/', function (req, res, next) {
try { try {
var index = views.index.render({ var index = views.index.render({
last_tag: MediaLibrary.getLastTag() last_tag: PlayLog.getLastTag()
, config: config , config: config
, play_log: MediaLibrary.getLog() , play_log: PlayLog.getLog()
}); });
var html = views.base.render({ var html = views.base.render({