App-MHFS

 view release on metacpan or  search on metacpan

share/public_html/static/music_worklet_inprogress/player/mhfsplayer.js  view on Meta::CPAN

    }

    // runs gui updates
    const UpdateTrack = function(bDraw) {
        // determine if a queue update or draw needs to happen
        let toDelete = 0;
        for(let i = 0; i < that.AudioQueue.length; i++) {
            const aqitem = that.AudioQueue[i];
            // mark track as started 
            if(aqitem.needsstart && (aqitem._starttime <= that.ac.currentTime)) {
                aqitem.needsstart = 0;
                bDraw = 1;
            }

            // nothing more to do if track hasn't ended
            if( (!aqitem.queued) || (aqitem.endTime && (aqitem.endTime > that.ac.currentTime))) {
                break;
            }

            // mark ended track
            toDelete++;
        }

        let QueueUpdate = {};
        // draw if we started loading
        if(that.AudioQueue[0] && that.AudioQueue[0].startedLoading) {
            bDraw = 1;
            that.AudioQueue[0].startedLoading = undefined;
            QueueUpdate.trackstate = 'loading';
            QueueUpdate.curtime = that.AudioQueue[0].skiptime;
        }

        // perform queue update
        let track;
        if(toDelete) {
            bDraw = 1;
            const lastTrack = that.AudioQueue[that.AudioQueue.length-1].track;
            that.AudioQueue.splice(0, toDelete);
            QueueUpdate.trackended = 1;
            if(that.AudioQueue.length === 0) {
                track = lastTrack;
                QueueUpdate.trackstate = 'ended';
                that.ac.suspend();
            }
        }

        // no update occured, no need to draw
        if(!bDraw) {
            return;
        }

        // determine the current track if still unknown
        track ||= that.AudioQueue[0].track;
        that.playlistCursor = track;
        QueueUpdate.track = track;

        // show the track
        that.gui.OnQueueUpdate(QueueUpdate);
    }

    // passes in the dest array, the maximum frames to read and when they will be played
    const ReadAudioQueue = function (dest, count, when) {
        UpdateTrack();
        let framesWritten = 0;
        let destoffset = 0;
        for(let i = 0; that.AudioQueue[i]; i++) {
            const item = that.AudioQueue[i];
            if(item.queued) continue;
            if(item.sampleCount === 0) break;
            const toread = Math.min(count, item.sampleCount);
            that.decoderdatareader.read(dest, toread, destoffset);         
            framesWritten += toread;
            item.sampleCount -= toread;
            ProcessTimes(item, toread, when);        
            item.queued = item.donedecode && (item.sampleCount === 0);
            if(!item.queued) break;
            count -= toread;
            if(count === 0) break;
            destoffset = framesWritten;
            when += (toread / that.sampleRate); 
        }
        return framesWritten;
    };

    // The Audio Pump
    const PumpAudioData = [];
    for(let i = 0; i < that.channels; i++) {
        PumpAudioData[i] = new Float32Array(that.ARBLen);
    }
    const PumpAudioZeros = [];
    for(let i = 0; i < that.channels; i++) {
        PumpAudioZeros[i] = new Float32Array(that.ARBLen);
    }    
    const PumpAudio = async function() {
        while(1) {
            do {                

                let bufferedTime = that._ab.gettime();
                const mindelta = 0.1;
                let space = that._ab.getspace();
                if(space === 0) break;
                // ensure we are queuing at least 100 ms in advance
                if(bufferedTime < mindelta) {
                    const bufferFrames = 0.1 * that.sampleRate;
                    const towrite = Math.min(bufferFrames, space);
                    that._ab.write(PumpAudioZeros, towrite);
                    space -= towrite;
                    if(space === 0) break;
                    bufferedTime += (towrite / that.sampleRate);           
                }
                const towrite = ReadAudioQueue(PumpAudioData, space, bufferedTime + that.ac.currentTime);
                if(towrite > 0) {
                    that._ab.write(PumpAudioData, towrite);
                }
            } while(0);
            const mysignal = that.FACAbortController.signal;
            await abortablesleep(50, mysignal);   
        }
    };

    // Audio queuing / decoding
    that.STATES = {
        'NEED_FAQ'   : 0,
        'FAQ_RUNNING': 1
    };

    that.QState = that.STATES.NEED_FAQ;
    that.Tracks_HEAD;
    that.Tracks_TAIL;
    that.playlistCursor;

    const getNextTrack = function(currentTrack) {
        if(that.pborder === "pborder_rptrack") {
            return currentTrack;
        }
        else if(that.pborder === "pborder_rpplaylist") {
            return currentTrack.next ? currentTrack.next : that.Tracks_HEAD;
        }
        else if(that.pborder === "pborder_random") {
            // count the tracks
            let tcount = 0;
            for(let track = that.Tracks_HEAD; track; track = track.next) {

share/public_html/static/music_worklet_inprogress/player/mhfsplayer.js  view on Meta::CPAN

            const pbtrack = {
                'track' : track,
                'skiptime' : start_output_time,
                'sampleCount' : 0,
                'startedLoading' : 1
            };
            that.AudioQueue.push(pbtrack)
            time = 0;
            try {
                const md = await decoder.openTrack(mysignal, track.md, start_output_time);
                if(md) {
                    track.md.duration ??= md.duration;
                    track.md.mediametadata ??= md.mediametadata;
                }
            }
            catch(error) {
                pbtrack.startedLoading = undefined;
                pbtrack.donedecode = 1;
                pbtrack.queued = 1;
                console.error(error);
                if(mysignal.aborted) {
                    break;
                }
                if(firstFailedTrack === track) {
                    console.error("FAQ done, encountered same track failing again");
                    break;
                }
                firstFailedTrack ||= track;
                continue;
            }

            firstFailedTrack = undefined;
            // We better not modify the AQ if we're cancelled
            if(mysignal.aborted) break;

            // art
            LoadTrackArt(track, decoder.track);
    
            // decode the track
            const todec = that.ac.sampleRate;         
            SAMPLELOOP: while(1) {
                // yield so buffers can be queued
                if(pbtrack.sampleCount > 0) {
                    if(!(await abortablesleep_status(0, mysignal)))
                    {
                        break TRACKLOOP;                    
                    }
                }           
    
                // wait for there to be space
                while(that.decoderdatawriter.getspace() < that.ac.sampleRate) {
                    if(!(await abortablesleep_status(250, mysignal)))
                    {
                        break TRACKLOOP;                    
                    }
                }
                
                // decode
                let decdata;
                try {
                    decdata = await decoder.read_pcm_frames_f32_arrs(todec, mysignal);
                    pbtrack.startedLoading = undefined;
                    if(!decdata) break SAMPLELOOP;
                }
                catch(error) {
                    console.error(error);
                    if(mysignal.aborted) {
                        break TRACKLOOP;
                    }
                    await decoder.closeCurrentTrack();
                    break SAMPLELOOP;
                }
                // We better not modify the AQ if we're cancelled
                if(mysignal.aborted) break TRACKLOOP;                     
    
                pbtrack.sampleCount += decdata.length;
                that.decoderdatawriter.write(decdata.chanData);
                
                // break out at end
                if(decdata.length < todec) {
                    break SAMPLELOOP;
                }                      
            }
            pbtrack.donedecode = 1;
            pbtrack.queued = (pbtrack.sampleCount === 0);
        }
        decoder.flush();
        unlock();
        that.QState = that.STATES.NEED_FAQ;
    }

    let FAQPromise;
    const StartQueue = function(track, time) {
        FAQPromise = fillAudioQueue(track, time);    
    };
    
    const StopQueue = async function() {
        that.FACAbortController.abort();
        await FAQPromise;    
    }
    that.StartQueue = StartQueue;
    that.StopQueue  = StopQueue;
    
    // Main playlist queuing. must be done when holding the USERMUTEX

    that.USERMUTEX = new Mutex();

    const Track = function(trackname) {
        that.trackdb[trackname] ??= {'trackname' : trackname, 'url' : that.gui.geturl(trackname)}
        return { md : that.trackdb[trackname]};
    };

    that._queuetracks = function(tracknames, after) {
        // build a linked list of tracks to append
        const HEAD = Track(tracknames[0]);
        let TAIL = HEAD;
        for(let i = 1; i < tracknames.length; i++) {
            const track = Track(tracknames[i]);
            TAIL.next = track;
            track.prev = TAIL;
            TAIL = track;



( run in 0.498 second using v1.01-cache-2.11-cpan-e1769b4cff6 )