summaryrefslogblamecommitdiff
path: root/libs/music/player.d/pcm.js
blob: 6e2022c662e665f7df7f46029311e64ad16d57e6 (plain) (tree)
1
2
3
4
5

                                                                  

                                     
 

















                                                                                                          


                                                                                   





















                                                                                                









                                                                                    
























                                                                         





                                                                                 
                  
                         

                                      
                                                         






                                                         







                                                       









                                                                            

























                                                                               
                                  

                                                                                              


                                                                                                  
                                    

                              


































                                                                                                                 


                                             
                            









                                                                        













                                                                                              
                







                                                                          
                
                                                             
               



                               







                                                  


                                                                        
               
          
 





                                            
                                              








































                                                                         
                                                                         



                                                  

                                                                              



































                                                                              
 

                            




                                                 





                                                               
 


                            

                                             

                                                                             
                                                                                                      
                          
                                                                  
                                                                                          
                                                                                










                                                 
                                                 








                                                                   
                                                                                                                             






































                                                                            
 











                                                                                                                
 


                   
; // SPDX-FileCopyrightText: 2021 Gary Wang <toblumia@outlook.com>
; // SPDX-License-Identifier: MIT
; // szO Chris && 2jjy && jxpxxzj Orz
; //     ↑ Moe    ↑ Moe   ↑ Moe

// formatTime,getCookie by Chrissssss
function formatTime(t) {
    if(isNaN(t))return '--:--';
    let m=Math.floor(t/60),s=Math.round(t-Math.floor(t/60)*60);
    if(s<10)return `${m}:0${s}`;
    else if(s==60)return `${m+1}:00`;
    else return `${m}:${s}`;
}
function getCookie(key) {
    if (!navigator.cookieEnabled) return "";
    return document.cookie.replace(new RegExp('(?:(?:^|.*;\\s*)'+key+'\\s*\\=\\s*([^;]*).*$)|^.*$'),'$1');
}
function setCookie(cookieName, cookieValue, maxAge = 0) {
    if (!navigator.cookieEnabled) return;
    var cookieStr = cookieName + "=" + cookieValue;
    if (maxAge > 0) cookieStr += ";max-age=" + maxAge;
    document.cookie = cookieStr;
}
function displayName(item) {
    return item.displayName ? item.displayName : decodeURIComponent(item.fileName);
}

