; // SPDX-FileCopyrightText: 2021 Gary Wang ; // 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("").attr('aim', item).append( H("
  • ").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("").attr('index', i).append( H("
  • ").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("").attr('aim', item).append( H("
  • ").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