App-MHFS

 view release on metacpan or  search on metacpan

share/public_html/static/music_worklet_inprogress/index.html  view on Meta::CPAN

<link rel="stylesheet" href="music.css">
<title>MHFS/Music</title>
</head>
<body>
<div class="header">
    <h1>MHFS/Music</h1>
    <ul class="navbar"><li id="navlibrary" class="navbarleft navbaractive"><a href="#">Library View</a></li><li id="navart" class="navbarright"><a href="#">Playback View</a></li></ul>
</div>
<div class="mainview" id="libraryview">
    <div id="musicdb"></div>
</div>
<div class="mainview" id="artview">
    <img class="artviewimg" alt="album art">
</div>
<div class="footer">
    <div class="ptdiv">
        <div class="scol">
            <div class="colheader">Playlist Previous</div>
            <div id="prev_text" class="newtracktext">&nbsp;</div>
        </div>
        <div class="mcol">
            <div class="colheader">Playlist Cursor</div>
            <div id="play_text" class="newtracktext">&nbsp;</div>
        </div>
        <div class="scol">
            <div class="colheader">Playlist Next</div>
            <div id="next_text" class="newtracktext">&nbsp;</div>
        </div>
    </div>
    <div class="acontrols">
        <input type="button" value="PREV" id="prevbtn" class="controlbtns">
        <button id="ppbtn" type="button" class="controlbtns">PLAY</button>
        <input id="curtime" type="text" class="timedisplay" name="curseconds" value="0:00">
        <input type="range" step="any" id="seekbar" value="0">
        <input id="endtime" type="text" class="timedisplay" name="endseconds" value="0:00">
        <input type="button" value="NEXT" id="nextbtn" class="controlbtns">
        <input type="range" min="0" max="1" value="1.0" step="0.01" id="volslider">
        <select id="playback_order">
            <option value="pborder_default">Default</option>
            <option value="pborder_rptrack">Repeat (Track)</option>
            <option value="pborder_rpplaylist">Repeat (Playlist)</option>
            <option value="pborder_random">Random</option>
            <option value="pborder_reverse">Reverse</option>
        </select>
    </div>
