|
|
|
|
@ -58,7 +58,7 @@ var NEXT_FILE_COUNT = OPEN_FILE_COUNT - PREV_FILE_COUNT;
|
|
|
|
|
|
|
|
|
|
// when a streaming client connects we send them many buffers quickly
|
|
|
|
|
// in order to get the stream started, then we slow down.
|
|
|
|
|
var instantBufferBytes = 220 * 1024;
|
|
|
|
|
var ENCODE_QUEUE_DURATION = 5;
|
|
|
|
|
|
|
|
|
|
var DB_SCALE = Math.log(10.0) * 0.05;
|
|
|
|
|
var REPLAYGAIN_PREAMP = 0.75;
|
|
|
|
|
@ -90,9 +90,6 @@ function Player(db, musicDirectory) {
|
|
|
|
|
maxAsync: 1,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.groovePlayer = null; // initialized by initialize method
|
|
|
|
|
this.groovePlaylist = null; // initialized by initialize method
|
|
|
|
|
|
|
|
|
|
this.playlist = {};
|
|
|
|
|
this.currentTrack = null;
|
|
|
|
|
this.tracksInOrder = []; // another way to look at playlist
|
|
|
|
|
@ -101,6 +98,7 @@ function Player(db, musicDirectory) {
|
|
|
|
|
this.invalidPaths = {}; // files that could not be opened
|
|
|
|
|
|
|
|
|
|
this.repeat = Player.REPEAT_OFF;
|
|
|
|
|
this.hardwarePlayback = null;
|
|
|
|
|
this.isPlaying = false;
|
|
|
|
|
this.trackStartDate = null;
|
|
|
|
|
this.pausedTime = 0;
|
|
|
|
|
@ -115,21 +113,29 @@ function Player(db, musicDirectory) {
|
|
|
|
|
|
|
|
|
|
this.headerBuffers = [];
|
|
|
|
|
this.recentBuffers = [];
|
|
|
|
|
this.recentBuffersByteCount = 0;
|
|
|
|
|
this.newHeaderBuffers = [];
|
|
|
|
|
this.openStreamers = [];
|
|
|
|
|
this.lastEncodeItem = null;
|
|
|
|
|
this.lastEncodePos = null;
|
|
|
|
|
this.expectHeaders = true;
|
|
|
|
|
|
|
|
|
|
this.groovePlaylist = groove.createPlaylist();
|
|
|
|
|
this.groovePlayer = groove.createPlayer();
|
|
|
|
|
this.grooveEncoder = groove.createEncoder();
|
|
|
|
|
|
|
|
|
|
this.manualTimeInterval = null;
|
|
|
|
|
this.pendingEncoderAttachDetach = null;
|
|
|
|
|
this.desiredEncoderAttachState = false;
|
|
|
|
|
this.flushEncodedInterval = null;
|
|
|
|
|
this.groovePlaylist.pause();
|
|
|
|
|
this.volume = this.groovePlaylist.volume;
|
|
|
|
|
this.grooveEncoder.formatShortName = "mp3";
|
|
|
|
|
this.grooveEncoder.codecShortName = "mp3";
|
|
|
|
|
this.grooveEncoder.bitRate = 256 * 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Player.prototype.initialize = function(cb) {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
var pend = new Pend();
|
|
|
|
|
pend.go(initPlayer);
|
|
|
|
|
pend.go(initLibrary);
|
|
|
|
|
pend.wait(function(err) {
|
|
|
|
|
initLibrary(function(err) {
|
|
|
|
|
if (err) return cb(err);
|
|
|
|
|
self.requestUpdateDb();
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
@ -137,102 +143,6 @@ Player.prototype.initialize = function(cb) {
|
|
|
|
|
cacheAllOptions(cb);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function initPlayer(cb) {
|
|
|
|
|
var groovePlaylist = groove.createPlaylist();
|
|
|
|
|
var groovePlayer = groove.createPlayer();
|
|
|
|
|
var grooveEncoder = groove.createEncoder();
|
|
|
|
|
grooveEncoder.formatShortName = "mp3";
|
|
|
|
|
grooveEncoder.codecShortName = "mp3";
|
|
|
|
|
grooveEncoder.bitRate = 256 * 1000;
|
|
|
|
|
|
|
|
|
|
var pend = new Pend();
|
|
|
|
|
pend.go(function(cb) {
|
|
|
|
|
groovePlayer.attach(groovePlaylist, cb);
|
|
|
|
|
});
|
|
|
|
|
pend.go(function(cb) {
|
|
|
|
|
grooveEncoder.attach(groovePlaylist, cb);
|
|
|
|
|
});
|
|
|
|
|
pend.wait(doneAttaching);
|
|
|
|
|
|
|
|
|
|
function doneAttaching(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
cb(err);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.groovePlaylist = groovePlaylist;
|
|
|
|
|
self.groovePlayer = groovePlayer;
|
|
|
|
|
self.grooveEncoder = grooveEncoder;
|
|
|
|
|
self.groovePlaylist.pause();
|
|
|
|
|
self.volume = self.groovePlaylist.volume;
|
|
|
|
|
self.groovePlayer.on('nowplaying', onNowPlaying);
|
|
|
|
|
self.flushEncodedInterval = setInterval(flushEncoded, 10);
|
|
|
|
|
cb();
|
|
|
|
|
|
|
|
|
|
function flushEncoded() {
|
|
|
|
|
// poll the encoder for more buffers until either there are no buffers
|
|
|
|
|
// available or we get enough buffered
|
|
|
|
|
while (1) {
|
|
|
|
|
var bufferedSeconds = self.secondsIntoFuture(self.lastEncodeItem, self.lastEncodePos);
|
|
|
|
|
if (bufferedSeconds > 0.5 && self.recentBuffersByteCount >= instantBufferBytes) return;
|
|
|
|
|
var buf = self.grooveEncoder.getBuffer();
|
|
|
|
|
if (!buf) return;
|
|
|
|
|
if (buf.buffer) {
|
|
|
|
|
if (buf.item) {
|
|
|
|
|
if (self.expectHeaders) {
|
|
|
|
|
console.log("encoder: got first non-header");
|
|
|
|
|
self.headerBuffers = self.newHeaderBuffers;
|
|
|
|
|
self.newHeaderBuffers = [];
|
|
|
|
|
self.expectHeaders = false;
|
|
|
|
|
}
|
|
|
|
|
self.recentBuffers.push(buf.buffer);
|
|
|
|
|
self.recentBuffersByteCount += buf.buffer.length;
|
|
|
|
|
while (self.recentBuffers.length > 0 &&
|
|
|
|
|
self.recentBuffersByteCount - self.recentBuffers[0].length >= instantBufferBytes)
|
|
|
|
|
{
|
|
|
|
|
self.recentBuffersByteCount -= self.recentBuffers.shift().length;
|
|
|
|
|
}
|
|
|
|
|
for (var i = 0; i < self.openStreamers.length; i += 1) {
|
|
|
|
|
self.openStreamers[i].write(buf.buffer);
|
|
|
|
|
}
|
|
|
|
|
self.lastEncodeItem = buf.item;
|
|
|
|
|
self.lastEncodePos = buf.pos;
|
|
|
|
|
} else if (self.expectHeaders) {
|
|
|
|
|
// this is a header
|
|
|
|
|
console.log("encoder: got header");
|
|
|
|
|
self.newHeaderBuffers.push(buf.buffer);
|
|
|
|
|
} else {
|
|
|
|
|
// it's a footer, ignore the fuck out of it
|
|
|
|
|
console.info("ignoring encoded audio footer");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// end of playlist sentinel
|
|
|
|
|
console.log("encoder: end of playlist sentinel");
|
|
|
|
|
self.expectHeaders = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onNowPlaying() {
|
|
|
|
|
var playHead = self.groovePlayer.position();
|
|
|
|
|
var decodeHead = self.groovePlaylist.position();
|
|
|
|
|
if (playHead.item) {
|
|
|
|
|
var nowMs = (new Date()).getTime();
|
|
|
|
|
var posMs = playHead.pos * 1000;
|
|
|
|
|
self.trackStartDate = new Date(nowMs - posMs);
|
|
|
|
|
self.currentTrack = self.grooveItems[playHead.item.id];
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
self.emit('currentTrack');
|
|
|
|
|
} else if (!decodeHead.item) {
|
|
|
|
|
// both play head and decode head are null. end of playlist.
|
|
|
|
|
console.log("end of playlist");
|
|
|
|
|
self.currentTrack = null;
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
self.emit('currentTrack');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function initLibrary(cb) {
|
|
|
|
|
var pend = new Pend();
|
|
|
|
|
pend.go(cacheAllDb);
|
|
|
|
|
@ -271,6 +181,7 @@ Player.prototype.initialize = function(cb) {
|
|
|
|
|
dynamicModeOn: null,
|
|
|
|
|
dynamicModeHistorySize: null,
|
|
|
|
|
dynamicModeFutureSize: null,
|
|
|
|
|
hardwarePlayback: null,
|
|
|
|
|
};
|
|
|
|
|
var pend = new Pend();
|
|
|
|
|
for (var name in options) {
|
|
|
|
|
@ -290,6 +201,12 @@ Player.prototype.initialize = function(cb) {
|
|
|
|
|
if (options.dynamicModeFutureSize != null) {
|
|
|
|
|
self.setDynamicModeFutureSize(options.dynamicModeFutureSize);
|
|
|
|
|
}
|
|
|
|
|
var hardwarePlaybackValue = options.hardwarePlayback == null ? true : options.hardwarePlayback;
|
|
|
|
|
if (hardwarePlaybackValue) {
|
|
|
|
|
self.setHardwarePlayback(hardwarePlaybackValue);
|
|
|
|
|
} else {
|
|
|
|
|
self.refreshManualTimeInterval();
|
|
|
|
|
}
|
|
|
|
|
cb();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@ -374,6 +291,181 @@ Player.prototype.initialize = function(cb) {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function startEncoderAttach(self, cb) {
|
|
|
|
|
self.desiredEncoderAttachState = true;
|
|
|
|
|
if (!self.pendingEncoderAttachDetach) {
|
|
|
|
|
self.pendingEncoderAttachDetach = true;
|
|
|
|
|
self.grooveEncoder.attach(self.groovePlaylist, function(err) {
|
|
|
|
|
if (err) return cb(err);
|
|
|
|
|
self.pendingEncoderAttachDetach = false;
|
|
|
|
|
if (!self.desiredEncoderAttachState) startEncoderDetach(self, cb);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startEncoderDetach(self, cb) {
|
|
|
|
|
self.desiredEncoderAttachState = false;
|
|
|
|
|
if (!self.pendingEncoderAttachDetach) {
|
|
|
|
|
self.pendingEncoderAttachDetach = true;
|
|
|
|
|
self.grooveEncoder.detach(function(err) {
|
|
|
|
|
if (err) return cb(err);
|
|
|
|
|
self.pendingEncoderAttachDetach = false;
|
|
|
|
|
if (self.desiredEncoderAttachState) startEncoderAttach(self, cb);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Player.prototype.refreshManualTimeInterval = function() {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
var wantManualTime = !self.hardwarePlayback && !self.desiredEncoderAttachState;
|
|
|
|
|
if (wantManualTime && !self.manualTimeInterval) {
|
|
|
|
|
self.manualTimeInterval = setInterval(checkNowPlaying, 100);
|
|
|
|
|
} else if (!wantManualTime && self.manualTimeInterval) {
|
|
|
|
|
clearInterval(self.manualTimeInterval);
|
|
|
|
|
self.manualTimeInterval = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkNowPlaying() {
|
|
|
|
|
if (!self.currentTrack) return;
|
|
|
|
|
if (!self.isPlaying) return;
|
|
|
|
|
var now = new Date();
|
|
|
|
|
var dbFile = self.libraryIndex.trackTable[self.currentTrack.key];
|
|
|
|
|
var nextTrackBegin = new Date(self.trackStartDate.getTime() + dbFile.duration * 1000);
|
|
|
|
|
if (now > nextTrackBegin) {
|
|
|
|
|
self.currentTrack = self.tracksInOrder[self.currentTrack.index + 1];
|
|
|
|
|
self.trackStartDate = nextTrackBegin;
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
self.emit('currentTrack');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Player.prototype.getBufferedSeconds = function() {
|
|
|
|
|
if (this.recentBuffers.length < 2) return 0;
|
|
|
|
|
var curBuf = this.recentBuffers[0];
|
|
|
|
|
var curSongId = curBuf.item.id;
|
|
|
|
|
var prevBuf = curBuf;
|
|
|
|
|
var totalTime = 0;
|
|
|
|
|
for (var i = 1; i < this.recentBuffers.length - 1; i += 1) {
|
|
|
|
|
var buf = this.recentBuffers[i];
|
|
|
|
|
var thisSongId = buf.item.id;
|
|
|
|
|
if (thisSongId !== curSongId) {
|
|
|
|
|
curSongId = thisSongId;
|
|
|
|
|
totalTime += prevBuf.pos - curBuf.pos;
|
|
|
|
|
curBuf = buf;
|
|
|
|
|
}
|
|
|
|
|
prevBuf = buf;
|
|
|
|
|
}
|
|
|
|
|
totalTime += prevBuf.pos - curBuf.pos;
|
|
|
|
|
return totalTime;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Player.prototype.attachEncoder = function(cb) {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
cb = cb || logIfError;
|
|
|
|
|
|
|
|
|
|
if (self.flushEncodedInterval) return cb();
|
|
|
|
|
|
|
|
|
|
console.info("first streamer connected - attaching encoder");
|
|
|
|
|
self.flushEncodedInterval = setInterval(flushEncoded, 20);
|
|
|
|
|
self.grooveEncoder.on('buffer', onBuffer);
|
|
|
|
|
self.refreshManualTimeInterval();
|
|
|
|
|
|
|
|
|
|
startEncoderAttach(self, cb);
|
|
|
|
|
|
|
|
|
|
function onBuffer() {
|
|
|
|
|
if (!self.desiredEncoderAttachState) return;
|
|
|
|
|
if (self.hardwarePlayback) return;
|
|
|
|
|
var encodeHead = self.grooveEncoder.position();
|
|
|
|
|
var decodeHead = self.groovePlaylist.position();
|
|
|
|
|
var prevCurrentTrack = self.currentTrack;
|
|
|
|
|
if (encodeHead.item) {
|
|
|
|
|
var nowMs = (new Date()).getTime();
|
|
|
|
|
var posMs = encodeHead.pos * 1000;
|
|
|
|
|
self.trackStartDate = new Date(nowMs - posMs);
|
|
|
|
|
self.currentTrack = self.grooveItems[encodeHead.item.id];
|
|
|
|
|
} else if (!decodeHead.item) {
|
|
|
|
|
// both play head and decode head are null. end of playlist.
|
|
|
|
|
console.log("encoder: end of playlist");
|
|
|
|
|
self.currentTrack = null;
|
|
|
|
|
}
|
|
|
|
|
if (prevCurrentTrack !== self.currentTrack) {
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
self.emit('currentTrack');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function flushEncoded() {
|
|
|
|
|
// get rid of old items
|
|
|
|
|
var buf;
|
|
|
|
|
while (buf = self.recentBuffers[0]) {
|
|
|
|
|
var thisBufTrack = self.grooveItems[buf.item.id];
|
|
|
|
|
if (!thisBufTrack) return;
|
|
|
|
|
if (thisBufTrack !== self.currentTrack || self.getCurPos() > buf.pos) {
|
|
|
|
|
self.recentBuffers.shift();
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// poll the encoder for more buffers until either there are no buffers
|
|
|
|
|
// available or we get enough buffered
|
|
|
|
|
while (self.getBufferedSeconds() < ENCODE_QUEUE_DURATION) {
|
|
|
|
|
buf = self.grooveEncoder.getBuffer();
|
|
|
|
|
if (!buf) break;
|
|
|
|
|
if (buf.buffer) {
|
|
|
|
|
if (buf.item) {
|
|
|
|
|
if (self.expectHeaders) {
|
|
|
|
|
console.log("encoder: got first non-header");
|
|
|
|
|
self.headerBuffers = self.newHeaderBuffers;
|
|
|
|
|
self.newHeaderBuffers = [];
|
|
|
|
|
self.expectHeaders = false;
|
|
|
|
|
}
|
|
|
|
|
self.recentBuffers.push(buf);
|
|
|
|
|
for (var i = 0; i < self.openStreamers.length; i += 1) {
|
|
|
|
|
self.openStreamers[i].write(buf.buffer);
|
|
|
|
|
}
|
|
|
|
|
} else if (self.expectHeaders) {
|
|
|
|
|
// this is a header
|
|
|
|
|
console.log("encoder: got header");
|
|
|
|
|
self.newHeaderBuffers.push(buf.buffer);
|
|
|
|
|
} else {
|
|
|
|
|
// it's a footer, ignore the fuck out of it
|
|
|
|
|
console.info("ignoring encoded audio footer");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// end of playlist sentinel
|
|
|
|
|
console.log("encoder: end of playlist sentinel");
|
|
|
|
|
self.expectHeaders = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function logIfError(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
console.error("Unable to attach encoder:", err.stack);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Player.prototype.detachEncoder = function(cb) {
|
|
|
|
|
cb = cb || logIfError;
|
|
|
|
|
|
|
|
|
|
this.clearEncodedBuffer();
|
|
|
|
|
clearInterval(this.flushEncodedInterval);
|
|
|
|
|
this.flushEncodedInterval = null;
|
|
|
|
|
startEncoderDetach(this, cb);
|
|
|
|
|
this.refreshManualTimeInterval();
|
|
|
|
|
this.grooveEncoder.removeAllListeners();
|
|
|
|
|
|
|
|
|
|
function logIfError(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
console.error("Unable to attach encoder:", err.stack);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Player.prototype.requestUpdateDb = function(dirName, forceRescan, cb) {
|
|
|
|
|
var fullPath = path.resolve(this.musicDirectory, dirName || "");
|
|
|
|
|
this.dirScanQueue.add(fullPath, {
|
|
|
|
|
@ -536,23 +628,73 @@ Player.prototype.getOrCreateDir = function (dirName, stat) {
|
|
|
|
|
return dirEntry;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Player.prototype.getCurPos = function() {
|
|
|
|
|
return this.isPlaying ?
|
|
|
|
|
((new Date() - this.trackStartDate) / 1000.0) : this.pausedTime;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Player.prototype.secondsIntoFuture = function(groovePlaylistItem, pos) {
|
|
|
|
|
if (!groovePlaylistItem || !pos) {
|
|
|
|
|
return 0;
|
|
|
|
|
Player.prototype.setHardwarePlayback = function(value, cb) {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
cb = cb || logIfError;
|
|
|
|
|
value = !!value;
|
|
|
|
|
|
|
|
|
|
if (value === self.hardwarePlayback) return cb();
|
|
|
|
|
|
|
|
|
|
self.hardwarePlayback = value;
|
|
|
|
|
if (self.hardwarePlayback) {
|
|
|
|
|
self.groovePlayer = groove.createPlayer();
|
|
|
|
|
self.groovePlayer.attach(self.groovePlaylist, onAttachPlayer);
|
|
|
|
|
} else {
|
|
|
|
|
self.groovePlayer.detach(onDetachPlayer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var item = this.grooveItems[groovePlaylistItem.id];
|
|
|
|
|
function onAttachPlayer(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
self.hardwarePlayback = false;
|
|
|
|
|
return cb(err);
|
|
|
|
|
}
|
|
|
|
|
self.refreshManualTimeInterval();
|
|
|
|
|
self.groovePlayer.on('nowplaying', onNowPlaying);
|
|
|
|
|
self.persistOption('hardwarePlayback', self.hardwarePlayback);
|
|
|
|
|
self.emit('hardwarePlayback', self.hardwarePlayback);
|
|
|
|
|
cb();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (item === this.currentTrack) {
|
|
|
|
|
return pos - this.getCurPos();
|
|
|
|
|
function onDetachPlayer(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
self.hardwarePlayback = true;
|
|
|
|
|
} else {
|
|
|
|
|
return pos;
|
|
|
|
|
self.refreshManualTimeInterval();
|
|
|
|
|
self.persistOption('hardwarePlayback', self.hardwarePlayback);
|
|
|
|
|
self.emit('hardwarePlayback', self.hardwarePlayback);
|
|
|
|
|
}
|
|
|
|
|
cb(err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function logIfError(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
console.error("Unable to set hardware playback mode:", err.stack);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onNowPlaying() {
|
|
|
|
|
var playHead = self.getPlayHead();
|
|
|
|
|
var decodeHead = self.groovePlaylist.position();
|
|
|
|
|
if (playHead.item) {
|
|
|
|
|
var nowMs = (new Date()).getTime();
|
|
|
|
|
var posMs = playHead.pos * 1000;
|
|
|
|
|
self.trackStartDate = new Date(nowMs - posMs);
|
|
|
|
|
self.currentTrack = self.grooveItems[playHead.item.id];
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
self.emit('currentTrack');
|
|
|
|
|
} else if (!decodeHead.item) {
|
|
|
|
|
// both play head and decode head are null. end of playlist.
|
|
|
|
|
console.log("end of playlist");
|
|
|
|
|
self.currentTrack = null;
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
self.emit('currentTrack');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ -572,15 +714,20 @@ Player.prototype.streamMiddleware = function(req, resp, next) {
|
|
|
|
|
resp.write(headerBuffer);
|
|
|
|
|
});
|
|
|
|
|
self.recentBuffers.forEach(function(recentBuffer) {
|
|
|
|
|
resp.write(recentBuffer);
|
|
|
|
|
resp.write(recentBuffer.buffer);
|
|
|
|
|
});
|
|
|
|
|
console.log("sent", count, "bytes of headers and", self.recentBuffersByteCount,
|
|
|
|
|
"bytes of unthrottled data");
|
|
|
|
|
self.attachEncoder();
|
|
|
|
|
self.openStreamers.push(resp);
|
|
|
|
|
req.on('abort', function() {
|
|
|
|
|
req.on('close', function() {
|
|
|
|
|
for (var i = 0; i < self.openStreamers.length; i += 1) {
|
|
|
|
|
if (self.openStreamers[i] === resp) {
|
|
|
|
|
self.openStreamers.splice(i, 1);
|
|
|
|
|
if (self.openStreamers.length === 0) {
|
|
|
|
|
console.info("last streamer disconnected. detaching encoder");
|
|
|
|
|
self.detachEncoder();
|
|
|
|
|
} else {
|
|
|
|
|
console.info("streamer count:", self.openStreamers.length);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -1119,7 +1266,6 @@ Player.prototype.clearEncodedBuffer = function() {
|
|
|
|
|
while (this.recentBuffers.length > 0) {
|
|
|
|
|
this.recentBuffers.shift();
|
|
|
|
|
}
|
|
|
|
|
this.recentBuffersByteCount = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Player.prototype.getSuggestedPath = function(track, filenameHint) {
|
|
|
|
|
@ -1348,6 +1494,16 @@ Player.prototype.checkDynamicMode = function() {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Player.prototype.getPlayHead = function() {
|
|
|
|
|
if (this.hardwarePlayback) {
|
|
|
|
|
return this.groovePlayer.position();
|
|
|
|
|
} else if (this.desiredEncoderAttachState && !this.pendingEncoderAttachDetach) {
|
|
|
|
|
return this.grooveEncoder.position();
|
|
|
|
|
} else {
|
|
|
|
|
return this.groovePlaylist.position();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function operatorCompare(a, b) {
|
|
|
|
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
@ -1401,12 +1557,29 @@ function lazyReplayGainScanPlaylist(self) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function playlistChanged(self) {
|
|
|
|
|
if (self.desiredEncoderAttachState && !self.pendingEncoderAttachDetach && !self.hardwarePlayback) {
|
|
|
|
|
var encodeHead = self.grooveEncoder.position();
|
|
|
|
|
var decodeHead = self.groovePlaylist.position();
|
|
|
|
|
var prevCurrentTrack = self.currentTrack;
|
|
|
|
|
if (encodeHead.item) {
|
|
|
|
|
var nowMs = (new Date()).getTime();
|
|
|
|
|
var posMs = encodeHead.pos * 1000;
|
|
|
|
|
self.trackStartDate = new Date(nowMs - posMs);
|
|
|
|
|
self.currentTrack = self.grooveItems[encodeHead.item.id];
|
|
|
|
|
} else if (!decodeHead.item) {
|
|
|
|
|
// both play head and decode head are null. end of playlist.
|
|
|
|
|
console.log("encoder: end of playlist");
|
|
|
|
|
self.currentTrack = null;
|
|
|
|
|
}
|
|
|
|
|
if (prevCurrentTrack !== self.currentTrack) {
|
|
|
|
|
playlistChanged(self);
|
|
|
|
|
self.emit('currentTrack');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cacheTracksArray(self);
|
|
|
|
|
disambiguateSortKeys(self);
|
|
|
|
|
|
|
|
|
|
self.lastEncodeItem = null;
|
|
|
|
|
self.lastEncodePos = null;
|
|
|
|
|
|
|
|
|
|
if (self.currentTrack) {
|
|
|
|
|
self.tracksInOrder.forEach(function(track, index) {
|
|
|
|
|
var withinPrev = (self.currentTrack.index - index) <= PREV_FILE_COUNT;
|
|
|
|
|
@ -1425,6 +1598,11 @@ function playlistChanged(self) {
|
|
|
|
|
self.pausedTime = 0;
|
|
|
|
|
}
|
|
|
|
|
checkUpdateGroovePlaylist(self);
|
|
|
|
|
console.log("Begin Groove Playlist:");
|
|
|
|
|
self.groovePlaylist.items().forEach(function(item, index) {
|
|
|
|
|
console.log(index, item.file.filename);
|
|
|
|
|
});
|
|
|
|
|
console.log("End Groove Playlist:");
|
|
|
|
|
self.checkDynamicMode();
|
|
|
|
|
|
|
|
|
|
self.emit('playlistUpdate');
|
|
|
|
|
@ -1457,7 +1635,7 @@ function checkUpdateGroovePlaylist(self) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var groovePlaylist = self.groovePlaylist.items();
|
|
|
|
|
var playHead = self.groovePlayer.position();
|
|
|
|
|
var playHead = self.getPlayHead();
|
|
|
|
|
var playHeadItemId = playHead.item && playHead.item.id;
|
|
|
|
|
var groovePlIndex = 0;
|
|
|
|
|
var grooveItem;
|
|
|
|
|
|