(function() {
    var Helper = function() {
        this.el = null;
        this.entry = function(selector) {
            if (typeof selector == 'string') {
                if (selector[0] == '<') {
                    var singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
                    if (singleTagRE.test(selector)) this.el = document.createElement(RegExp.$1);
                } else {
                    this.el = document.getElementById(selector);
                }
            }
            else this.el = selector;
            return this;
        }
    }
    Helper.prototype = {    
        css: function(property, value) {
            if(this.el) this.el.style.cssText += ';' + property + ":" + value;
            return this;
        },
        attr: function(attr, value) {
            if(this.el) this.el.setAttribute(attr, value);
            return this;
        },
        removeData: function(attr) {
            if(this.el) this.el.removeAttribute("data-" + attr);
            return this;
        },
        data: function(attr, value) {
            if(this.el) this.el.setAttribute("data-" + attr, JSON.stringify(value));
            return this;
        },
        append: function(node) {
            if(this.el) this.el.appendChild(node);
            return this;
        },
        text: function(content) {
            if(this.el) this.el.textContent = content;
            return this;
        },
        click: function(handler) {
            if(!this.el) return this;
            if (typeof(handler) == "function") this.el.onclick = handler;
            else this.el.click();
            return this;
        },
        innerHTML: function(text) {
            if(this.el) this.el.innerHTML = text;
            return this;
        }
    }
    var H = function(selector) {
        var f = new Helper();
        return f.entry(selector);
    }
    function TrickOrTreat(promiseRsp) {
        if (!promiseRsp.ok) {
            throw Error(promiseRsp.statusText); // to cancel the Promise chain...
        }
        return promiseRsp.json();
    }
    var Player = {
        mediaRootUrl: '',
        path: null, // sample: 'Test/'
        data: null,
        preferredFormats: undefined, // sample: 'mp3,ogg'
        audio: document.getElementsByTagName('audio')[0],
        currentIndex: -1,
        loop: 0,
        order: 0,
        playlist: H("playlist").el,
        folderlist: H("folderlist").el,
        nowPlaying: H("nowPlaying").el,
        apiUrl: "/libs/music/player.d/cgi-bin/pcm.cgi",
        _currentSongInfoJson: undefined,
        _chapterNeedUpdate: true,

        setInfoJson: (jsonData) => {
            this._currentSongInfoJson = jsonData;
            this._chapterNeedUpdate = true;
        },
        
        updateMetadata: function() {
            if ('mediaSession' in navigator) {
                window.navigator.mediaSession.metadata = new MediaMetadata({
                    title: nowPlaying.innerHTML,
                    album: decodeURIComponent(this.path)
                });
            }
        },

        applyChapterData: () => {
            if (!this._chapterNeedUpdate) return;
            if (Player.audio.duration) {
                if (this._currentSongInfoJson) {
                    let duration = Player.audio.duration;
                    let progressChapterData = [];
                    this._currentSongInfoJson.chapters.forEach((chapter) => {
                        let chapterObj = {};
                        chapterObj.start = chapter.start_time / duration * 100;
                        chapterObj.title = chapter.title;
                        progressChapterData.push(chapterObj);
                    });
                    H("progress-bar").data("chapters", progressChapterData);
                } else {
                    H("progress-bar").removeData("chapters");
                }
                this._chapterNeedUpdate = false;
            }
        },

        fetchAdditionalInfo: (infoJsonfileUrl) => {
            fetch(infoJsonfileUrl).then(TrickOrTreat).then((data) => {
                Player.setInfoJson(data);
            });
        },

        playAtIndex: function(i) {
            let fullPath = this.path + this.data[i].fileName;
            let srcUrl = this.data[i].url ? this.data[i].url : (this.mediaRootUrl + fullPath);
            // FIXME: trigger this when audio doesn't finished load will cause play promise error.
            this.audio.pause();
            this.currentIndex = i;
            this.audio.src = srcUrl;
            this.audio.load();
            this.audio.play();
            window.history.replaceState("","Useless Title","#/" + fullPath + "/"); // title seems be fucked.
            H(this.nowPlaying).innerHTML(displayName(this.data[i]));

            if (this.data[i].additionalInfo) {
                let infoJsonFile = (fullPath.substring(0, fullPath.lastIndexOf('.')) || fullPath) + ".info.json";
                this.fetchAdditionalInfo(infoJsonFile);
            } else {
                this.setInfoJson(undefined);
            }
        },

        fetchServerInfo: function(callback) {
            var that = this;
            fetch(this.apiUrl, {
                method: 'POST',
                body: new URLSearchParams({
                    'do': 'getserverinfo'
                })
            }).then(TrickOrTreat).then((data) => {
                if (data.result.mediaRootUrl && data.result.mediaRootUrl.length > 1) {
                    that.mediaRootUrl = data.result.mediaRootUrl;
                    if (!that.mediaRootUrl.endsWith('/')) {
                        that.mediaRootUrl = that.mediaRootUrl + '/';
                    }
                }
                if (data.result.serverName) {
                    let el = H("server-name");
                    if (el) {
                        el.text(data.result.serverName);
                    }
                    document.title = data.result.serverName;
                }
                
                typeof callback === 'function' && callback();
            })
        },

        freshFolderlist: function(callback) {
            var that = this;
            requestBody = {
                'do': 'getfilelist',
            };
            if (that.preferredFormats) {
                requestBody['preferredFormats'] = that.preferredFormats;
            }
            fetch(this.apiUrl, {
                method: 'POST',
                body: new URLSearchParams(requestBody)
            }).then(TrickOrTreat).then((data) => {
                if (data.status != 200) { 
                    console.error("Fetch error. Reason: " + data.message + " Url: ./api.php");
                    return;
                }
                data.result.data.subFolderList.forEach(function(item, i) {
                    var decodedFolderName = decodeURIComponent(item);
                    if (that.path == null) that.path = item + '/';
                    // attr aim data as uriencoded path.
                    H(that.folderlist).append(
                        H("<a>").attr('aim', item).append(
                            H("<li>").text(decodedFolderName + '/').el
                        ).el
                    );
                });
                
                var nodeList = document.querySelectorAll('#folderlist a');
                for(var i = 0; i < nodeList.length; i++) {
                    var el = nodeList[i];
                    el.onclick = function() {
                        that.path = this.getAttribute('aim') + '/';
                        that.fetchData();
                    };
                }
                
                typeof callback === 'function' && callback();
            });
        },

        fetchData: function() {
            var that = this;
            
            fetch(this.apiUrl, {
                method: 'POST',
                body: new URLSearchParams({
                    'do': 'getfilelist',
                    'folder': that.path
                })
            }).then(TrickOrTreat).then((data) => {
                that.data = data.result.data.musicList;
                that.freshPlaylist();
                that.freshSubFolderList(data.result.data.subFolderList);
            });
        },

        freshPlaylist : function() {
            var that = this;
            var data = this.data;
            var songTitle = '';
            this.playlist.innerHTML = '';
            data.forEach(function(item, i) {
                songTitle = displayName(item);
                H(that.playlist).append(
                    H("<a>").attr('index', i).append(
                        H("<li>").text(songTitle).el
                    ).el
                );
            });
            // everytime after update playlist dom, do this.
            var nodeList = document.querySelectorAll('#playlist a');
            for(var i = 0; i < nodeList.length; i++) {
                var el = nodeList[i];
                el.onclick = function() {
                    that.playAtIndex(this.getAttribute('index'));
                };
            }
        },

        freshSubFolderList : function(list) {
            var that = this;
            H("subfolderlist").innerHTML("");
            list.forEach(function(item, i) {
                var decodedFolderName = decodeURIComponent(item);
                // attr aim data as uriencoded path.
                H("subfolderlist").append(
                    H("<a>").attr('aim', item).append(
                        H("<li>").text(decodedFolderName + '/').el
                    ).el
                );
            });
            var nodeList = document.querySelectorAll('#subfolderlist a');
            for(var i = 0; i < nodeList.length; i++) {
                var el = nodeList[i];
                el.onclick = function() {
                    that.path = this.getAttribute('aim') + '/';
                    that.fetchData();
                };
            }
        },

        urlMatch : function() {
            var isUrlMatched = false;
            // Match folder name and song title.
            var re = new RegExp("[#][/](.*[/])(.*.[a-zA-z0-9]{1,3})[/]");
            var urlMatch = re.exec(location.href);
            if (urlMatch != null) {
                isUrlMatched = true;
                this.path = urlMatch[1];
                this.audio.src = (this.path + urlMatch[2]);
                this.audio.play().catch((reason) => { console.log(reason); });
                H(this.nowPlaying).innerHTML(decodeURIComponent(urlMatch[2]));
            }
            // Only match folder name.
            if (!isUrlMatched) {
                re = new RegExp("[#][/](.*[/])");
                urlMatch = re.exec(location.href);
                if (urlMatch != null) {
                    isUrlMatched = true;
                    this.path = urlMatch[1];
                }
            }
        },

        applyLoop : function() {
            if (this.loop == 1) {
                this.audio.loop = true;
                H("btn-loop").innerHTML("Loop: √");
            } else {
                this.audio.loop = false;
                H("btn-loop").innerHTML("Loop: ×");
            }
        },

        applyOrder : function() {
            if (this.order == 1) {
                this.audio.onended = function() {
                    if (this.loop == 0) {
                        H("btn-next").click();
                    }
                };
                H("btn-order").innerHTML("Order: √");
            } else {
                this.audio.onended = undefined;
                H("btn-order").innerHTML("Order: ×");
            }
        },

        init : function() {
            var that = this;
            this.fetchServerInfo(function() {
                that.freshFolderlist(function() {
                    that.urlMatch();
                    that.fetchData();
                });
            });
            this.loop = getCookie("pcm-loop") == "1" ? 1 : 0;
            this.order = getCookie("pcm-order") == "1" ? 1 : 0;
            this.applyLoop();
            this.applyOrder();
        },

        ready : function() {
            var that = this;
            
            this.audio.ontimeupdate = () => {
                this.applyChapterData();
                H("curTime").innerHTML(formatTime(Player.audio.currentTime));
                H("totalTime").innerHTML(formatTime(Player.audio.duration));
                H("progress-bar").attr("value", Player.audio.currentTime / Player.audio.duration*100);
                var r = 0;
                for (var i=0; i<Player.audio.buffered.length; ++i)
                    r = r<Player.audio.buffered.end(i) ? Player.audio.buffered.end(i) : r;
                H("progress-bar").attr("buffer", r / Player.audio.duration*100);
            };
            
            this.audio.onpause = function() {
                H("btn-play").innerHTML("Play");
            }

            this.audio.onplay = function() {
                H("btn-play").innerHTML("Pause");
                that.updateMetadata();
            }

            H("progress-bar").click(function(e) {
                var sr=this.getBoundingClientRect();
                var p=(e.clientX-sr.left)/sr.width;
                that.audio.currentTime=that.audio.duration*p;
            });

            var nodeList = document.getElementsByTagName('button');
            for(var i = 0; i < nodeList.length; i++) {
                var el = nodeList[i];
                el.onclick = function() {
                    if(that.data[that.currentIndex]) H(that.nowPlaying).innerHTML(displayName(that.data[that.currentIndex]));
                };
            }

            H("btn-play").click(function() {
                if(that.audio.paused) {
                    that.audio.play();
                } else {
                    that.audio.pause();
                }
                if (that.currentIndex == -1 && that.audio.readyState == 0) {
                    H("btn-next").click();
                }
            });

            H("btn-next").click(function() {
                if (that.currentIndex == -1) {
                    that.playAtIndex(0);
                } else if (that.currentIndex == (that.data.length - 1)) {
                    that.playAtIndex(0);
                } else {
                    that.playAtIndex(Number(that.currentIndex) + 1);
                }
            });

            H("btn-prev").click(function() {
                if (that.currentIndex == -1) {
                    that.playAtIndex(0);
                } else if (that.currentIndex == 0) {
                    that.playAtIndex(that.data.length - 1);
                } else {
                    that.playAtIndex(Number(that.currentIndex) - 1);
                }
            });

            H("btn-loop").click(function() {
                that.loop = 1 - that.loop;
                that.applyLoop();
                setCookie("pcm-loop", that.loop, 157680000);
            });

            H("btn-order").click(function() {
                that.order = 1 - that.order;
                that.applyOrder();
                setCookie("pcm-order", that.order, 157680000);
            });
            
            if ('mediaSession' in navigator) {
                navigator.mediaSession.setActionHandler('previoustrack', function() { H("btn-prev").click(); });
                navigator.mediaSession.setActionHandler('nexttrack', function() { H("btn-next").click(); });
            }
        }
    };

    Player.init();
    Player.ready();
}());