</div>
<audio id="silentaudio" src="15-seconds-of-silence.mp3" loop></audio>
<link rel="modulepreload" href="decoder/bin/_mhfscl.js">
<link rel="modulepreload" href="decoder/mhfscl.js">
<link rel="modulepreload" href="player/AudioWriterReader.js">
<link rel="modulepreload" href='player/mhfsplayer.js'>
<script src="music_inc_module.js" type="module" async> </script>
<script>
    // load the DB
    let urlParams = new URLSearchParams(window.location.search);
    /*
    urlParams.append('fmt', 'musicdbhtml');
    let myRequest = new Request('../../music?'+urlParams.toString());
    fetch(myRequest).then(function(response) {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.text();        
    }).then((html) => {
        document.getElementById("musicdb").innerHTML = html;
    });
    */

    function escapeHTML(unsafe) {
    return unsafe
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
    }

    function dlurl(apiroot, namestack, name) {
        let str = apiroot + '/music_dl?action=dl&name=';
        let fullname = '';
        namestack.forEach(function(elm) {
            fullname += elm + '/';
        });
        fullname += name;
        return str+encodeURIComponent(fullname);        
    }

    function trackHTML(apiroot, namestack, name) {
        let text = '<tr class="track"><td>' + escapeHTML(name) + '</td>';        
        text += '<td><a href="#">Play</a></td><td><a href="#">Queue</a></td><td><a href="';
        text += dlurl(apiroot, namestack, name) + '">DL</a></td></tr>'; 
        return text;
    }

    const OldDB2HTML = function(json) {
        let text = '';
        let namestack = [];
        let files = json.files;        
        while(files.length > 0) {
            let file = files.shift();
            // end of dir
            if(!file) {
                namestack.length = namestack.length-1;
                text += '</tbody></table></td></tr>';
                if((namestack.length === 0)) {
                    text += '<br>';
                }

                continue;
            }
            // is directory
            else if(file.files) {
                text += '<tr><td><table border="1" class="tbl_track"><tbody><tr class="track"><th>';
                text += escapeHTML(file.name) + '</th><th><a href="#">Play</a></th><th><a href="#">Queue</a></th><th><a href="';
                text += dlurl(apiroot, namestack, file.name) + '">DL</a></th></tr>';
                namestack.push(file.name);     
                file.files.push(null);
                file.files.push(...files);
                files = file.files;
            }
            else {

share/public_html/static/music_worklet_inprogress/index.html  view on Meta::CPAN

    };

    const DB2HTMLDomRunner = function(json) {
        const newmdb = document.createElement("div");
        newmdb.setAttribute("id", 'musicdb');
        for( const file of json.files) {
            newmdb.appendChild(DB2HTMLDom(file, ''));
            newmdb.appendChild(document.createElement("br"));
        }
        const oldmdb = document.getElementById("musicdb");
        oldmdb.parentNode.replaceChild(newmdb, oldmdb);
    };

    async function* JSONArrayStreamer(fetchResource, options) {
        const startStr = options?.startStr ?? '[';
        const endStr = options?.endStr ?? ']';
        const dbRes = await fetch(fetchResource);
        const reader = dbRes.body.getReader();
        const decoder = new TextDecoder('utf-8', {'fatal' : true});

        let foundStart;
        let existingData = '';
        do {
            const { value: chunk, done: readerDone } = await reader.read();
            if(chunk) {
                existingData += decoder.decode(chunk, {'stream' : true});
                // find the start token and exclude it and before from the string
                if(!foundStart) {
                    const startStrIndex = existingData.indexOf(startStr);
                    if(startStrIndex === -1) {
                        console.log('existingData does not start with ' + startStr);
                        continue;
                    }
                    existingData = existingData.slice(startStrIndex + startStr.length);
                    foundStart = 1;
                }
                // array items are seperated by ','.
                // attempt parse the input as an array each time it's found
                let toSearch = existingData;
                do {
                    const seperatorIndex = toSearch.lastIndexOf(',');
                    if(seperatorIndex === -1) break;
                    toSearch = toSearch.slice(0, seperatorIndex);
                    try {
                        const records = JSON.parse('['+toSearch+']');
                        yield records;
                        existingData = existingData.slice(toSearch.length+1);
                        break;
                    }
                    catch {
                    }
                } while(1);
            }
            if(readerDone) break;
        } while(1);
        // search backwards for the end token
        // attempting to parse the input as an array each time it's found
        do {
            const endIndex = existingData.lastIndexOf(endStr);
            if(endIndex === -1) {
                throw("Failed to find json end!");
            }
            existingData = existingData.slice(0, endIndex);
            try {
                const records = JSON.parse('['+existingData + ']');
                yield records;
                return;
            }
            catch {
            }
        } while(1);
    };

    urlParams.append('fmt', 'musicdbjson');
    const apiroot = '../..';
    const jsonurl = apiroot + '/music?'+urlParams.toString();
    //fetch(jsonurl).then(function(response) {
    //    if (!response.ok) {
    //        throw new Error(`HTTP error! status: ${response.status}`);
    //    }
    //    return response.json();
    ////}).then(DB2HTMLRunner);
    ////}).then(OldDB2HTML);
    //}).then(DB2HTMLDomRunner);

    //const newmdb = document.createElement("div");
    ///newmdb.setAttribute("id", 'musicdb');
    const oldmdb = document.getElementById("musicdb");
    const jsonReader = JSONArrayStreamer(jsonurl);
    (async function(){
        for await (const json of jsonReader) {
            for( const file of json) {
                //newmdb.appendChild(DB2HTMLDom(file, ''));
                //newmdb.appendChild(document.createElement("br"));
                oldmdb.appendChild(DB2HTMLDom(file, ''));
                oldmdb.appendChild(document.createElement("br"));
            }
        }
        //oldmdb.parentNode.replaceChild(newmdb, oldmdb);
    })();

    // views
    const libraryview = document.getElementById("libraryview");
    const artview = document.getElementById("artview");

    const navlibrary = document.getElementById("navlibrary");
    const navart = document.getElementById("navart");

    const navbaractiveclass = "navbaractive";
    navlibrary.addEventListener('click', function(){
        navart.classList.remove(navbaractiveclass);
        navlibrary.classList.add(navbaractiveclass);
        artview.style.display = "none";
    });

    navart.addEventListener('click', function(){
        navlibrary.classList.remove(navbaractiveclass);
        navart.classList.add(navbaractiveclass);
        artview.style.display = "block";
    });
</script>
</body>
</html>



( run in 0.647 second using v1.01-cache-2.11-cpan-39bf76dae61 )