From f112c9977963edfca2ddba6de2ef83f0d8979cf4 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Sun, 5 Dec 2021 14:56:02 +0800 Subject: Roll Pineapple Cloud Music to c1dd09c. --- libs/music/player.d/pcm.js | 200 +++++++++++++++++++++++++++++++-------------- 1 file changed, 139 insertions(+), 61 deletions(-) (limited to 'libs/music/player.d/pcm.js') diff --git a/libs/music/player.d/pcm.js b/libs/music/player.d/pcm.js index 0efe849..6e2022c 100644 --- a/libs/music/player.d/pcm.js +++ b/libs/music/player.d/pcm.js @@ -1,11 +1,8 @@ -; // Private Cloud Music - player.js -; // Licence: WTFPL -; // BLumia - 2016/11/11 +; // SPDX-FileCopyrightText: 2021 Gary Wang +; // SPDX-License-Identifier: MIT ; // 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 '--:--'; @@ -24,9 +21,9 @@ function setCookie(cookieName, cookieValue, maxAge = 0) { 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 displayName(item) { + return item.displayName ? item.displayName : decodeURIComponent(item.fileName); +} (function() { var Helper = function() { @@ -49,8 +46,16 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; if(this.el) this.el.style.cssText += ';' + property + ":" + value; return this; }, - attr: function(property, value) { - if(this.el) this.el.setAttribute(property, value); + 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) { @@ -76,9 +81,17 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; 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, @@ -86,6 +99,14 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; 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) { @@ -96,25 +117,90 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; } }, + 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 = AUDIO_URL + this.data[i].fileName; + this.audio.src = srcUrl; 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)); + 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 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); + 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; @@ -129,11 +215,7 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; ).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]; @@ -142,41 +224,34 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; 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; - } + + 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); - }; - 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); + songTitle = displayName(item); H(that.playlist).append( H("").attr('index', i).append( H("
  • ").text(songTitle).el @@ -218,13 +293,13 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; urlMatch : function() { var isUrlMatched = false; // Match folder name and song title. - var re = new RegExp("[#][/](.*[/])(.*)[/]$"); + 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 = AUDIO_URL + urlMatch[2] + '.ogg'; - this.audio.play(); + 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. @@ -261,30 +336,33 @@ const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; H("btn-order").innerHTML("Order: ×"); } }, - + init : function() { var that = this; - this.freshFolderlist(function() { - that.urlMatch(); - that.fetchData(); + 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 = function() { + this.audio.ontimeupdate = () => { + this.applyChapterData(); 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+"%"); + H("progress-bar").attr("value", Player.audio.currentTime / Player.audio.duration*100); var r = 0; - for(var i=0; i