; // Private Cloud Music - player.js ; // Licence: WTFPL ; // BLumia - 2016/11/11 ; // szO Chris && 2jjy && jxpxxzj Orz ; // ↑ Moe ↑ Moe ↑ Moe ; // Modified to use on chrisoft.org by Chris Xiong ; // szO BLumia Orz ; // ↑ 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; } const PCMAPI_URL='/libs/music/player.d/cgi-bin/pcm.cgi'; const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; (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(property, value) { if(this.el) this.el.setAttribute(property, 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); } var Player = { path: null, // sample: 'Test/' data: null, audio: document.getElementsByTagName('audio')[0], currentIndex: -1, loop: 0, order: 0, playlist: H("playlist").el, folderlist: H("folderlist").el, nowPlaying: H("nowPlaying").el, updateMetadata: function() { if ('mediaSession' in navigator) { window.navigator.mediaSession.metadata = new MediaMetadata({ title: nowPlaying.innerHTML, album: decodeURIComponent(this.path) }); } }, playAtIndex: function(i) { // FIXME: trigger this when audio doesn't finished load will cause play promise error. this.audio.pause(); this.currentIndex = i; this.audio.src = AUDIO_URL + this.data[i].fileName; this.audio.load(); this.audio.play(); window.history.replaceState("","Useless Title","#/"+this.path+this.data[i].fileName+"/"); // title seems be fucked. H(this.nowPlaying).innerHTML(decodeURIComponent(this.data[i].fileName)); }, freshFolderlist: function(callback) { var xhr = new XMLHttpRequest(); xhr.open("POST", PCMAPI_URL, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); var that = this; xhr.onreadystatechange = function () { if (xhr.readyState != 4 || xhr.status != 200) return; var data = JSON.parse(xhr.responseText); 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 ); }); }; xhr.onerror = function() { console.error("Ajax load folders failed. Status: " + xhr.status + " Url: ./api.php"); }; xhr.onloadend = function() { 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(); } xhr.send("do=getfilelist"); }, fetchData: function() { var that = this; var xhr = new XMLHttpRequest(); xhr.open("POST", PCMAPI_URL, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function () { if (xhr.readyState != 4 || xhr.status != 200) return; var data = JSON.parse(xhr.responseText); if (data.status != 200) { console.error("Fetch error. Reason: " + data.message + " Url: ./api.php"); return; } that.data = data.result.data.musicList; that.freshPlaylist(); that.freshSubFolderList(data.result.data.subFolderList); }; xhr.onerror = function() { console.error("Ajax load playlist failed. Status: " + xhr.status + " Url: ./api.php"); that.data = []; }; xhr.send("do=getfilelist&folder="+that.path); }, freshPlaylist : function() { var that = this; var data = this.data; var songTitle = ''; this.playlist.innerHTML = ''; data.forEach(function(item, i) { songTitle = decodeURIComponent(item.fileName); 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("[#][/](.*[/])(.*)[/]$"); var urlMatch = re.exec(location.href); if (urlMatch != null) { isUrlMatched = true; this.path = urlMatch[1]; this.audio.src = AUDIO_URL + urlMatch[2] + '.ogg'; this.audio.play(); 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.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 = function() { H("curTime").innerHTML(formatTime(Player.audio.currentTime)); H("totalTime").innerHTML(formatTime(Player.audio.duration)); H("timebar").css("width", Player.audio.currentTime / Player.audio.duration*100+"%"); var r = 0; for(var i=0; i