mpd killing rampage
This commit is contained in:
parent
4226183402
commit
d32fff0a3f
14 changed files with 62 additions and 784 deletions
65
README.md
65
README.md
|
|
@ -5,12 +5,6 @@ No-nonsense music client and server for your home or office.
|
|||
Run it on a server connected to your main speakers. Guests can connect with
|
||||
their laptops, tablets, and phones, and play and share music.
|
||||
|
||||
Depends on [mpd](http://musicpd.org) version 0.17+ for the backend. Some might
|
||||
call this project an mpd client. (Note, version 0.17 is only available from
|
||||
source as of writing this; see below instructions regarding mpd installation.)
|
||||
|
||||
[Live demo](http://superjoe.zapto.org:16242/)
|
||||
|
||||
## Features
|
||||
|
||||
* Lightning-fast, responsive UI. You can hardly tell that the music server is
|
||||
|
|
@ -29,16 +23,14 @@ source as of writing this; see below instructions regarding mpd installation.)
|
|||
|
||||
## Get Started
|
||||
|
||||
Make sure you have [Node](http://nodejs.org) >=0.8.0 installed and
|
||||
[mpd](http://musicpd.org) version >=0.17.0 (see below) running, then:
|
||||
1. Make sure you have the latest stable [Node.js](http://nodejs.org) installed.
|
||||
2. Install [libgroove](https://github.com/superjoe30/libgroove).
|
||||
|
||||
```
|
||||
$ npm install --production groovebasin
|
||||
$ npm start groovebasin
|
||||
```
|
||||
|
||||
At this point, Groove Basin will issue warnings telling you what to do next.
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
|
@ -46,59 +38,13 @@ At this point, Groove Basin will issue warnings telling you what to do next.
|
|||

|
||||

|
||||
|
||||
## Mpd
|
||||
|
||||
Groove Basin depends on [mpd](http://musicpd.org) version 0.17+.
|
||||
|
||||
To compile from source, start here
|
||||
|
||||
```
|
||||
$ git clone git://git.musicpd.org/master/mpd.git
|
||||
```
|
||||
|
||||
and follow mpd's instructions from there.
|
||||
|
||||
### Configuration
|
||||
|
||||
* `default_permissions` - Recommended to remove `admin` so that anonymous
|
||||
users can't do nefarious things.
|
||||
|
||||
* `password` - Recommended to add a password for yourself to give yourself `admin` permissions.
|
||||
|
||||
* `read` - allows reading the library, current playlist, and playback status.
|
||||
|
||||
* `add` - allows adding songs, loading playlists, and uploading songs.
|
||||
|
||||
* `control` - allows controlling playback state and manipulating playlists.
|
||||
|
||||
* `admin` - allows updating the db, killing mpd, deleting songs from the
|
||||
library, and updating song tags.
|
||||
|
||||
* `audio_output` - Uncomment the "httpd" one and configure the port to enable
|
||||
streaming. Recommended "vorbis" encoder for better browser support.
|
||||
|
||||
* `sticker_file` - Groove Basin will not run without one set.
|
||||
|
||||
* `gapless_mp3_playback` - "yes" recommended. <3 gapless playback.
|
||||
|
||||
* `volume_normalization` - "yes" recommended. Replaygain scanners are not
|
||||
implemented for all the formats that can be played back. Volume normalization
|
||||
works on all formats.
|
||||
|
||||
* `max_command_list_size` - "16384" recommended. You do not want mpd crashing
|
||||
when you try to remove a ton of songs from the playlist at once.
|
||||
|
||||
* `auto_update` - "yes" recommended. Required for uploaded songs to show up
|
||||
in your library.
|
||||
|
||||
## Configuring Groove Basin
|
||||
## Configuration
|
||||
|
||||
Groove Basin is configured using environment variables. Available options
|
||||
and their defaults:
|
||||
|
||||
HOST="0.0.0.0"
|
||||
PORT="16242"
|
||||
MPD_CONF="/etc/mpd.conf"
|
||||
STATE_FILE=".state.json"
|
||||
NODE_ENV="dev"
|
||||
LASTFM_API_KEY=<not shown>
|
||||
|
|
@ -106,11 +52,6 @@ and their defaults:
|
|||
|
||||
## Developing
|
||||
|
||||
Install dependencies and run mpd as described in the Get Started section.
|
||||
|
||||
Clone the repository using `git clone --recursive` or if you have
|
||||
already cloned, do `git submodule update --init --recursive`.
|
||||
|
||||
```
|
||||
$ npm run dev
|
||||
```
|
||||
|
|
|
|||
1
TODO
1
TODO
|
|
@ -21,5 +21,4 @@ Eventually:
|
|||
* replace socket.io with https://github.com/einaros/ws
|
||||
* ditch qq fileuploader
|
||||
* extract the skewed random song selection into a separate, tested module
|
||||
* make dynamic mode work with version of mpd that is in ubuntu 0.16.5-1ubuntu4 (raspberry pi is 0.16.7-2)
|
||||
* ditch watch (prefer https://github.com/paulmillr/chokidar)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var mpd = require('mpd');
|
||||
var async = require('async');
|
||||
var http = require('http');
|
||||
var assert = require('assert');
|
||||
var socketio = require('socket.io');
|
||||
|
|
@ -8,15 +6,13 @@ var fs = require('fs');
|
|||
var util = require('util');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var which = require('which');
|
||||
var express = require('express');
|
||||
var osenv = require('osenv');
|
||||
var spawn = require('child_process').spawn;
|
||||
var requireIndex = require('requireindex');
|
||||
var plugins = requireIndex(path.join(__dirname, 'plugins'));
|
||||
var PlayerServer = require('./playerserver');
|
||||
var Killer = require('./killer');
|
||||
var Library = require('./library');
|
||||
var MpdConf = require('./mpdconf');
|
||||
|
||||
module.exports = GrooveBasin;
|
||||
|
||||
|
|
@ -32,13 +28,8 @@ function GrooveBasin() {
|
|||
EventEmitter.call(this);
|
||||
|
||||
this.runDir = "run";
|
||||
this.mpdSocketPath = path.join(this.runDir, "mpd.socket");
|
||||
this.stateFile = path.join(this.runDir, "state.json");
|
||||
this.mpdConfPath = path.join(this.runDir, "mpd.conf");
|
||||
this.mpdPidFile = path.join(this.runDir, "mpd.pid");
|
||||
|
||||
this.mpdConf = new MpdConf();
|
||||
this.mpdConf.setRunDir(this.runDir);
|
||||
mkdirp.sync(this.runDir);
|
||||
|
||||
this.app = express();
|
||||
this.app.disable('x-powered-by');
|
||||
|
|
@ -54,14 +45,6 @@ GrooveBasin.prototype.start = function(options) {
|
|||
start(this, options || {});
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.makeRunDir = function(cb) {
|
||||
mkdirp(this.runDir, cb);
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.initState = function(cb) {
|
||||
initState(this, cb);
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.restoreState = function(cb) {
|
||||
restoreState(this, cb);
|
||||
}
|
||||
|
|
@ -72,24 +55,10 @@ GrooveBasin.prototype.saveState = function(cb) {
|
|||
saveState(this, cb);
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.writeMpdConf = function(cb) {
|
||||
var mc = new MpdConf(this.state.mpd_conf);
|
||||
this.state.mpd_conf = mc.state;
|
||||
fs.writeFile(this.mpdConfPath, mc.toMpdConf(), cb);
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.restartMpd = function(cb) {
|
||||
restartMpd(this, cb);
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.startServer = function(cb) {
|
||||
startServer(this, cb);
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.connectToMpd = function() {
|
||||
connectToMpd(this);
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.saveAndSendStatus = function() {
|
||||
this.saveState();
|
||||
this.socketIo.sockets.emit('Status', JSON.stringify(this.state.status));
|
||||
|
|
@ -99,47 +68,7 @@ GrooveBasin.prototype.rescanLibrary = function() {
|
|||
console.error("TODO: rescanning library is not yet supported.");
|
||||
};
|
||||
|
||||
function connectToMpd(self) {
|
||||
var connectTimeout = null;
|
||||
var connectSuccess = true;
|
||||
|
||||
connect();
|
||||
|
||||
function tryReconnect() {
|
||||
if (connectTimeout != null) return;
|
||||
connectTimeout = setTimeout(function(){
|
||||
connectTimeout = null;
|
||||
connect();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function connect() {
|
||||
var mpdClient = mpd.connect({path: self.mpdSocketPath});
|
||||
mpdClient.on('end', function(){
|
||||
if (connectSuccess) console.warn("mpd connection closed");
|
||||
tryReconnect();
|
||||
});
|
||||
mpdClient.on('error', function(){
|
||||
if (connectSuccess) {
|
||||
connectSuccess = false;
|
||||
console.warn("mpd connection error...");
|
||||
}
|
||||
tryReconnect();
|
||||
});
|
||||
mpdClient.on('ready', function() {
|
||||
console.log((connectSuccess ? '' : '...') + "mpd connected");
|
||||
connectSuccess = true;
|
||||
self.playerServer = new PlayerServer(self.library, mpdClient, authenticate);
|
||||
self.emit('playerServerInit', self.playerServer);
|
||||
});
|
||||
}
|
||||
|
||||
function authenticate(pass) {
|
||||
return self.state.permissions[pass];
|
||||
}
|
||||
}
|
||||
|
||||
function startServer(self, cb) {
|
||||
function startServer(self) {
|
||||
assert.ok(self.httpServer == null);
|
||||
assert.ok(self.socketIo == null);
|
||||
|
||||
|
|
@ -164,42 +93,6 @@ function startServer(self, cb) {
|
|||
}
|
||||
}
|
||||
|
||||
function startMpd(self, cb){
|
||||
console.info("starting mpd", self.state.mpd_exe_path);
|
||||
var args = ['--no-daemon', self.mpdConfPath];
|
||||
var opts = {
|
||||
stdio: 'inherit',
|
||||
detached: true,
|
||||
};
|
||||
var child = spawn(self.state.mpd_exe_path, args, opts);
|
||||
cb();
|
||||
}
|
||||
|
||||
function restartMpd(self, cb) {
|
||||
mkdirp(self.mpdConf.playlistDirectory(), function(err) {
|
||||
if (err) return cb(err);
|
||||
fs.readFile(self.mpdPidFile, {encoding: 'utf8'}, function(err, pidStr) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
startMpd(self, cb);
|
||||
return;
|
||||
} else if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
var pid = parseInt(pidStr, 10);
|
||||
console.info("killing mpd", pid);
|
||||
var killer = new Killer(pid);
|
||||
killer.on('error', function(err) {
|
||||
cb(err);
|
||||
});
|
||||
killer.on('end', function() {
|
||||
startMpd(self, cb);
|
||||
});
|
||||
killer.kill();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function saveState(self, cb) {
|
||||
self.emit('aboutToSaveState', self.state);
|
||||
process.nextTick(function() {
|
||||
|
|
@ -238,52 +131,45 @@ function restoreState(self, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
function initState(self, cb) {
|
||||
which('mpd', function(err, mpdExe){
|
||||
if (err) {
|
||||
// it's okay. this was just a good default.
|
||||
console.warn("Unable to find mpd binary in path:", err.stack);
|
||||
}
|
||||
self.state = {
|
||||
state_version: STATE_VERSION,
|
||||
mpd_exe_path: mpdExe,
|
||||
status: {},
|
||||
mpd_conf: self.mpdConf.state,
|
||||
permissions: {},
|
||||
default_permissions: DEFAULT_PERMISSIONS
|
||||
};
|
||||
self.emit("stateInitialized");
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
function start(self, options) {
|
||||
self.httpHost = options.host || "0.0.0.0";
|
||||
self.httpPort = options.port || 16242;
|
||||
|
||||
self.on('stateRestored', function() {
|
||||
self.library = new Library(self.mpdConf.state.music_directory);
|
||||
self.once('stateRestored', function() {
|
||||
self.library = new Library(self.state.musicDirectory);
|
||||
self.library.startScan();
|
||||
self.library.on('library', function() {
|
||||
// TODO: this is weird. PlayerServer and Library should be the same object IMO
|
||||
self.playerServer = new PlayerServer(self.library, authenticate);
|
||||
startServer(self);
|
||||
});
|
||||
});
|
||||
|
||||
self.app.use(express.static(path.join(__dirname, '../public')));
|
||||
self.app.use(express.static(path.join(__dirname, '../src/public')));
|
||||
|
||||
self.state = {
|
||||
musicDirectory: path.join(osenv.home(), "music"),
|
||||
state_version: STATE_VERSION,
|
||||
status: {},
|
||||
permissions: {},
|
||||
default_permissions: DEFAULT_PERMISSIONS
|
||||
};
|
||||
|
||||
for (var pluginName in plugins) {
|
||||
plugins[pluginName](self, options);
|
||||
}
|
||||
|
||||
async.series([
|
||||
self.initState.bind(self),
|
||||
self.makeRunDir.bind(self),
|
||||
self.restoreState.bind(self),
|
||||
self.writeMpdConf.bind(self),
|
||||
self.restartMpd.bind(self),
|
||||
], function(err) {
|
||||
assert.ifError(err);
|
||||
self.connectToMpd();
|
||||
self.startServer();
|
||||
restoreState(self, function(err) {
|
||||
if (err) {
|
||||
console.error("unable to restore state:", err.stack);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
function authenticate(pass) {
|
||||
return self.state.permissions[pass];
|
||||
}
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
module.exports = Killer;
|
||||
|
||||
var POLL_INTERVAL = 100;
|
||||
var ESCALATE_TIMEOUT = 3000;
|
||||
var ERROR_TIMEOUT = 2000;
|
||||
|
||||
util.inherits(Killer, EventEmitter);
|
||||
function Killer(pid) {
|
||||
EventEmitter.call(this);
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
Killer.prototype.kill = function() {
|
||||
this.interval = setInterval(this.check.bind(this), POLL_INTERVAL);
|
||||
this.sig_kill_timeout = setTimeout(this.escalate.bind(this), ESCALATE_TIMEOUT);
|
||||
this.sig = "SIGTERM";
|
||||
};
|
||||
|
||||
Killer.prototype.check = function() {
|
||||
try {
|
||||
process.kill(this.pid, this.sig);
|
||||
} catch (err) {
|
||||
this.clean();
|
||||
if (err.code === 'ESRCH') {
|
||||
this.emit('end');
|
||||
} else {
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Killer.prototype.clean = function() {
|
||||
if (this.interval != null) clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
|
||||
if (this.sig_kill_timeout != null) clearTimeout(this.sig_kill_timeout);
|
||||
this.sig_kill_timeout = null;
|
||||
|
||||
if (this.error_timeout != null) clearTimeout(this.error_timeout);
|
||||
this.error_timeout = null;
|
||||
};
|
||||
|
||||
Killer.prototype.escalate = function() {
|
||||
this.sig = "SIGKILL";
|
||||
this.error_timeout = setTimeout(this.giveUp.bind(this), ERROR_TIMEOUT);
|
||||
};
|
||||
|
||||
Killer.prototype.giveUp = function() {
|
||||
this.clean();
|
||||
this.emit('error', new Error("Unable to kill " + this.pid + ": timeout"));
|
||||
};
|
||||
103
lib/mpdconf.js
103
lib/mpdconf.js
|
|
@ -1,103 +0,0 @@
|
|||
var path = require('path');
|
||||
var osenv = require('osenv');
|
||||
|
||||
module.exports = MpdConf;
|
||||
|
||||
function MpdConf(state) {
|
||||
this.state = state;
|
||||
if (this.state == null) this.setDefaultState();
|
||||
}
|
||||
|
||||
MpdConf.prototype.setDefaultState = function(){
|
||||
this.state = {};
|
||||
this.state.audio_httpd = {
|
||||
format: 'ogg',
|
||||
quality: 6,
|
||||
port: 16243
|
||||
};
|
||||
this.state.audio_pulse = null;
|
||||
this.state.audio_alsa = null;
|
||||
this.state.audio_oss = null;
|
||||
this.state.run_dir = null;
|
||||
this.state.music_directory = path.join(osenv.home(), 'music');
|
||||
};
|
||||
|
||||
MpdConf.prototype.playlistDirectory = function(){
|
||||
return path.join(this.state.run_dir, "playlists");
|
||||
};
|
||||
|
||||
MpdConf.prototype.setRunDir = function(it){
|
||||
this.state.run_dir = path.resolve(it);
|
||||
};
|
||||
|
||||
MpdConf.prototype.toMpdConf = function(){
|
||||
var quality, bitrate, encoder_value;
|
||||
var audio_outputs = [];
|
||||
if (this.state.audio_httpd != null) {
|
||||
if (this.state.audio_httpd.format === 'ogg') {
|
||||
quality = "quality \"" + this.state.audio_httpd.quality + "\"";
|
||||
bitrate = "";
|
||||
encoder_value = "vorbis";
|
||||
} else if (this.state.audio_httpd.format === 'mp3') {
|
||||
quality = "";
|
||||
bitrate = "bitrate \"" + this.state.audio_httpd.bitrate + "\"";
|
||||
encoder_value = "lame";
|
||||
}
|
||||
audio_outputs.push("audio_output {\n" +
|
||||
" type \"httpd\"\n" +
|
||||
" name \"Groove Basin (httpd)\"\n" +
|
||||
" encoder \"" + encoder_value + "\"\n" +
|
||||
" port \"" + this.state.audio_httpd.port + "\"\n" +
|
||||
" bind_to_address \"0.0.0.0\"\n" +
|
||||
" " + quality + "\n" +
|
||||
" " + bitrate + "\n" +
|
||||
" format \"44100:16:2\"\n" +
|
||||
" max_clients \"0\"\n" +
|
||||
"}");
|
||||
}
|
||||
if (this.state.audio_pulse != null) {
|
||||
audio_outputs.push("audio_output {\n" +
|
||||
" type \"pulse\"\n" +
|
||||
" name \"Groove Basin (pulse)\"\n" +
|
||||
"}");
|
||||
}
|
||||
if (this.state.audio_alsa != null) {
|
||||
audio_outputs.push("audio_output {\n" +
|
||||
" type \"alsa\"\n" +
|
||||
" name \"Groove Basin (alsa)\"\n" +
|
||||
"}");
|
||||
}
|
||||
if (this.state.audio_oss != null) {
|
||||
audio_outputs.push("audio_output {\n" +
|
||||
" type \"oss\"\n" +
|
||||
" name \"Groove Basin (oss)\"\n" +
|
||||
"}");
|
||||
}
|
||||
if (!audio_outputs.length) {
|
||||
audio_outputs.push("audio_output {\n" +
|
||||
" type \"null\"\n" +
|
||||
" name \"Groove Basin (null)\"\n" +
|
||||
"}");
|
||||
}
|
||||
return "music_directory \"" + this.state.music_directory + "\"\n" +
|
||||
"playlist_directory \"" + this.playlistDirectory() + "\"\n" +
|
||||
"db_file \"" + path.join(this.state.run_dir, "mpd.music.db") + "\"\n" +
|
||||
"log_file \"" + path.join(this.state.run_dir, "mpd.log") + "\"\n" +
|
||||
"pid_file \"" + path.join(this.state.run_dir, "mpd.pid") + "\"\n" +
|
||||
"state_file \"" + path.join(this.state.run_dir, "mpd.state") + "\"\n" +
|
||||
"sticker_file \"" + path.join(this.state.run_dir, "mpd.sticker.db") + "\"\n" +
|
||||
"bind_to_address \"" + path.join(this.state.run_dir, "mpd.socket") + "\"\n" +
|
||||
"gapless_mp3_playback \"yes\"\n" +
|
||||
"auto_update \"yes\"\n" +
|
||||
"default_permissions \"read,add,control,admin\"\n" +
|
||||
"replaygain \"album\"\n" +
|
||||
"volume_normalization \"yes\"\n" +
|
||||
"max_command_list_size \"16384\"\n" +
|
||||
"max_connections \"10\"\n" +
|
||||
"max_output_buffer_size \"16384\"\nid3v1_encoding \"UTF-8\"\n" +
|
||||
audio_outputs.join("\n") +
|
||||
"\n" +
|
||||
"# this socket is just for debugging with telnet\n" +
|
||||
"bind_to_address \"localhost\"\n" +
|
||||
"port \"16244\"\n";
|
||||
};
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
var util = require('util');
|
||||
var mpd = require('mpd');
|
||||
var assert = require('assert');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
|
|
@ -142,20 +141,10 @@ action('stop', {
|
|||
});
|
||||
|
||||
util.inherits(PlayerServer, EventEmitter);
|
||||
function PlayerServer(library, mpdClient, authenticate) {
|
||||
function PlayerServer(library, authenticate) {
|
||||
var self = this;
|
||||
self.library = library;
|
||||
self.mpdClient = mpdClient;
|
||||
self.authenticate = authenticate;
|
||||
self.mpdClient.on('system', function(system){
|
||||
console.log('changed system:', system);
|
||||
});
|
||||
self.mpdClient.on('system-stored_playlist', clientSystemChanged);
|
||||
self.mpdClient.on('system-sticker', clientSystemChanged);
|
||||
self.mpdClient.on('system-playlist', doRefreshMpdStatus);
|
||||
self.mpdClient.on('system-player', doRefreshMpdStatus);
|
||||
self.mpdClient.on('system-mixer', doRefreshMpdStatus);
|
||||
self.mpdClient.on('system-options', doRefreshMpdStatus);
|
||||
self.playlist = {};
|
||||
self.current_id = null;
|
||||
self.repeat = {
|
||||
|
|
@ -163,17 +152,11 @@ function PlayerServer(library, mpdClient, authenticate) {
|
|||
single: false
|
||||
};
|
||||
self.is_playing = false;
|
||||
self.mpd_is_playing = false;
|
||||
self.mpd_should_be_playing_id = null;
|
||||
self.mpdClient.sendCommand("clear");
|
||||
self.track_start_date = null;
|
||||
self.paused_time = 0;
|
||||
function clientSystemChanged(system) {
|
||||
self.emit('status', [system]);
|
||||
}
|
||||
function doRefreshMpdStatus(system) {
|
||||
refreshMpdStatus(self);
|
||||
}
|
||||
}
|
||||
|
||||
PlayerServer.prototype.createClient = function(socket, permissions) {
|
||||
|
|
@ -214,28 +197,16 @@ PlayerServer.prototype.authenticateWithPassword = function(client, password) {
|
|||
// items looks like [{file, sort_key}]
|
||||
PlayerServer.prototype.addItems = function(items, tagAsRandom) {
|
||||
tagAsRandom = !!tagAsRandom;
|
||||
var wantInfoTracksByFile = {};
|
||||
var commands = [];
|
||||
for (var id in items) {
|
||||
var item = items[id];
|
||||
var playlistItem = {
|
||||
file: item.file,
|
||||
sort_key: item.sort_key,
|
||||
is_random: tagAsRandom,
|
||||
time: library[id].time, // <--- TODO this is pseudocode
|
||||
};
|
||||
this.playlist[id] = playlistItem;
|
||||
wantInfoTracksByFile[item.file] = playlistItem;
|
||||
commands.push(mpd.cmd('listallinfo', [item.file]));
|
||||
}
|
||||
this.mpdClient.sendCommands(commands, function(err, msg) {
|
||||
if (err) console.error("Error getting time info for tracks:", err.stack);
|
||||
var objects = parseMpdObjects(msg);
|
||||
objects.forEach(function(o) {
|
||||
wantInfoTracksByFile[o.file].time = parseInt(o.Time, 10);
|
||||
this.emit('status', ['playlist', 'player']);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
playlistChanged(this);
|
||||
}
|
||||
|
||||
PlayerServer.prototype.clearPlaylist = function() {
|
||||
|
|
@ -381,46 +352,7 @@ function playlistChanged(self, o) {
|
|||
self.paused_time = 0;
|
||||
o.seekto = null;
|
||||
}
|
||||
var commands = [];
|
||||
if (self.is_playing) {
|
||||
if (self.current_id !== self.mpd_should_be_playing_id) {
|
||||
var file = self.playlist[self.current_id].file;
|
||||
commands.push("clear");
|
||||
commands.push(mpd.cmd("addid", [file]));
|
||||
commands.push("play");
|
||||
self.mpd_should_be_playing_id = self.current_id;
|
||||
if (o.seekto === 0) {
|
||||
o.seekto = null;
|
||||
} else if (self.paused_time) {
|
||||
o.seekto = self.paused_time;
|
||||
}
|
||||
self.track_start_date = new Date();
|
||||
}
|
||||
if (o.seekto != null) {
|
||||
var seek_command = mpd.cmd("seek", [0, Math.round(o.seekto)]);
|
||||
if (commands[commands.length - 1] === "play") {
|
||||
commands.splice(commands.length - 1, 0, seek_command);
|
||||
} else {
|
||||
commands.push(seek_command);
|
||||
}
|
||||
self.track_start_date = new Date() - o.seekto * 1000;
|
||||
}
|
||||
self.paused_time = null;
|
||||
} else {
|
||||
if (self.mpd_should_be_playing_id != null) {
|
||||
commands.push("clear");
|
||||
self.mpd_should_be_playing_id = null;
|
||||
}
|
||||
if (o.seekto != null) {
|
||||
self.paused_time = o.seekto;
|
||||
}
|
||||
self.track_start_date = null;
|
||||
if (self.paused_time == null) self.paused_time = 0;
|
||||
}
|
||||
|
||||
if (commands.length) self.mpdClient.sendCommands(commands)
|
||||
|
||||
self.emit('status', ['playlist', 'player']);
|
||||
// TODO something
|
||||
|
||||
disambiguateSortKeys(self);
|
||||
}
|
||||
|
|
@ -440,50 +372,7 @@ function findNext(object, from_id){
|
|||
return result;
|
||||
}
|
||||
|
||||
var refreshMpdCommands = ['currentsong', 'status'];
|
||||
function refreshMpdStatus(self) {
|
||||
self.mpdClient.sendCommands(refreshMpdCommands, function(err, msg) {
|
||||
if (err) {
|
||||
console.error("mpd status error:", err.stack);
|
||||
return;
|
||||
}
|
||||
var o = parseMpdObject(msg);
|
||||
var mpd_was_playing = self.mpd_is_playing;
|
||||
self.mpd_is_playing = o.state === 'play';
|
||||
if (self.mpd_should_be_playing_id != null && mpd_was_playing && !self.mpd_is_playing) {
|
||||
self.current_id = findNext(self.playlist, self.current_id);
|
||||
return playlistChanged(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function parseMpdObjects(msg) {
|
||||
var list = [];
|
||||
var o = null;
|
||||
msg.split("\n").forEach(function(line) {
|
||||
var index = line.indexOf(": ");
|
||||
var key = line.substr(0, index);
|
||||
var value = line.substr(index + 2);
|
||||
if (key === 'file') {
|
||||
if (o) list.push(o);
|
||||
o = {};
|
||||
}
|
||||
o[key] = value;
|
||||
});
|
||||
if (o) list.push(o);
|
||||
return list;
|
||||
}
|
||||
|
||||
function parseMpdObject(msg) {
|
||||
var o = {};
|
||||
msg.split("\n").forEach(function(line) {
|
||||
var index = line.indexOf(": ");
|
||||
var key = line.substr(0, index);
|
||||
var value = line.substr(index + 2);
|
||||
o[key] = value;
|
||||
});
|
||||
return o;
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ function setup(self) {
|
|||
});
|
||||
self.gb.on('socketConnect', onSocketConnection.bind(self));
|
||||
self.gb.on('stateRestored', function(state) {
|
||||
self.music_directory = state.mpd_conf.music_directory;
|
||||
// TODO set self.music_directory based on state
|
||||
//self.music_directory = ??
|
||||
if (self.music_directory == null) {
|
||||
self.is_enabled = false;
|
||||
console.warn("No music directory set. Delete disabled.");
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ function Download(gb) {
|
|||
});
|
||||
bus.on('restore_state', function(state){
|
||||
self.is_enabled = true;
|
||||
if ((self.music_directory = state.mpd_conf.music_directory) == null) {
|
||||
// TODO set music_directory based on config
|
||||
// self.music_directory = ??
|
||||
if (!self.music_directory) {
|
||||
self.is_enabled = false;
|
||||
console.warn("No music directory set. Download plugin disabled.");
|
||||
}
|
||||
|
|
@ -48,45 +50,14 @@ function Download(gb) {
|
|||
return self.sendZipOfFiles(zip_name, files, req, resp);
|
||||
});
|
||||
app.get('/download/album/:album', self.checkEnabledMiddleware, function(req, resp){
|
||||
var album, res$, i$, ref$, len$, track, files, zip_name;
|
||||
album = self.mpd.library.album_table[req.params.album];
|
||||
if (album == null) {
|
||||
resp.statusCode = 404;
|
||||
resp.end();
|
||||
return;
|
||||
}
|
||||
res$ = [];
|
||||
for (i$ = 0, len$ = (ref$ = album.tracks).length; i$ < len$; ++i$) {
|
||||
track = ref$[i$];
|
||||
res$.push(path.join(self.music_directory, track.file));
|
||||
}
|
||||
files = res$;
|
||||
zip_name = safePath(album.name) + ".zip";
|
||||
return self.sendZipOfFiles(zip_name, files, req, resp);
|
||||
// TODO implement
|
||||
});
|
||||
return app.get('/download/artist/:artist', self.checkEnabledMiddleware, function(req, resp){
|
||||
var artist, zip_name, files, i$, ref$, len$, album, j$, ref1$, len1$, track;
|
||||
artist = self.mpd.library.artist_table[req.params.artist];
|
||||
if (artist == null) {
|
||||
resp.statusCode = 404;
|
||||
resp.end();
|
||||
return;
|
||||
}
|
||||
zip_name = safePath(artist.name) + ".zip";
|
||||
files = [];
|
||||
for (i$ = 0, len$ = (ref$ = artist.albums).length; i$ < len$; ++i$) {
|
||||
album = ref$[i$];
|
||||
for (j$ = 0, len1$ = (ref1$ = album.tracks).length; j$ < len1$; ++j$) {
|
||||
track = ref1$[j$];
|
||||
files.push(path.join(self.music_directory, track.file));
|
||||
}
|
||||
}
|
||||
return self.sendZipOfFiles(zip_name, files, req, resp);
|
||||
return app.get('/download/artist/:artist', self.checkEnabledMiddleware,
|
||||
function(req, resp)
|
||||
{
|
||||
// TODO: implement
|
||||
});
|
||||
});
|
||||
bus.on('mpd', function(mpd){
|
||||
self.mpd = mpd;
|
||||
});
|
||||
}
|
||||
|
||||
Download.prototype.downloadPath = function(dl_path, zip_name, req, resp){
|
||||
|
|
|
|||
|
|
@ -66,125 +66,11 @@ DynamicMode.prototype.checkDynamicMode = function(){
|
|||
}
|
||||
};
|
||||
DynamicMode.prototype.checkDynamicModeOrWhyNot = function(){
|
||||
var item_list, ref$, current_id, current_index, all_ids, new_files, last_key, i, len$, item, now, i$, file, delete_count, add_count, self = this;
|
||||
if (!this.is_enabled) {
|
||||
return "disabled";
|
||||
}
|
||||
if (!Object.keys(this.mpd.library.track_table).length) {
|
||||
return "no tracks";
|
||||
}
|
||||
if (!this.got_stickers) {
|
||||
return "no stickers";
|
||||
}
|
||||
item_list = this.mpd.playlist.item_list;
|
||||
current_id = (ref$ = this.mpd.status.current_item) != null ? ref$.id : void 8;
|
||||
current_index = -1;
|
||||
all_ids = {};
|
||||
new_files = [];
|
||||
last_key = null;
|
||||
for (i = 0, len$ = item_list.length; i < len$; ++i) {
|
||||
item = item_list[i];
|
||||
if (!(item.track != null && item.id != null)) {
|
||||
return "item with no track";
|
||||
}
|
||||
if (item.id === current_id) {
|
||||
current_index = i;
|
||||
}
|
||||
if (last_key == null || last_key < item.sort_key) {
|
||||
last_key = item.sort_key;
|
||||
}
|
||||
all_ids[item.id] = true;
|
||||
if (this.previous_ids[item.id] == null) {
|
||||
new_files.push(item.track.file);
|
||||
}
|
||||
}
|
||||
now = new Date();
|
||||
this.mpd.setStickers(new_files, LAST_QUEUED_STICKER, JSON.stringify(now), function(err){
|
||||
if (err) {
|
||||
console.warn("dynamic mode set stickers error:", err);
|
||||
}
|
||||
});
|
||||
for (i$ = 0, len$ = new_files.length; i$ < len$; ++i$) {
|
||||
file = new_files[i$];
|
||||
this.last_queued[file] = now;
|
||||
}
|
||||
if (current_index === -1) {
|
||||
current_index = 0;
|
||||
}
|
||||
if (this.is_on) {
|
||||
delete_count = Math.max(current_index - history_size, 0);
|
||||
if (history_size < 0) {
|
||||
delete_count = 0;
|
||||
}
|
||||
this.mpd.removeIds((function(){
|
||||
var to$, results$ = [];
|
||||
for (i = 0, to$ = delete_count; i < to$; ++i) {
|
||||
results$.push(item_list[i].id);
|
||||
}
|
||||
return results$;
|
||||
}()));
|
||||
add_count = Math.max(future_size - (item_list.length - current_index), 0);
|
||||
this.mpd.queueFiles(this.getRandomSongFiles(add_count), last_key, null, true);
|
||||
}
|
||||
this.previous_ids = all_ids;
|
||||
if (delete_count + add_count > 0) {
|
||||
this.emit('status_changed');
|
||||
}
|
||||
return null;
|
||||
// TODO: implement
|
||||
};
|
||||
DynamicMode.prototype.updateStickers = function(){
|
||||
var self = this;
|
||||
this.mpd.findStickers('/', LAST_QUEUED_STICKER, function(err, stickers){
|
||||
var sticker, file, value, track;
|
||||
if (err) {
|
||||
console.error('dynamicmode find sticker error:', err);
|
||||
return;
|
||||
}
|
||||
for (sticker in stickers) {
|
||||
file = sticker[0], value = sticker[1];
|
||||
track = self.mpd.library.track_table[file];
|
||||
self.last_queued[file] = new Date(value);
|
||||
}
|
||||
self.got_stickers = true;
|
||||
});
|
||||
// TODO: implement
|
||||
};
|
||||
DynamicMode.prototype.getRandomSongFiles = function(count){
|
||||
var never_queued, sometimes_queued, file, ref$, track, max_weight, triangle_area, rectangle_area, total_size, files, i, index, self = this;
|
||||
if (count === 0) {
|
||||
return [];
|
||||
}
|
||||
never_queued = [];
|
||||
sometimes_queued = [];
|
||||
for (file in (ref$ = this.mpd.library.track_table)) {
|
||||
track = ref$[file];
|
||||
if (this.last_queued[file] != null) {
|
||||
sometimes_queued.push(track);
|
||||
} else {
|
||||
never_queued.push(track);
|
||||
}
|
||||
}
|
||||
sometimes_queued.sort(function(a, b){
|
||||
return self.last_queued[b.file].getTime() - self.last_queued[a.file].getTime();
|
||||
});
|
||||
max_weight = sometimes_queued.length;
|
||||
triangle_area = Math.floor(max_weight * max_weight / 2);
|
||||
if (max_weight === 0) {
|
||||
max_weight = 1;
|
||||
}
|
||||
rectangle_area = max_weight * never_queued.length;
|
||||
total_size = triangle_area + rectangle_area;
|
||||
if (total_size === 0) {
|
||||
return [];
|
||||
}
|
||||
files = [];
|
||||
for (i = 0; i < count; ++i) {
|
||||
index = Math.random() * total_size;
|
||||
if (index < triangle_area) {
|
||||
track = sometimes_queued[Math.floor(Math.sqrt(index))];
|
||||
} else {
|
||||
track = never_queued[Math.floor((index - triangle_area) / max_weight)];
|
||||
}
|
||||
files.push(track.file);
|
||||
}
|
||||
return files;
|
||||
// TODO: implement
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ function LastFm(bus) {
|
|||
bus.on('save_state', bind$(self, 'saveState'));
|
||||
bus.on('restore_state', bind$(self, 'restoreState'));
|
||||
bus.on('socket_connect', bind$(self, 'onSocketConnection'));
|
||||
bus.on('mpd', bind$(self, 'setMpd'));
|
||||
}
|
||||
LastFm.prototype.restoreState = function(state){
|
||||
var ref$;
|
||||
|
|
@ -36,14 +35,6 @@ LastFm.prototype.saveState = function(state){
|
|||
state.status.lastfm_api_key = this.api_key;
|
||||
state.lastfm_secret = this.api_secret;
|
||||
};
|
||||
LastFm.prototype.setMpd = function(mpd){
|
||||
var self = this;
|
||||
this.mpd = mpd;
|
||||
this.mpd.on('statusupdate', function(){
|
||||
self.updateNowPlaying();
|
||||
self.checkScrobble();
|
||||
});
|
||||
};
|
||||
LastFm.prototype.onSocketConnection = function(socket){
|
||||
var self = this;
|
||||
socket.on('LastfmGetSession', function(data){
|
||||
|
|
@ -115,82 +106,10 @@ LastFm.prototype.checkTrackNumber = function(trackNumber){
|
|||
}
|
||||
};
|
||||
LastFm.prototype.checkScrobble = function(){
|
||||
var this_item, ref$, track, min_amt, max_amt, half_amt, session_key, len$, username, ref1$;
|
||||
this_item = this.mpd.status.current_item;
|
||||
if (this.mpd.status.state === 'play') {
|
||||
if (this.previous_play_state !== 'play') {
|
||||
this.playing_start = new Date(new Date().getTime() - this.playing_time);
|
||||
this.previous_play_state = this.mpd.status.state;
|
||||
}
|
||||
}
|
||||
this.playing_time = new Date().getTime() - this.playing_start.getTime();
|
||||
if ((this_item != null ? this_item.id : void 8) === ((ref$ = this.last_playing_item) != null ? ref$.id : void 8)) {
|
||||
return;
|
||||
}
|
||||
if ((track = (ref$ = this.last_playing_item) != null ? ref$.track : void 8) != null) {
|
||||
min_amt = 15 * 1000;
|
||||
max_amt = 4 * 60 * 1000;
|
||||
half_amt = track.time / 2 * 1000;
|
||||
if (this.playing_time >= min_amt && (this.playing_time >= max_amt || this.playing_time >= half_amt)) {
|
||||
if (track.artist_name) {
|
||||
for (session_key = 0, len$ = (ref$ = this.scrobblers).length; session_key < len$; ++session_key) {
|
||||
username = ref$[session_key];
|
||||
this.queueScrobble({
|
||||
sk: session_key,
|
||||
timestamp: Math.round(this.playing_start.getTime() / 1000),
|
||||
album: ((ref1$ = track.album) != null ? ref1$.name : void 8) || "",
|
||||
track: track.name || "",
|
||||
artist: track.artist_name || "",
|
||||
albumArtist: track.album_artist_name || "",
|
||||
duration: track.time || "",
|
||||
trackNumber: this.checkTrackNumber(track.track)
|
||||
});
|
||||
}
|
||||
this.flushScrobbleQueue();
|
||||
} else {
|
||||
console.warn("Not scrobbling " + track.name + " - missing artist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
this.last_playing_item = this_item;
|
||||
this.previous_play_state = this.mpd.status.state;
|
||||
this.playing_start = new Date();
|
||||
this.playing_time = 0;
|
||||
// TODO: implement
|
||||
};
|
||||
LastFm.prototype.updateNowPlaying = function(){
|
||||
var ref$, track, username, session_key, ref1$;
|
||||
if (this.mpd.status.state !== 'play') {
|
||||
return;
|
||||
}
|
||||
if ((track = (ref$ = this.mpd.status.current_item) != null ? ref$.track : void 8) == null) {
|
||||
return;
|
||||
}
|
||||
if (this.previous_now_playing_id === this.mpd.status.current_item.id) {
|
||||
return;
|
||||
}
|
||||
this.previous_now_playing_id = this.mpd.status.current_item.id;
|
||||
if (!track.artist_name) {
|
||||
console.warn("Not updating last.fm now playing for " + track.name + ": missing artist");
|
||||
return;
|
||||
}
|
||||
for (username in (ref$ = this.scrobblers)) {
|
||||
session_key = ref$[username];
|
||||
this.lastfm.request("track.updateNowPlaying", {
|
||||
sk: session_key,
|
||||
track: track.name || "",
|
||||
artist: track.artist_name || "",
|
||||
album: ((ref1$ = track.album) != null ? ref1$.name : void 8) || "",
|
||||
albumArtist: track.album_artist_name || "",
|
||||
trackNumber: this.checkTrackNumber(track.track),
|
||||
duration: track.time || "",
|
||||
handlers: {
|
||||
error: fn$
|
||||
}
|
||||
});
|
||||
}
|
||||
function fn$(error){
|
||||
console.error("error from last.fm track.updateNowPlaying: " + error.message);
|
||||
}
|
||||
// TODO: implement
|
||||
};
|
||||
function bind$(obj, key){
|
||||
return function(){ return obj[key].apply(obj, arguments) };
|
||||
|
|
|
|||
|
|
@ -8,15 +8,5 @@ function Stream(gb) {
|
|||
}
|
||||
|
||||
function setup(self) {
|
||||
self.gb.on('stateRestored', function(state) {
|
||||
var ahttpd = state.mpd_conf.audio_httpd;
|
||||
self.port = ahttpd.port;
|
||||
self.format = ahttpd.format;
|
||||
});
|
||||
self.gb.on('socketConnect', function(client) {
|
||||
client.emit('StreamInfo', {
|
||||
port: self.port,
|
||||
format: self.format,
|
||||
});
|
||||
});
|
||||
// TODO implement
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ function Upload(gb) {
|
|||
console.error("TODO: fix upload plugin (upload disabled)");
|
||||
return;
|
||||
bus.on('app', bind$(this, 'setUpRoutes'));
|
||||
bus.on('mpd', bind$(this, 'setMpd'));
|
||||
bus.on('save_state', bind$(this, 'saveState'));
|
||||
bus.on('restore_state', bind$(this, 'restoreState'));
|
||||
bus.on('socket_connect', bind$(this, 'onSocketConnection'));
|
||||
|
|
@ -30,7 +29,9 @@ Upload.prototype.restoreState = function(state){
|
|||
var ref$;
|
||||
this.want_to_queue = (ref$ = state.want_to_queue) != null ? ref$ : [];
|
||||
this.is_enabled = true;
|
||||
if ((this.music_directory = state.mpd_conf.music_directory) == null) {
|
||||
// TODO set this.music_directory based on config
|
||||
// this.music_directory = ??
|
||||
if (!this.music_directory) {
|
||||
this.is_enabled = false;
|
||||
console.warn("No music directory set. Upload disabled.");
|
||||
return;
|
||||
|
|
@ -40,10 +41,6 @@ Upload.prototype.saveState = function(state){
|
|||
state.want_to_queue = this.want_to_queue;
|
||||
state.status.upload_enabled = this.is_enabled;
|
||||
};
|
||||
Upload.prototype.setMpd = function(mpd){
|
||||
this.mpd = mpd;
|
||||
this.mpd.on('libraryupdate', bind$(this, 'flushWantToQueue'));
|
||||
};
|
||||
Upload.prototype.onSocketConnection = function(socket){
|
||||
var this$ = this;
|
||||
socket.on('ImportTrackUrl', function(url_string){
|
||||
|
|
@ -73,29 +70,7 @@ Upload.prototype.onSocketConnection = function(socket){
|
|||
Upload.prototype.importFile = function(temp_file, remote_filename, cb){
|
||||
var this$ = this;
|
||||
if (cb == null) cb = function(){};
|
||||
this.mpd.getFileInfo("file://" + temp_file, function(err, track){
|
||||
var suggested_path, relative_path, dest;
|
||||
if (err) {
|
||||
console.warn("Unable to read tags to get a suggested upload path: " + err.stack);
|
||||
suggested_path = safePath(remote_filename);
|
||||
} else {
|
||||
suggested_path = getSuggestedPath(track, remote_filename);
|
||||
}
|
||||
relative_path = path.join('incoming', suggested_path);
|
||||
dest = path.join(this$.music_directory, relative_path);
|
||||
mkdirp(path.dirname(dest), function(err){
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return cb(err);
|
||||
}
|
||||
mv(temp_file, dest, function(err){
|
||||
this$.want_to_queue.push(relative_path);
|
||||
this$.emit('state_changed');
|
||||
console.info("Track was uploaded: " + dest);
|
||||
cb(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
// TODO implement this
|
||||
};
|
||||
Upload.prototype.setUpRoutes = function(app){
|
||||
var this$ = this;
|
||||
|
|
@ -117,25 +92,6 @@ Upload.prototype.setUpRoutes = function(app){
|
|||
}));
|
||||
});
|
||||
};
|
||||
Upload.prototype.flushWantToQueue = function(){
|
||||
var i, files, file;
|
||||
i = 0;
|
||||
files = [];
|
||||
while (i < this.want_to_queue.length) {
|
||||
file = this.want_to_queue[i];
|
||||
if (this.mpd.library.track_table[file] != null) {
|
||||
files.push(file);
|
||||
this.want_to_queue.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
this.mpd.queueFiles(files);
|
||||
this.mpd.queueFilesInStoredPlaylist(files, "Incoming");
|
||||
if (files.length) {
|
||||
this.emit('state_changed');
|
||||
}
|
||||
};
|
||||
function bind$(obj, key){
|
||||
return function(){ return obj[key].apply(obj, arguments) };
|
||||
}
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "groovebasin",
|
||||
"description": "No-nonsense music client and daemon based on mpd",
|
||||
"description": "Web-based music server and client inspired by Amarok 1.4",
|
||||
"author": "Andrew Kelley <superjoe30@gmail.com>",
|
||||
"version": "0.2.0",
|
||||
"licenses": [
|
||||
|
|
@ -23,20 +23,17 @@
|
|||
"zipstream": "~0.2.1",
|
||||
"express": "~3.3.8",
|
||||
"temp": "~0.5.1",
|
||||
"async": "~0.1.22",
|
||||
"superagent": "~0.15.4",
|
||||
"mkdirp": "~0.3.5",
|
||||
"mv": "0.0.5",
|
||||
"which": "~1.0.5",
|
||||
"osenv": "0.0.3",
|
||||
"walkdir": "0.0.7",
|
||||
"pend": "~1.1.0",
|
||||
"musicmetadata-superjoe30": "~0.5.0",
|
||||
"zfill": "0.0.1",
|
||||
"mpd": "~1.0.2",
|
||||
"requireindex": "~1.0.1",
|
||||
"mess": "~0.1.1",
|
||||
"diacritics": "~1.0.0"
|
||||
"diacritics": "~1.0.0",
|
||||
"groove": "0.0.0",
|
||||
"osenv": "0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"handlebars": "1.0.7",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ exports.init = init;
|
|||
var trying_to_stream = false;
|
||||
var actually_streaming = false;
|
||||
var streaming_buffering = false;
|
||||
var mpd = null;
|
||||
var player = null;
|
||||
var port = null;
|
||||
var format = null;
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ function getUrl(){
|
|||
}
|
||||
|
||||
function updatePlayer() {
|
||||
var should_stream = trying_to_stream && mpd.status.state === "play";
|
||||
var should_stream = trying_to_stream && player.status.state === "play";
|
||||
if (actually_streaming === should_stream) return;
|
||||
if (should_stream) {
|
||||
soundManager.destroySound('stream');
|
||||
|
|
@ -88,15 +88,15 @@ function setUpUi() {
|
|||
$stream_btn.on('click', toggleStatus);
|
||||
}
|
||||
|
||||
function init(mpdInstance, socket) {
|
||||
mpd = mpdInstance;
|
||||
function init(playerInstance, socket) {
|
||||
player = playerInstance;
|
||||
|
||||
soundManager.setup({
|
||||
url: "/vendor/soundmanager2/",
|
||||
flashVersion: 9,
|
||||
debugMode: false
|
||||
});
|
||||
mpd.on('statusupdate', updatePlayer);
|
||||
player.on('statusupdate', updatePlayer);
|
||||
socket.on('StreamInfo', function(info) {
|
||||
port = info.port;
|
||||
format = info.format;
|
||||
|
|
|
|||
Loading…
Reference in a new issue