App-MHFS
view release on metacpan or search on metacpan
share/public_html/static/music_inc/music_inc_module.js view on Meta::CPAN
}
}
return track;
}
function _PlayTrack(trackname) {
let queuePos;
if(AQ_ID() !== -1) {
queuePos = AudioQueue[0].track;
}
else if(Tracks_QueueCurrent) {
queuePos = Tracks_QueueCurrent;
}
let queueAfter; //falsey is tail
if(queuePos) {
queueAfter = queuePos.next;
}
AQ_stopAudioWithoutID(-1);
Tracks_QueueCurrent = null;
return _QueueTrack(trackname, queuePos, queueAfter);
}
// BuildPTrack is expensive so _QueueTrack and _PlayTrack don't call it
function QueueTrack(trackname, after, before) {
let res = _QueueTrack(trackname, after, before);
BuildPTrack();
return res;
}
function PlayTrack(trackname) {
let res = _PlayTrack(trackname);
BuildPTrack();
return res;
}
function QueueTracks(tracks, after) {
tracks.forEach(function(elm) {
after = _QueueTrack(elm, after);
});
BuildPTrack();
return after;
}
function PlayTracks(tracks) {
let trackname = tracks.shift();
if(!trackname) return;
let after = _PlayTrack(trackname);
if(!tracks.length) return;
QueueTracks(tracks, after);
}
// remove played items from the audio queue
function AQ_clean() {
let toDelete = 0;
for(let i = 0; i < AudioQueue.length; i++) {
if(! AudioQueue[i].endTime) break;
// run and remove associated graphics timers
let timerdel = 0;
for(let j = 0; j < AudioQueue[i].timers.length; j++) {
if(AudioQueue[i].timers[j].time <= MainAudioContext.currentTime) {
console.log('aqid: ' + AudioQueue[i].aqid + ' running timer at ' + MainAudioContext.currentTime);
AudioQueue[i].timers[j].func(AudioQueue[i].timers[j]);
timerdel++;
}
}
if(timerdel)AudioQueue[i].timers.splice(0, timerdel);
// remove if it has passed
if(AudioQueue[i].endTime <= MainAudioContext.currentTime) {
console.log('aqid: ' + AudioQueue[i].aqid + ' segment elapsed, removing');
toDelete++;
}
}
if(toDelete) {
// if the AQ is empty and there's a current track we fell behind
if((toDelete === AudioQueue.length) && (Tracks_QueueCurrent)) {
SetPlayText(Tracks_QueueCurrent.trackname + ' {LOADING}');
}
AudioQueue.splice(0, toDelete);
}
}
//imprecise, in seconds
function AQ_unqueuedTime() {
let unqueuedtime = 0;
for(let i = 0; i < AudioQueue.length; i++) {
if(!AudioQueue[i].startTime) {
unqueuedtime += (AudioQueue[i].duration / AudioQueue[i].track.sampleRate);
}
}
return unqueuedtime;
}
// returns the currently or about to be playing aqid
function AQ_ID() {
AQ_clean();
for(let i = 0; i < AudioQueue.length; i++) {
return AudioQueue[i].aqid;
}
return -1;
}
function AQ_stopAudioWithoutID(aqid) {
if(!AudioQueue.length) return;
let dCount = 0;
for(let i = AudioQueue.length-1; i >= 0; i--) {
if(AudioQueue[i].aqid === aqid) {
break;
}
dCount++;
if(AudioQueue[i].source) {
AudioQueue[i].source.disconnect();
AudioQueue[i].source.stop();
}
console.log('aqid: ' + AudioQueue[i].aqid + ' AQ_stopAudioWithoutID delete, curr: ' + aqid);
}
if(dCount) {
AudioQueue.splice(AudioQueue.length - dCount, dCount);
}
}
if(typeof abortablesleep === 'undefined') {
//const sleep = m => new Promise(r => setTimeout(r, m));
const abortablesleep = (ms, signal) => new Promise(function(resolve) {
const onTimerDone = function() {
resolve();
signal.removeEventListener('abort', stoptimer);
};
let timer = setTimeout(function() {
console.log('sleep done ' + ms);
onTimerDone();
}, ms);
const stoptimer = function() {
console.log('aborted sleep');
onTimerDone();
clearTimeout(timer);
};
signal.addEventListener('abort', stoptimer);
});
DeclareGlobalFunc('abortablesleep', abortablesleep);
}
let FAQ_MUTEX = new Mutex();
async function fillAudioQueue(time) {
// starting a fresh queue, render the text
if(Tracks_QueueCurrent) {
let track = Tracks_QueueCurrent;
let prevtext = track.prev ? track.prev.trackname : '';
SetPrevText(prevtext);
SetPlayText(track.trackname + ' {LOADING}');
let nexttext = track.next ? track.next.trackname : '';
SetNextText(nexttext);
SetCurtimeText(time || 0);
if(!time) SetSeekbarValue(time || 0);
SetEndtimeText(track.duration || 0);
}
// Stop the previous FAQ before starting
FACAbortController.abort();
FACAbortController = new AbortController();
let mysignal = FACAbortController.signal;
let unlock = await FAQ_MUTEX.lock();
if(mysignal.aborted) {
console.log('abort after mutex acquire');
unlock();
return;
}
let initializing = 1;
TRACKLOOP:while(1) {
// advance the track
AQID++;
if(!initializing) {
if(!document.getElementById("repeattrack").checked) {
Tracks_QueueCurrent = Tracks_QueueCurrent.next;
}
}
initializing = 0;
let track = Tracks_QueueCurrent;
if(! track) {
unlock();
return;
}
// cleanup nwdrflac
if(NWDRFLAC) {
// we can reuse it if the urls match
if(NWDRFLAC.url !== track.url)
{
await NWDRFLAC.close();
NWDRFLAC = null;
if(mysignal.aborted) {
console.log('abort after cleanup');
unlock();
return;
}
}
else{
share/public_html/static/music_inc/music_inc_module.js view on Meta::CPAN
// queue the track
let dectime = 0;
if(time) {
dectime = Math.floor(time * NWDRFLAC.sampleRate);
time = 0;
}
let isStart = true;
while(dectime < NWDRFLAC.totalPCMFrameCount) {
let todec = Math.min(NWDRFLAC.sampleRate, NWDRFLAC.totalPCMFrameCount - dectime);
// if plenty of audio is queued. Don't download yet
let todecsecs = todec / NWDRFLAC.sampleRate;
const nextendtime = function(){
return (AQ_unqueuedTime()+todecsecs);
};
console.log('nextendtime ' + nextendtime() + ' curdecsecs ' + todecsecs);
while(nextendtime() > AQMaxDecodedTime) {
let mssleep = (nextendtime() - AQMaxDecodedTime) * 1000;
await abortablesleep(mssleep, mysignal);
if(mysignal.aborted) {
console.log('handling aborted sleep');
unlock();
return;
}
}
// decode
let buffer;
for(let failedcount = 0;!buffer;) {
try {
buffer = await NWDRFLAC.read_pcm_frames_to_AudioBuffer(dectime, todec, mysignal, MainAudioContext);
if(mysignal.aborted) {
console.log('aborted decodeaudiodata success');
unlock();
return;
}
if(buffer.duration !== (todec / NWDRFLAC.sampleRate)) {
buffer = null;
throw('network error? buffer wrong length');
}
}
catch(error) {
console.error(error);
if(mysignal.aborted) {
console.log('aborted read_pcm_frames decodeaudiodata catch');
unlock();
return;
}
failedcount++;
if(failedcount == 2) {
console.log('Encountered error twice, advancing to next track');
// assume it's corrupted. force free it
await NWDRFLAC.close();
NWDRFLAC = null;
continue TRACKLOOP;
}
}
}
// Add to the audio queue
let aqItem = { 'buffer' : buffer, 'duration' : todec, 'aqid' : AQID, 'skiptime' : (dectime / NWDRFLAC.sampleRate), 'track' : track, 'playbackinfo' : {}, 'timers' : []};
// At start and end track update the GUI
let isEnd = ((dectime+todec) === NWDRFLAC.totalPCMFrameCount);
if(isStart || isEnd) {
aqItem.func = function(startTime, endTime) {
if(isStart) {
console.log('aqid: ' + aqItem.aqid + ' start timer at ' + startTime + ' currentTime ' + MainAudioContext.currentTime);
aqItem.timers.push({'time': startTime, 'func': function() {
seekbar.min = 0;
seekbar.max = track.duration;
SetEndtimeText(track.duration);
SetPlayText(track.trackname);
let prevtext = track.prev ? track.prev.trackname : '';
SetPrevText(prevtext);
let nexttext = track.next ? track.next.trackname : '';
SetNextText(nexttext);
}});
isStart = false;
}
if(isEnd) {
console.log('aqid: ' + aqItem.aqid + ' end timer at ' + endTime + ' currentTime ' + MainAudioContext.currentTime);
aqItem.timers.push({'time': endTime, 'func': function(){
let curTime = 0;
SetEndtimeText(0);
SetCurtimeText(curTime);
SetSeekbarValue(curTime);
SetPrevText(track.trackname);
SetPlayText('');
SetNextText('');
}});
}
}
}
AudioQueue.push(aqItem);
dectime += todec;
}
}
unlock();
}
var prevbtn = document.getElementById("prevbtn");
var sktxt = document.getElementById("seekfield");
var seekbar = document.getElementById("seekbar");
var ppbtn = document.getElementById("ppbtn");
var rptrackbtn = document.getElementById("repeattrack");
var curtimetxt = document.getElementById("curtime");
var endtimetxt = document.getElementById("endtime");
var nexttxt = document.getElementById('next_text');
var prevtxt = document.getElementById('prev_text');
var playtxt = document.getElementById('play_text');
var dbarea = document.getElementById('musicdb');
// BEGIN UI handlers
rptrackbtn.addEventListener('change', function(e) {
let aqid = AQ_ID();
if(aqid === -1) return; // nothing is playing repeattrack should do nothing
if(aqid === AQID) return; // current playing is still being queued do nothing
console.log('rptrack abort');
AQ_stopAudioWithoutID(aqid); // stop the audio queue of next track(s)
if(e.target.checked) {
// repeat the currently playing track
Tracks_QueueCurrent = AudioQueue[0].track;
}
else {
// queue the next track
Tracks_QueueCurrent = AudioQueue[0].track.next;
}
fillAudioQueue();
});
ppbtn.addEventListener('click', function (e) {
if ((ppbtn.textContent == 'PAUSE')) {
MainAudioContext.suspend();
ppbtn.textContent = 'PLAY';
}
else if ((ppbtn.textContent == 'PLAY')) {
MainAudioContext.resume();
ppbtn.textContent = 'PAUSE';
}
( run in 3.358 seconds using v1.01-cache-2.11-cpan-75ffa21a3d4 )