diff --git a/src/frontend/assets/extensions/local.js b/src/frontend/assets/components/LocalFolderExtension.js
similarity index 97%
rename from src/frontend/assets/extensions/local.js
rename to src/frontend/assets/components/LocalFolderExtension.js
index ba24edd..5592cae 100644
--- a/src/frontend/assets/extensions/local.js
+++ b/src/frontend/assets/components/LocalFolderExtension.js
@@ -7,8 +7,6 @@
/**************** 基础配置 ****************/
-// ExtensionRuntime在加载时会自动添加json中的scheme字段到ExtensionConfig下,所以无需担心ExtensionConfig.xxx是否存在
-ExtensionConfig.file.uiName = "本地";
// 当没有config.setItem时,调用config.getItem会返回defaultConfig中的值
defaultConfig["folderLists"] = [];
@@ -209,5 +207,9 @@ ExtensionConfig.file.search = async keyword => {
if (songInfoString.includes(keyword)) resultArray.push(file);
}
});
- return {files: resultArray, menu: FileExtensionTools.fileMenuItem, hasMore: false};
+ return {
+ files: resultArray,
+ menu: FileExtensionTools.fileMenuItem,//.concat(DownloadController.getMenuItems()),
+ hasMore: false
+ };
}
\ No newline at end of file
diff --git a/src/frontend/assets/components/PublicConfig.js b/src/frontend/assets/components/PublicConfig.js
index 3d4e564..b2087cf 100644
--- a/src/frontend/assets/components/PublicConfig.js
+++ b/src/frontend/assets/components/PublicConfig.js
@@ -19,14 +19,16 @@ const defaultConfig = {
desktopLyricsProtection: true,
desktopLyricsAutoHide: true,
desktopLyricsColor: "#1E9FFF",
+ desktopLyricsStrokeEnabled: true,
desktopLyricsStroke: "#1672B8",
desktopLyricsSize: 30,
desktopLyricsWidth: 700,
desktopLyricsTop: screen.height - 300,
desktopLyricsLeft: screen.width / 2,
- extensions: ["assets/extensions/local.json"],
- extensionCache: {},
+ ext: {},
musicListSort: [1, 1],
+ parallelDownload: 3,
+ downloadFileName: "[title] - [artist]",
}
const configListeners = {};
diff --git a/src/frontend/assets/components/SimAP.css b/src/frontend/assets/components/SimAP.css
index 95c6b55..fce2602 100644
--- a/src/frontend/assets/components/SimAP.css
+++ b/src/frontend/assets/components/SimAP.css
@@ -27,7 +27,7 @@ body:not(.playing) #background>div{animation-play-state:paused;}
.controls .progressControl{display:flex;align-items:center;margin:10px 0;}
.controls .progressControl span{width:55px;font-size:.85em;opacity:.8;}
.controls .progressControl span:last-child{text-align:right;}
-.musicLoading #progressBar>div,.musicLoading #bottomProgressBar>div{animation:progressLoading 1s linear infinite;background-image:linear-gradient(-45deg,rgba(0,0,0,.1) 25%,transparent 0,transparent 50%,rgba(0,0,0,.1) 0,rgba(0,0,0,.1) 75%,transparent 0,transparent)!important;background-repeat:repeat-x;background-size:25px 20px;}
+.musicLoading #progressBar>div,.musicLoading #bottomProgressBar>div{animation:progressLoading 1s linear infinite;background-image:linear-gradient(-45deg,rgba(0,0,0,.05) 25%,transparent 0,transparent 50%,rgba(0,0,0,.05) 0,rgba(0,0,0,.05) 75%,transparent 0,transparent)!important;background-repeat:repeat-x;background-size:25px 20px;}
@keyframes progressLoading{to{background-position:25px 0;}}
/* 下方按钮 */
.controls .buttons,.bottom .center{align-items:center;margin-top:5px;display:flex;align-items:center;justify-content:center;}
@@ -56,7 +56,7 @@ body:not(.playing) #background>div{animation-play-state:paused;}
/* 3D特效 */
body:not(.hideLyrics.hideList) .playerContainer{transform:translateX(-25px);}
.threeEffect .controls{transform:perspective(900px) rotateY(10deg);}
-.threeEffect .lyrics,.list{transform:perspective(900px) rotateY(-12.5deg);}
+.threeEffect .lyrics,.threeEffect .list{transform:perspective(900px) rotateY(-12.5deg);}
/* 歌词区域 */
.lyrics{position:absolute;left:410px;top:0;width:calc(100% - 410px);font-size:var(--lrcSize);height:100%;transform-origin:left center;transition:all .3s;mask:linear-gradient(180deg,hsla(0,0%,100%,0),hsla(0,0%,100%,.6) 15%,#fff 25%,#fff 75%,hsla(0,0%,100%,.6) 85%,hsla(0,0%,100%,0));}
diff --git a/src/frontend/assets/components/SimAP.js b/src/frontend/assets/components/SimAP.js
index 284c8a4..a1c54aa 100644
--- a/src/frontend/assets/components/SimAP.js
+++ b/src/frontend/assets/components/SimAP.js
@@ -146,7 +146,11 @@ const SimAPControls = {
audio[audio.paused ? "play" : "pause"]();
SimAPControls.loadAudioState();
},
- prev() {this.switchIndex(-1);},
+ prev() {
+ const audio = document.getElementById("audio");
+ if (!config.getItem("fastPlayback") || audio.currentTime / audio.duration < .9) this.switchIndex(-1);
+ else audio.currentTime = 0;
+ },
next() {this.switchIndex(1);},
switchIndex(offset) {
const list = config.getItem("playList");
@@ -202,16 +206,14 @@ const SimAPControls = {
document.querySelector(".volBtnBottom i").innerHTML = icon;
},
toggleList(isShow = document.body.classList.contains("hideList")) {
- document.body.classList[isShow ? "add" : "remove"]("hideList");
document.body.classList[isShow ? "remove" : "add"]("hideList");
if (isShow) document.body.classList.add("hideLyrics");
PlayerController.loadMusicListActive();
},
- toggleLyrics() {
- document.body.classList[document.body.classList.contains("hideLyrics") ? "add" : "remove"]("hideLyrics");
- document.body.classList[document.body.classList.contains("hideLyrics") ? "remove" : "add"]("hideLyrics");
- if (!document.body.classList.contains("hideLyrics")) document.body.classList.add("hideList");
- config.setItem("lrcShow", !document.body.classList.contains("hideLyrics"));
+ toggleLyrics(isShow = document.body.classList.contains("hideLyrics")) {
+ document.body.classList[isShow ? "remove" : "add"]("hideLyrics");
+ if (isShow) document.body.classList.add("hideList");
+ config.setItem("lrcShow", isShow);
},
loadConfig() {
document.querySelector(".SimLRC").style.setProperty("--lineSpace", config.getItem("lyricSpace") + "em");
diff --git a/src/frontend/assets/components/SimLRC.js b/src/frontend/assets/components/SimLRC.js
index 1e6c2f1..ae1283a 100644
--- a/src/frontend/assets/components/SimLRC.js
+++ b/src/frontend/assets/components/SimLRC.js
@@ -78,7 +78,7 @@ class SimLRC {
container.style.setProperty("--lineSpace", options.lineSpace + "em");
// 监听事件
const refreshLrcProgress = forceScroll => {
- const currentTime = audio.currentTime * 1000;
+ const currentTime = Math.round(audio.currentTime * 1000);
let lrcEles = Array.from(container.getElementsByTagName("div"));
for (let index in lrcEles) {
let div = lrcEles[index];
diff --git a/src/frontend/assets/components/require.js b/src/frontend/assets/components/require.js
index 43e128a..53f7f8e 100644
--- a/src/frontend/assets/components/require.js
+++ b/src/frontend/assets/components/require.js
@@ -2,4 +2,5 @@ const {ipcRenderer, shell} = require("electron");
const fs = require("fs");
const path = require("path");
const musicMetadata = require("music-metadata");
-const nodeId3 = require("node-id3");
\ No newline at end of file
+const nodeId3 = require("node-id3");
+const fflate = require("fflate");
\ No newline at end of file
diff --git a/src/frontend/assets/extensions/local.json b/src/frontend/assets/extensions/local.json
deleted file mode 100644
index aa88f35..0000000
--- a/src/frontend/assets/extensions/local.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "本地歌曲",
- "url": "assets/extensions/local.js",
- "isDev": true,
- "scheme": "file",
- "version": "1.0.4"
-}
\ No newline at end of file
diff --git a/src/frontend/assets/main.css b/src/frontend/assets/main.css
index 8165a02..0f41ede 100644
--- a/src/frontend/assets/main.css
+++ b/src/frontend/assets/main.css
@@ -25,7 +25,11 @@ input,select{background:rgba(0,0,0,.03);padding:8px 10px;border-radius:5px;borde
-.left .leftBar div,.right .musicListTitle section b,.right .musicListTitle section .details,.bottom .info .musicInfoBottom,.controls .musicInfo,.list>div>div>*{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
+.left .leftBar div,.right .musicListTitle section b,
+.right .musicListTitle section .details,
+.bottom .info .musicInfoBottom,
+.controls .musicInfo,.list>div>div>*,
+.right #downloadContainer>div>.info>.music>b{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
/* 主窗体 - 加载动画 */
@@ -39,12 +43,13 @@ header{position:fixed;top:0;height:35px;display:flex;justify-content:right;width
header i{font-family:"windowsicon";height:100%;width:40px;display:flex;align-items:center;justify-content:center;transition:background .2s,opacity .2s,color .2s;-webkit-app-region:no-drag;}
header i#lyricsBtn{font-size:.95em;padding-bottom:1px;font-family:"微软雅黑"!important;}
header i#miniBtn{transform:scaleY(-1);font-size:.95em;}
+header i#devBtn{font-size:.95em;}
header i:hover{background:rgba(0,0,0,.05);}
header i:active{background:rgba(0,0,0,.1);opacity:.8;}
header i#closeBtn:hover{background:#E81123;color:white;}
header i#closeBtn:active{background:#E81123;color:rgba(252,252,252,.8);}
header i.active{color:#1E9FFF;}
-.playerShown #miniBtn,.playerShown #desktopBtn,.playerShown #lyricsBtn{opacity:0;pointer-events:none;}
+.playerShown #miniBtn,.playerShown #lyricsBtn,.playerShown #devBtn{opacity:0;pointer-events:none;}
header #hidePlayerBtn{opacity:0;position:absolute;right:120px;pointer-events:none;}
.playerShown header #hidePlayerBtn{opacity:1;pointer-events:all;}
@@ -113,13 +118,33 @@ header #hidePlayerBtn{opacity:0;position:absolute;right:120px;pointer-events:non
.right .searchTitle .inputGroup input{height:37px;margin-right:10px;max-width:500px;width:100%;border-radius:0 5px 5px 0;padding:0 10px;}
.right .searchTitle .inputGroup button{height:37px;font-size:1.05em;}
.right #searchBottomIndicator{text-align:center;opacity:.8;margin-top:50px;margin:30px 0 10px 0;font-size:.9em;}
+/* 下载 */
+.right #downloadContainer{padding-top:90px!important;}
+.right #downloadContainer>div{background:white;width:100%;border-radius:5px;margin-bottom:5px;padding:10px 15px;position:relative;height:65px;overflow:hidden;transition:background .2s;}
+.right #downloadContainer>div[data-status="success"]{background:#EFF6EF;}
+.right #downloadContainer>div[data-status="error"]{background:#FCEFEF;}
+.right #downloadContainer>div>.info{display:flex;align-items:center;white-space:nowrap;position:absolute;inset:0;z-index:5;padding:0 15px;}
+.right #downloadContainer>div>.info>.music{display:flex;flex-direction:column;width:calc(100% - 150px);}
+.right #downloadContainer>div>.info>.music>span{display:block;opacity:.8;font-size:.9em;margin-top:3px;}
+.right #downloadContainer>div[data-status="pending"]>.info>.music>span>i::after{content:"\F337";}
+.right #downloadContainer>div[data-status="download"]>.info>.music>span>i::after{content:"\EC5A";}
+.right #downloadContainer>div[data-status="success"]>.info>.music>span>i::after{content:"\EB7B";}
+.right #downloadContainer>div[data-status="error"]>.info>.music>span>i::after{content:"\F4C8";}
+.right #downloadContainer>div>.info>.buttons{display:flex;justify-content:flex-end;width:150px;}
+.right #downloadContainer>div>.info>.buttons>i{width:30px;height:30px;display:flex;align-items:center;justify-content:center;font-size:1.1em;border-radius:50%;transition:background .2s;}
+.right #downloadContainer>div>.info>.buttons>i:hover{background:rgba(0,0,0,.05);}
+.right #downloadContainer>div>.info>.buttons>i:active{background:rgba(0,0,0,.1);}
+.right #downloadContainer>div:not([data-status="error"])>.info>.buttons>i.errorOnly{display:none;}
+.right #downloadContainer>div:not([data-status="success"])>.info>.buttons>i.successOnly{display:none;}
+.right #downloadContainer>div>.progressBar{transition:width .2s,opacity .2s;position:absolute;inset:0;right:unset;background:#DAEFFF;width:var(--progressWidth);}
+.right #downloadContainer>div:not([data-status="download"])>.progressBar{opacity:0;}
/* 扩展 */
-.right #extensionPage #extensionContainer{padding-top:90px!important;}
+.right #extensionContainer{padding-top:90px!important;}
/* 设置 */
.right .page .header{padding:30px 30px 10px 30px;font-size:1.5em;border-bottom:1px solid rgba(0,0,0,.05);position:absolute;background:rgba(252,252,252,.9);backdrop-filter:blur(20px);width:100%;z-index:2;display:flex;align-items:center;}
.right .page .header i{margin-right:5px;}
-.right .page .header button{margin-left:15px;}
-.right #settingsPage #settingsContainer, .right #extensionPage #extensionContainer{position:absolute;z-index:1;padding:70px 27.5px 100px 27.5px;width:100%;height:100%;overflow-y:scroll;}
+.right .page .header small{font-size:.6em;margin-left:15px;opacity:.8;}
+.right #settingsPage #settingsContainer, .right #extensionContainer, .right #downloadContainer{position:absolute;z-index:1;padding:70px 27.5px 100px 27.5px;width:100%;height:100%;overflow-y:scroll;}
.right #settingsPage #settingsContainer .title{font-weight:bold;margin:20px 2.5px 5px 2.5px;}
.right #settingsPage #settingsContainer .block, .right #extensionPage #extensionContainer>div{background:white;width:100%;border-radius:5px;margin-bottom:5px;padding:10px 15px;display:flex;align-items:center;}
.right #settingsPage #settingsContainer .block section, .right #extensionPage #extensionContainer>div section{width:100%;margin-right:10px;}
@@ -192,4 +217,19 @@ header #hidePlayerBtn{opacity:0;position:absolute;right:120px;pointer-events:non
/* 主窗体 - 播放内页 */
#playPage{position:fixed;top:120vh;left:0;width:100%;height:100%;background:white;transition:top .3s;overflow:hidden;}
-.playerShown #playPage{top:0;}
\ No newline at end of file
+.playerShown #playPage{top:0;}
+
+
+/* 主窗体 - 迷你模式 */
+header{-webkit-app-region:no-drag;}
+.miniMode .bottom{z-index:300;height:60px;background:white;}
+.miniMode .bottom .progressBefore,.miniMode .bottom .progressAfter{display:none;}
+.miniMode .bottom #bottomProgressBar{bottom:0;top:unset;width:100%;left:0;}
+.miniMode .bottom #bottomProgressBar::after{display:none;}
+.miniMode .bottom .info{right:0;zoom:.72;top:0px;width:230px;bottom:10px;-webkit-app-region:drag;}
+.miniMode .bottom .info .musicInfoBottom{font-size:1.1em;max-width:160px;}
+.miniMode .bottom .info .musicInfoBottom div{margin-top:-2px;}
+.miniMode .bottom .center{right:15px;zoom:.7;bottom:10px;width:220px;left:unset;}
+.miniMode .bottom .center .bottomListBtn{display:none;}
+body:not(.miniMode) .bottom .center .miniModeBtn{display:none;}
+.miniMode .bottom .volume{display:none;}
\ No newline at end of file
diff --git a/src/frontend/assets/main.js b/src/frontend/assets/main.js
index 9a9a0dc..260ded1 100644
--- a/src/frontend/assets/main.js
+++ b/src/frontend/assets/main.js
@@ -1,6 +1,4 @@
-
-
-SimMusicVersion = "0.0.4-beta";
+SimMusicVersion = "0.1.0";
// 窗口处理
@@ -30,6 +28,12 @@ const WindowOps = {
document.getElementById("lyricsBtn").classList[lyricsShow ? "add" : "remove"]("active");
});
},
+ toggleMini () {
+ ipcRenderer.invoke("toggleMini")
+ .then(isMini => {
+ document.body.classList[isMini ? "add" : "remove"]("miniMode");
+ });
+ },
};
document.body.onresize = () => {
ipcRenderer.invoke("winOps", [document.documentElement.dataset.windowId, "isMaximized"])
@@ -44,7 +48,6 @@ document.documentElement.onkeydown = e => {
if (document.activeElement.tagName.toLowerCase() == "input") return;
e.preventDefault();
if (document.activeElement.tagName.toLowerCase() == "input") return;
- const audio = document.getElementById("audio");
switch (e.key.toLowerCase()) {
case "w": config.setItem("desktopLyricsTop", Math.max(config.getItem("desktopLyricsTop") - 2, 0)); break;
case "a": config.setItem("desktopLyricsLeft", Math.max(config.getItem("desktopLyricsLeft") - 2, 0)); break;
@@ -110,6 +113,9 @@ const SimMusicTools = {
getCoverUrl(arrayOrUrl) {
if (typeof(arrayOrUrl) == "object") return URL.createObjectURL(new Blob([arrayOrUrl]));
return arrayOrUrl;
+ },
+ getWindowsLegalName(original) {
+ return original.replaceAll("\\", "_").replaceAll("/", "_").replaceAll(":", ":").replaceAll("*", "×").replaceAll("?", "?").replaceAll('"', "'").replaceAll("<", "《").replaceAll(">", "》").replaceAll("|", "丨").replaceAll("\n", "");
}
};
SimMusicTools.initMusicIndex();
@@ -118,102 +124,116 @@ SimMusicTools.initMusicIndex();
// 扩展运行环境
const ExtensionConfig = {};
const ExtensionRuntime = {
- count: 0,
- init() {
- config.getItem("extensions").forEach((json, index) => {
- fetch(json)
- .then(res => res.json())
- .then(data => {
- this.renderExtension(json, data, index);
- const cacheData = config.getItem("extensionCache");
- if (cacheData[json] && cacheData[json].version == data.version && !data.isDev) this.runExtension(cacheData[json].code, data);
- else fetch((data.url + "?v=" +data.version))
- .then(res => res.text())
- .then(code => {
- cacheData[json] = { version: data.version, code: code };
- config.setItem("extensionCache", cacheData);
- this.runExtension(code, data);
- });
- })
- .catch(() => {
- this.renderExtension(json, {}, index, true);
- })
- });
- },
- renderExtension(jsonUrl, data, index, isErr) {
- if (isErr) { this.count++; this.checkMusicInit(); }
- const extensionContainer = document.getElementById("extensionContainer");
- extensionContainer.innerHTML += `
-
+ async init() {
+ const extData = await this.getExtData();
+ for (const packageId in extData) {
+ let extError;
+ try {
+ ExtensionConfig[packageId] = {};
+ Function(extData[packageId].code)();
+ if (ExtensionConfig[packageId].musicList) {
+ const span = document.createElement("span");
+ span.innerHTML = `
+
${extData[packageId].uiName}
+
`;
+ if (ExtensionConfig[packageId].musicList.add) {
+ span.querySelector("i").onclick = () => {
+ ExtensionConfig[packageId].musicList.add(() => {
+ span.querySelector(".lists").innerHTML = "";
+ ExtensionConfig[packageId].musicList.renderList(span.querySelector(".lists"));
+ });
+ }
+ } else {
+ span.querySelector("i").remove();
+ }
+ ExtensionConfig[packageId].musicList.renderList(span.querySelector(".lists"));
+ document.getElementById("extBars").appendChild(span);
+ }
+ } catch (err) {
+ extError = err;
+ }
+ const div = document.createElement("div");
+ div.innerHTML = `
- ${data.name ?? "扩展加载失败"}
- 加载来源:${SimMusicTools.escapeHtml(jsonUrl) == "assets/extensions/local.json" ? "SimMusic 内置组件" : SimMusicTools.escapeHtml(jsonUrl)}
+ ${SimMusicTools.escapeHtml(extData[packageId].extName)}
+
+ 扩展包名: ${SimMusicTools.escapeHtml(packageId)}
+ 扩展版本: ${SimMusicTools.escapeHtml(extData[packageId].version)}
+ ${extError ? ` ${extError}` : ""}
+
-
-
`;
- },
- runExtension(code, metadata) {
- if (metadata.scheme) {
- const scheme = metadata.scheme;
- ExtensionConfig[scheme] = {};
- Function(code)();
- if (ExtensionConfig[scheme].musicList) {
- const span = document.createElement("span");
- span.innerHTML = `
- ${ExtensionConfig[scheme].uiName}
- `;
- if (ExtensionConfig[scheme].musicList.add) {
- span.querySelector("i").onclick = () => {
- ExtensionConfig[scheme].musicList.add(() => {
- span.querySelector(".lists").innerHTML = "";
- ExtensionConfig[scheme].musicList.renderList(span.querySelector(".lists"));
- });
- }
- } else {
- span.querySelector("i").remove();
- }
- ExtensionConfig[scheme].musicList.renderList(span.querySelector(".lists"));
- document.getElementById("extBars").appendChild(span);
- }
+ `;
+ div.querySelector("button").onclick = () => {
+ this.uninstall(packageId);
+ };
+ document.getElementById("extensionContainer").appendChild(div);
}
- else Function(code)();
- this.count++;
- this.checkMusicInit();
- },
- install() {
- prompt("请输入扩展索引文件 URL ...", url => {
- if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file:")) return alert("索引文件 URL 无效。");
- confirm("请务必确保扩展来源可信,由攻击者开发的扩展将可能会恶意操作您的文件、控制您的设备。按「确认」以继续。", () => {
- const extensions = config.getItem("extensions");
- extensions.push(url);
- config.setItem("extensions", extensions);
- alert("扩展已添加,按「确认」重载此应用生效。", () => {ipcRenderer.invoke("restart");});
- });
+
+ SimMusicTools.readMusicIndex(index => {
+ lastMusicIndex = index;
+ if (config.getItem("currentMusic") && lastMusicIndex[config.getItem("currentMusic")]) {
+ PlayerController.switchMusicWithList(config.getItem("currentMusic"), config.getItem("playList"), false, true);
+ } else {
+ config.setItem("currentMusic", "");
+ }
+ setTimeout(() => {
+ document.body.classList.remove("appLoading");
+ }, 500);
});
},
- uninstall(index) {
- if (!index) return alert("内置扩展无法进行卸载。");
- confirm("确实要卸载此扩展吗?由此扩展提供的音乐将在移除后无法播放,请慎重选择。", () => {
- const extensions = config.getItem("extensions");
- extensions.splice(index, 1);
- config.setItem("extensions", extensions);
- alert("扩展已卸载,按「确认」重载此应用生效。", () => {ipcRenderer.invoke("restart");});
+ async install(file) {
+ confirm("请确保扩展包来源可信,由攻击者提供的扩展可能会对您的系统执行恶意操作。", async () => {
+ try {
+ if (file.size > 256 * 1024) return alert("扩展包文件过大,请确保扩展包内仅包含必要的代码文件。");
+ const arrBuffer = await file.arrayBuffer();
+ const unzipped = fflate.unzipSync(new Uint8Array(arrBuffer));
+ const manifestContent = fflate.strFromU8(unzipped["manifest.json"]);
+ const manifest = JSON.parse(manifestContent);
+ if (!manifest.packageId || !manifest.version || !manifest.extName) return alert("扩展清单字段数据不完整。");
+ const packageId = manifest.packageId;
+ let code = "";
+ for (const i in manifest.entries) {
+ const filename = manifest.entries[i];
+ const jsCode = fflate.strFromU8(unzipped[filename]);
+ code += jsCode + "\n";
+ }
+ console.log(code)
+ const extData = config.getItem("ext");
+ extData[packageId] = {
+ version: manifest.version,
+ extName: manifest.extName,
+ uiName: manifest.uiName ?? manifest.extName,
+ code: code,
+ };
+ config.setItem("ext", extData);
+ alert("扩展已成功安装,按「确定」重载此应用生效。", () => {
+ ipcRenderer.invoke("restart");
+ });
+ } catch (err) {
+ console.warn(err);
+ alert("扩展包已损坏、无法读取或存在未知错误。");
+ }
});
},
- checkMusicInit() {
- if (this.count == config.getItem("extensions").length) {
- SimMusicTools.readMusicIndex(index => {
- lastMusicIndex = index;
- if (config.getItem("currentMusic") && lastMusicIndex[config.getItem("currentMusic")]) {
- PlayerController.switchMusicWithList(config.getItem("currentMusic"), config.getItem("playList"), false, true);
- } else {
- config.setItem("currentMusic", "");
- }
+ uninstall(packageId) {
+ confirm("确实要卸载此扩展吗?卸载后由此扩展提供的曲目将无法正常播放,请慎重。", () => {
+ const extData = config.getItem("ext");
+ delete extData[packageId];
+ config.setItem("ext", extData);
+ alert("扩展已成功卸载,按「确定」重载此应用生效。", () => {
+ ipcRenderer.invoke("restart");
});
- setTimeout(() => {
- document.body.classList.remove("appLoading");
- }, 1000);
+ });
+ },
+ async getExtData() {
+ const extData = config.getItem("ext");
+ extData["file"] = {
+ code: await (await fetch("assets/components/LocalFolderExtension.js")).text(),
+ uiName: "本地",
+ extName: "本地文件播放支持",
+ version: "1.0.0",
}
+ return extData;
}
};
ExtensionRuntime.init();
@@ -342,34 +362,47 @@ MusicList.renderList();
-// 歌曲拖放
+// 歌曲&扩展包拖放
document.documentElement.ondragover = e => {
e.preventDefault();
- if (document.getElementById("musicListContainer").hidden || !document.querySelector("#musicListContainer>.show") || !document.querySelector("#musicListContainer>.show").dataset.musicListId.startsWith("musiclist-")) return;
+ if ((document.getElementById("extensionPage").hidden) &&
+ (document.getElementById("musicListContainer").hidden ||
+ !document.querySelector("#musicListContainer>.show") ||
+ !document.querySelector("#musicListContainer>.show").dataset.musicListId.startsWith("musiclist-"))
+ ) return;
if (e.dataTransfer.types.includes("Files")) {
document.body.classList.add("dragOver");
document.getElementById("dropTip").style.left = e.clientX + 10 > document.documentElement.clientWidth - 160 ? document.documentElement.clientWidth - 165 : e.clientX + 10 + "px";
document.getElementById("dropTip").style.top = e.clientY + 30 + "px";
+ document.getElementById("dropTipText").textContent = document.getElementById("musicListContainer").hidden ? "松手安装扩展" : "松手加入当前歌单";
}
};
document.documentElement.ondrop = e => {
e.preventDefault();
- if (!document.querySelector("#musicListContainer>.show") || document.getElementById("musicListContainer").hidden) return;
- const currentMusicList = document.querySelector("#musicListContainer>.show").dataset.musicListId;
- if (!currentMusicList.startsWith("musiclist-")) return;
+ if ((document.getElementById("extensionPage").hidden) &&
+ (document.getElementById("musicListContainer").hidden ||
+ !document.querySelector("#musicListContainer>.show") ||
+ !document.querySelector("#musicListContainer>.show").dataset.musicListId.startsWith("musiclist-"))
+ ) return;
document.body.classList.remove("dragOver");
if (e.dataTransfer.types.includes("Files")) {
- let files = [];
- const supportedExtensions = config.getItem("musicFormats").split(" ");
- for (let i = 0; i < e.dataTransfer.files.length; i++){
- const file = e.dataTransfer.files[i];
- const fullPath = file.path;
- const ext = path.extname(fullPath).toLowerCase();
- if (supportedExtensions.includes(ext)) files.push("file:" + fullPath);
+ if (!document.getElementById("musicListContainer").hidden) {
+ const currentMusicList = document.querySelector("#musicListContainer>.show").dataset.musicListId;
+ let files = [];
+ const supportedExtensions = config.getItem("musicFormats").split(" ");
+ for (let i = 0; i < e.dataTransfer.files.length; i++){
+ const file = e.dataTransfer.files[i];
+ const fullPath = file.path;
+ const ext = path.extname(fullPath).toLowerCase();
+ if (supportedExtensions.includes(ext)) files.push("file:" + fullPath);
+ }
+ const name = currentMusicList.substring(10);
+ MusicList.importToMusicList(name, files);
+ MusicList.switchList(name, true);
+ } else {
+ const file = e.dataTransfer.files[0];
+ ExtensionRuntime.install(file);
}
- const name = currentMusicList.substring(10);
- MusicList.importToMusicList(name, files);
- MusicList.switchList(name, true);
}
};
document.documentElement.ondragleave = () => {
@@ -380,16 +413,25 @@ document.documentElement.ondragleave = () => {
// 音乐搜索
Search = {
- switchSearch() {
+ async switchSearch() {
if (document.getElementById("searchBtn").classList.contains("active")) return;
document.getElementById("searchSubmitBtn").disabled = false;
document.querySelectorAll(".left .leftBar div").forEach(ele => ele.classList.remove("active"));
document.getElementById("searchBtn").classList.add("active");
const searchSource = document.getElementById("searchSource");
if (!searchSource.innerHTML) {
+ const extData = await ExtensionRuntime.getExtData();
for (const name in ExtensionConfig) {
if (ExtensionConfig[name].search) {
- searchSource.innerHTML += ``;
+ searchSource.innerHTML += ``;
+ }
+ }
+ document.getElementById("searchInput").onkeydown = e => {
+ if (e.key === "Tab") {
+ var currentOption = searchSource.options[searchSource.selectedIndex];
+ var nextOption = currentOption.nextElementSibling;
+ if (nextOption) searchSource.selectedIndex = searchSource.selectedIndex + 1;
+ else searchSource.selectedIndex = 0;
}
}
}
@@ -411,6 +453,7 @@ Search = {
if (!keyword) return alert("请输入搜索关键字。");
if (keyword == "OPENDEVTOOLS") {
ipcRenderer.invoke("openDevtools");
+ config.setItem("devMode", 1);
return document.getElementById("searchInput").value = "";
}
const btn = document.getElementById("searchSubmitBtn");
@@ -439,13 +482,14 @@ Search = {
if (this.hasMore) {
const searchBottomIndicator = document.getElementById("searchBottomIndicator");
if (searchBottomIndicator.dataset.status == "loading") return;
+ document.getElementById("searchBottomIndicator").style = "";
searchBottomIndicator.dataset.status = "loading";
searchBottomIndicator.textContent = "正在加载更多...";
const currentKeyword = this.currentSearchKeyword;
setTimeout(() => {
ExtensionConfig[this.currentSearchExt].search(currentKeyword, this.searchPage + 1)
.then((data) => {
- if (this.currentSearchKeyword != currentKeyword) return searchBottomIndicator.textContent = "暂无更多搜索结果";
+ if (this.currentSearchKeyword != currentKeyword) return;
searchBottomIndicator.dataset.status = "";
renderMusicList(getCurrentMusicList().concat(data.files ?? []), "search", false, true, "暂无搜索结果", data.menu ?? [], {}, true, this.loadIndicatorStatus);
this.searchPage++;
@@ -529,7 +573,7 @@ function renderMusicList(files, uniqueId, isFinalRender, dontRenderBeforeLoaded,
// 处理首次渲染与二次渲染
if (!isFinalRender) {
// 切换页面
- switchRightPage("musicListContainer");
+ if (uniqueId != "search") switchRightPage("musicListContainer");
unselectAll();
document.getElementById("searchSubmitBtn").disabled = false;
if (uniqueId != "search") {
@@ -544,15 +588,17 @@ function renderMusicList(files, uniqueId, isFinalRender, dontRenderBeforeLoaded,
// 清空容器,搜索除外
musicListContent.innerHTML = "";
}
- // 处理一些其他元素
- containerElement.querySelector(".musicListErrorOverlay").hidden = true;
}
+ // 处理一些其他元素
+ if (isFinalRender || !dontRenderBeforeLoaded) containerElement.querySelector(".musicListErrorOverlay").hidden = true;
// 读取索引并渲染列表
SimMusicTools.readMusicIndex(musicIndex => {
const renderObject = [];
lastMusicIndex = musicIndex;
if (!isFinalRender) {
- updateMusicIndex(files, uniqueId, errorText, menuItems, finishCallback);
+ updateMusicIndex(files, () => {
+ renderMusicList(files, uniqueId, true, dontRenderBeforeLoaded, errorText, menuItems, musicListInfo, force, finishCallback);
+ });
if (dontRenderBeforeLoaded) return;
} else {
if (finishCallback) finishCallback();
@@ -576,33 +622,35 @@ function renderMusicList(files, uniqueId, isFinalRender, dontRenderBeforeLoaded,
${SimMusicTools.escapeHtml(music[1].album ?? SimMusicTools.getDefaultAlbum(music[0]))} |
${SimMusicTools.formatTime(music[1].time)} | `;
// 绑定点击事件
- tr.oncontextmenu = e => {
- if (!tr.classList.contains("selected")) tr.click();
- handleMusicContextmenu(e, menuItems);
- };
- tr.onclick = e => {
- e.stopPropagation();
- const allTrs = Array.from(musicListContent.querySelectorAll("tr"));
- const lastSelectedElement = musicListContent.querySelector("tr.selected");
- if (e.ctrlKey) {
- if (tr.classList.contains("selected")) tr.classList.remove("selected");
- else tr.classList.add("selected");
- } else if (e.shiftKey && lastSelectedElement) {
- const indexLastSelected = allTrs.indexOf(lastSelectedElement);
- const indexCurrentSelected = allTrs.indexOf(tr);
- var start = Math.min(indexLastSelected, indexCurrentSelected);
- var end = Math.max(indexLastSelected, indexCurrentSelected);
- const selectedTrs = allTrs.slice(start, end + 1);
- selectedTrs.forEach(tr => tr.classList.add("selected"));
- } else {
- allTrs.forEach(tr => tr.classList.remove("selected"));
- tr.classList.add("selected");
+ if (isFinalRender) {
+ tr.oncontextmenu = e => {
+ if (!tr.classList.contains("selected")) tr.click();
+ handleMusicContextmenu(e, menuItems);
+ };
+ tr.onclick = e => {
+ e.stopPropagation();
+ const allTrs = Array.from(musicListContent.querySelectorAll("tr"));
+ const lastSelectedElement = musicListContent.querySelector("tr.selected");
+ if (e.ctrlKey) {
+ if (tr.classList.contains("selected")) tr.classList.remove("selected");
+ else tr.classList.add("selected");
+ } else if (e.shiftKey && lastSelectedElement) {
+ const indexLastSelected = allTrs.indexOf(lastSelectedElement);
+ const indexCurrentSelected = allTrs.indexOf(tr);
+ var start = Math.min(indexLastSelected, indexCurrentSelected);
+ var end = Math.max(indexLastSelected, indexCurrentSelected);
+ const selectedTrs = allTrs.slice(start, end + 1);
+ selectedTrs.forEach(tr => tr.classList.add("selected"));
+ } else {
+ allTrs.forEach(tr => tr.classList.remove("selected"));
+ tr.classList.add("selected");
+ }
}
+ tr.ondblclick = () => {
+ tr.classList.remove("selected");
+ PlayerController.switchMusicWithList(music[0], getCurrentMusicList());
+ };
}
- tr.ondblclick = () => {
- tr.classList.remove("selected");
- PlayerController.switchMusicWithList(music[0], getCurrentMusicList());
- };
// 统计音乐时间
if (music[1].time) totalTime += music[1].time;
// 加入列表
@@ -653,14 +701,14 @@ function loadMusicListSort() {
}
reloadMusicListCover();
}
-function updateMusicIndex(allFiles, uniqueId, errorText, menuItems, finishCallback) {
+function updateMusicIndex(allFiles, callback) {
const existedFiles = Object.keys(lastMusicIndex);
const files = allFiles.filter(file => !existedFiles.includes(file));
let finished = -1;
const record = () => {
finished ++;
- if (!files.length) renderMusicList(allFiles, uniqueId, true, false, errorText, menuItems, undefined, undefined, finishCallback);
- else if (finished == files.length) SimMusicTools.writeMusicIndex(lastMusicIndex, () => {renderMusicList(allFiles, uniqueId, true);});
+ if (!files.length) callback();
+ else if (finished == files.length) SimMusicTools.writeMusicIndex(lastMusicIndex, () => {callback();});
}
files.forEach(file => {
const updateMusicIndex = (data) => {
@@ -765,7 +813,9 @@ const PlayerController = {
const fileScheme = file.split(":")[0];
if (!ExtensionConfig[fileScheme] || !ExtensionConfig[fileScheme].player || !ExtensionConfig[fileScheme].player.getPlayUrl || !ExtensionConfig[fileScheme].player.getLyrics) {
shell.beep();
- return alert("播放此曲目所需的扩展程序已损坏或被删除。");
+ return confirm("播放此曲目所需的扩展程序已损坏或被删除,是否将其从播放列表中移除?", () => {
+ PlayerController.deleteFromList(config.getItem("currentMusic"));
+ });
}
// 这里是为了防止音频timeupdate事件撅飞加载动画
setTimeout(async () => {
@@ -860,6 +910,11 @@ const PlayerController = {
config.setItem("currentMusic", "");
SimAPUI.hide();
document.body.classList.remove("withCurrentMusic");
+ document.getElementById("audio").remove();
+ const audio = document.createElement("audio");
+ audio.id = "audio";
+ audio.volume = config.getItem("volume");
+ document.body.appendChild(audio);
} else {
SimAPControls.next();
}
@@ -887,8 +942,182 @@ const loadPlayColor = () => { document.body.classList[config.getItem("playBtnCol
config.listenChange("playBtnColor", loadPlayColor); loadPlayColor();
const load3dEffect = () => { document.body.classList[config.getItem("3dEffect") ? "add" : "remove"]("threeEffect"); }
config.listenChange("3dEffect", load3dEffect); load3dEffect();
+if (config.getItem("devMode", 1)) document.getElementById("devBtn").hidden = false;
+
+// 歌曲下载
+const DownloadController = {
+ getMenuItems() {
+ const array = [];
+ const lists = config.getItem("folderLists");
+ lists.forEach(name => array.push({
+ label: name,
+ click: () => {DownloadController.addTask(name);}
+ }));
+ if (lists.length) array.push(({ type: "separator" }));
+ array.push(({
+ label: Object.keys(lists).length ? "选择其他目录" : "选择目录",
+ click: () => { ipcRenderer.invoke("pickFolder").then(dir => { DownloadController.addTask(dir); }); }
+ }));
+ return {type: ["single", "multiple"], content: { label: "下载到本地", submenu: array },};
+ },
+ addTask(destination) {
+ const files = getCurrentSelected();
+ updateMusicIndex(files, () => {
+ const downloadContainer = document.getElementById("downloadContainer");
+ files.forEach(file => {
+ if (lastMusicIndex[file]) {
+ const div = document.createElement("div");
+ div.dataset.file = file;
+ div.dataset.destination = destination;
+ div.dataset.status = "pending";
+ div.innerHTML = `
+
+
+
+ ${lastMusicIndex[file].title} - ${lastMusicIndex[file].artist}
+ 正在等待下载
+
+
+
+
+
+
+
+
+
`;
+ div.querySelector(".buttons i:nth-child(1)").onclick = () => { // 重试
+ div.dataset.status = "pending";
+ div.querySelector(".progressText").textContent = "正在等待下载";
+ div.style.setProperty("--progressWidth", "0%");
+ DownloadController.loadDownloadStatus();
+ }
+ div.querySelector(".buttons i:nth-child(2)").onclick = () => { // 播放
+ const smFile = "file:" + div.dataset.fileName;
+ updateMusicIndex([smFile], () => {
+ PlayerController.switchMusicWithList(smFile, [smFile]);
+ });
+ }
+ div.querySelector(".buttons i:nth-child(3)").onclick = () => { // 资源管理器
+ shell.showItemInFolder(div.dataset.fileName);
+ }
+ div.querySelector(".buttons i:nth-child(4)").onclick = () => { // 删除文件
+ confirm("确实要删除此任务与本地已下载的文件吗?", () => {
+ fs.unlinkSync(div.dataset.fileName);
+ PlayerController.deleteFromList("file:" + div.dataset.fileName);
+ div.remove();
+ })
+ }
+ div.querySelector(".buttons i:nth-child(5)").onclick = () => { // 删除任务
+ div.remove();
+ }
+ downloadContainer.appendChild(div);
+ }
+ });
+ DownloadController.loadDownloadStatus();
+ });
+ document.querySelector('.left div[data-page-id="downloadPage"]').hidden = false;
+ switchRightPage("downloadPage");
+ },
+ loadDownloadStatus() {
+ const currentDownloadingCount = document.querySelectorAll("#downloadContainer>div[data-status='download']").length;
+ if (currentDownloadingCount < config.getItem("parallelDownload") && document.querySelector("#downloadContainer>div[data-status='pending']")) {
+ this.downloadFile();
+ if (currentDownloadingCount + 1 < config.getItem("parallelDownload")) this.loadDownloadStatus();
+ }
+ },
+ async downloadFile() {
+ const element = document.querySelector("#downloadContainer>div[data-status='pending']");
+ if (!element) return;
+ const updateDownloadStatus = (status, text, progress = 0) => {
+ element.dataset.status = status;
+ element.querySelector(".progressText").textContent = text;
+ element.style.setProperty("--progressWidth", progress + "%");
+ DownloadController.loadDownloadStatus();
+ }
+ // 获取歌曲信息
+ updateDownloadStatus("download", "正在获取下载地址");
+ const file = element.dataset.file;
+ const destination = element.dataset.destination;
+ const fileScheme = file.split(":")[0];
+ const downUrl = await ExtensionConfig[fileScheme].player.getPlayUrl(file);
+ // 发起网络请求
+ updateDownloadStatus("download", "正在连接下载地址");
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", downUrl, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onprogress = event => {
+ if (event.lengthComputable) {
+ const percentComplete = Math.round((event.loaded / event.total) * 100);
+ updateDownloadStatus("download", `正在下载 ${percentComplete}%`, percentComplete);
+ } else {
+ updateDownloadStatus("download", "正在下载曲目");
+ }
+ };
+ xhr.onload = () => {
+ updateDownloadStatus("download", "正在保存曲目", 100);
+ const buffer = Buffer.from(xhr.response);
+ const fileName = SimMusicTools.getWindowsLegalName(config.getItem("downloadFileName").replaceAll("[title]", lastMusicIndex[file].title).replaceAll("[artist]", lastMusicIndex[file].artist));
+ const tempPath = path.join(destination, `${fileName}.simtemp`);
+ const ws = fs.createWriteStream(tempPath);
+ ws.on("finish", async () => {
+ updateDownloadStatus("download", "正在写入元数据", 100);
+ let coverArrBuffer;
+ const coverData = lastMusicIndex[file].cover;
+ if (typeof(coverData) == "object") coverArrBuffer = coverData;
+ else {
+ const coverRes = await fetch(coverData)
+ coverArrBuffer = await coverRes.arrayBuffer();
+ }
+ const metadataResult = nodeId3.write({
+ title: lastMusicIndex[file].title,
+ artist: lastMusicIndex[file].artist,
+ album: lastMusicIndex[file].album,
+ unsynchronisedLyrics: {
+ language: "XXX",
+ text: lastMusicIndex[file].lyrics ? lastMusicIndex[file].lyrics : await ExtensionConfig[fileScheme].player.getLyrics(file)
+ },
+ image: {
+ type: {id: 3, name: "front cover"},
+ imageBuffer: Buffer.from(coverArrBuffer)
+ }
+ }, tempPath);
+ if (metadataResult) {
+ const renameResult = this.renameDownloadFile(destination, fileName)
+ if (renameResult) {
+ updateDownloadStatus("success", "曲目下载成功", 100);
+ element.dataset.fileName = renameResult;
+ }
+ else updateDownloadStatus("error", "曲目写入失败", 0);
+ } else {
+ updateDownloadStatus("error", "曲目元数据写入失败", 0);
+ }
+ });
+ ws.on("error", () => { updateDownloadStatus("error", "曲目保存失败", 0); });
+ ws.write(buffer);
+ ws.end();
+ };
+ xhr.onerror = () => {
+ updateDownloadStatus("error", "曲目下载失败", 0);
+ };
+ xhr.send();
+ },
+ renameDownloadFile(dir, filename) {
+ try {
+ const originalFile = path.join(dir, `${filename}.simtemp`);
+ const baseTargetFile = path.join(dir, `${filename}.mp3`);
+ let targetFile = baseTargetFile;
+ let count = 1;
+ // 序号递增
+ while (fs.existsSync(targetFile)) { targetFile = path.join(dir, `${filename} (${count++}).mp3`); }
+ fs.renameSync(originalFile, targetFile);
+ return targetFile;
+ } catch (err) {
+ return false;
+ }
+ }
+}
@@ -899,7 +1128,7 @@ const SettingsPage = {
{type: "boolean", text: "不驻留后台进程", description: "关闭主界面时停止播放并完全退出应用。", configItem: "disableBackground"},
{type: "title", text: "音频扫描"},
{type: "input", text: "音频格式", description: "扫描本地音乐时的音频文件扩展名,以空格分隔。", configItem: "musicFormats"},
- {type: "button", text: "清除音频索引", description: "若您更改了音频元数据,可在此删除索引数据以重新从文件读取。", button: "清除", onclick: () => { SimMusicTools.writeMusicIndex({}, () => { alert("索引数据已清除,按「确认」重载此应用生效。", () => { ipcRenderer.invoke("restart"); }); }); }},
+ {type: "button", text: "清除音频索引", description: "若您更改了音频元数据,可在此删除索引数据以重新从文件读取。", button: "清除", onclick: () => { SimMusicTools.writeMusicIndex({}, () => { alert("索引数据已清除,按「确定」重载此应用生效。", () => { ipcRenderer.invoke("restart"); }); }); }},
{type: "title", text: "播放界面"},
{type: "boolean", text: "背景流光", description: "关闭后可牺牲部分视觉效果以显著减少播放页硬件占用。", configItem: "backgroundBlur"},
{type: "boolean", text: "播放页 3D 特效", description: "在播放页的歌曲信息、播放列表与歌词界面使用 3D 视觉效果。", configItem: "3dEffect"},
@@ -909,16 +1138,22 @@ const SettingsPage = {
{type: "range", text: "歌词间距", configItem: "lyricSpace", min: .2, max: 1},
{type: "boolean", text: "歌词多语言支持", description: "开启后,时间戳一致的不同歌词将作为多语言翻译同时渲染。此配置项切换曲目生效。", configItem: "lyricMultiLang"},
{type: "range", text: "歌词翻译字号", description: "开启多语言支持后,可使用此选项调整多语言翻译的字号。", configItem: "lyricTranslation", min: .5, max: 1},
- {type: "title", text: "音频输出"},
- {type: "button", text: "均衡器", description: "不用点,还没写,别骂了", button: "配置", onclick: () => { alert("都让你别点了(恼"); }},
+ {type: "title", text: "播放控制"},
+ {type: "boolean", text: "快速重播", description: "在曲目即将结束时按「快退」按钮以回到当前曲目的开头。", configItem: "fastPlayback"},
+ {type: "button", text: "均衡器", description: "下次一定。", button: "配置", onclick: () => { alert("还没写。下次一定。"); }},
{type: "title", text: "桌面歌词"},
{type: "boolean", text: "启动时打开", configItem: "autoDesktopLyrics"},
{type: "boolean", text: "在截图中隐藏", description: "其他应用截图或录屏时隐藏桌面歌词的内容,与多数截图或录屏软件相兼容,支持 Windows 10 2004 以上版本及 Windows 11。此功能不会影响您查看歌词,对采集卡等外置硬件无效。", configItem: "desktopLyricsProtection"},
{type: "boolean", text: "在播放页自动关闭", description: "当 SimMusic 主窗口打开播放页时自动关闭桌面歌词。", configItem: "desktopLyricsAutoHide"},
- {type: "color", text: "字体颜色", inputType: "color", configItem: "desktopLyricsColor"},
- {type: "color", text: "边框颜色", inputType: "color", configItem: "desktopLyricsStroke"},
+ {type: "color", text: "字体颜色", configItem: "desktopLyricsColor"},
+ {type: "boolean", text: "启用边框", configItem: "desktopLyricsStrokeEnabled"},
+ {type: "color", text: "边框颜色", description: "在启用边框时生效。", configItem: "desktopLyricsStroke"},
{type: "range", text: "字体大小", configItem: "desktopLyricsSize", min: 20, max: 60},
{type: "range", text: "歌词区域宽度", configItem: "desktopLyricsWidth", min: 500, max: screen.width},
+ {type: "boolean", text: "始终居中", description: "无视用户左右拖拽操作,保持桌面歌词在屏幕中央。", configItem: "desktopLyricsCentered"},
+ {type: "title", text: "曲目下载"},
+ {type: "input", text: "并行下载数", inputType: "number", description: "下载在线歌曲时并行下载的任务数量。", configItem: "parallelDownload"},
+ {type: "input", text: "命名格式", description: "可使用 [title] 表示歌曲名,[artist] 表示艺术家。SimMusic 会自动处理 Windows 无法写入的文件名。", configItem: "downloadFileName"},
],
init() {
const settingsContainer = document.getElementById("settingsContainer");
@@ -976,7 +1211,7 @@ const SettingsPage = {
${SimMusicTools.escapeHtml(data.text)}
${data.description ? `${SimMusicTools.escapeHtml(data.description)}` : ""}
-
`;
+
`;
const colorInput = div.querySelector("input");
colorInput.value = config.getItem(data.configItem);
div.querySelector(".colorInput>span").textContent = config.getItem(data.configItem);
@@ -1010,12 +1245,14 @@ function updateDesktopLyricsConfig() {
ipcRenderer.invoke("updateDesktopLyricsConfig", config.getItem("desktopLyricsProtection"));
}
config.listenChange("desktopLyricsColor", updateDesktopLyricsConfig);
+config.listenChange("desktopLyricsStrokeEnabled", updateDesktopLyricsConfig);
config.listenChange("desktopLyricsStroke", updateDesktopLyricsConfig);
config.listenChange("desktopLyricsSize", updateDesktopLyricsConfig);
config.listenChange("desktopLyricsProtection", updateDesktopLyricsConfig);
config.listenChange("desktopLyricsWidth", updateDesktopLyricsConfig);
config.listenChange("desktopLyricsTop", updateDesktopLyricsConfig);
config.listenChange("desktopLyricsLeft", updateDesktopLyricsConfig);
+config.listenChange("desktopLyricsCentered", updateDesktopLyricsConfig);
updateDesktopLyricsConfig();
if (config.getItem("autoDesktopLyrics")) WindowOps.toggleLyrics();
@@ -1032,4 +1269,5 @@ function initAboutPage() {
shell.openExternal(link.dataset.href);
}
});
+ document.getElementById("copyrightYear").textContent = new Date().getFullYear();
}
\ No newline at end of file
diff --git a/src/frontend/lrc.html b/src/frontend/lrc.html
index c31404d..26523ff 100644
--- a/src/frontend/lrc.html
+++ b/src/frontend/lrc.html
@@ -14,7 +14,8 @@
#lyrics{position:fixed;width:var(--fontWidth);}
#move{position:absolute;inset:0 0 auto 0;width:fit-content;height:fit-content;border-radius:50%;color:white;opacity:0;text-shadow:0 0 2px black;padding:0 10px 10px 10px;z-index:114;margin:auto;transition:opacity .2s;}
.showBtn #move,.focused #move,.dragging #move{opacity:.7;}
- #text{font-size:var(--fontSize);color:var(--fontColor);-webkit-text-stroke:0.01px var(--fontStroke);position:absolute;inset:20px 0 auto 0;padding:0 5px;width:100%;font-family:"font";text-align:center;border-radius:10px;font-weight:bold;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;transition:font-size .2s,background .2s;}
+ #text{font-size:var(--fontSize);color:var(--fontColor);position:absolute;inset:20px 0 auto 0;padding:0 5px;width:100%;font-family:"font";text-align:center;border-radius:10px;font-weight:bold;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;transition:font-size .2s,background .2s;}
+ .showStroke #text{-webkit-text-stroke:0.01px var(--fontStroke);}
.dragging #text{background:rgba(0,0,0,.1);}
@@ -82,9 +83,11 @@
const width = config.getItem("desktopLyricsWidth");
document.body.style.setProperty("--fontSize", config.getItem("desktopLyricsSize") + "px");
document.body.style.setProperty("--fontColor", config.getItem("desktopLyricsColor"));
+ document.body.classList[config.getItem("desktopLyricsStrokeEnabled") ? "add" : "remove"]("showStroke");
document.body.style.setProperty("--fontStroke", config.getItem("desktopLyricsStroke"));
document.body.style.setProperty("--fontWidth", width + "px");
- lyrics.style.left = config.getItem("desktopLyricsLeft") - width / 2 + "px";
+ if (!config.getItem("desktopLyricsCentered")) lyrics.style.left = config.getItem("desktopLyricsLeft") - width / 2 + "px";
+ else lyrics.style.left = screen.width / 2 - width / 2 + "px";
lyrics.style.top = config.getItem("desktopLyricsTop") - 10 + "px";
}
ipcRenderer.on("lrcWinReload", reloadConfig);
diff --git a/src/frontend/main.html b/src/frontend/main.html
index 211dc23..6b8015d 100644
--- a/src/frontend/main.html
+++ b/src/frontend/main.html
@@ -15,6 +15,7 @@
+
词
@@ -39,6 +40,7 @@
搜索
+ 下载
扩展
设置
关于
@@ -84,7 +86,7 @@
@@ -98,11 +100,18 @@
+
+
@@ -119,7 +128,7 @@
SimMusic 2024
高颜值插件化音频播放器 · 应用版本
- © 2020-2024 Simsv Software, Licenced under GPL-3.0
+ © 2020-
Simsv Software, Licenced under GPL-3.0
@@ -128,11 +137,13 @@
相关推荐一些其他优秀的项目。
@@ -154,7 +165,7 @@
-
+
@@ -167,7 +178,8 @@
-
+
+
diff --git a/src/main.js b/src/main.js
index 9f59b10..f0c005f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,7 +1,7 @@
// © 2020 - 2024 Simsv Studio
-const {app, BrowserWindow, ipcMain, dialog, nativeImage, Tray, Menu} = require("electron");
+const {app, BrowserWindow, ipcMain, dialog, nativeImage, Tray, Menu, screen} = require("electron");
const {exec} = require("child_process");
const path = require("path");
@@ -48,11 +48,12 @@ const createWindow = () => {
webPreferences: { webSecurity: false, nodeIntegration: true, contextIsolation: false }
});
SimMusicWindows.lrcWin.loadURL(path.join(__dirname, "frontend/lrc.html"));
- SimMusicWindows.lrcWin.setFullScreen(true);
+ SimMusicWindows.lrcWin.maximize();
}
app.whenReady().then(() => {
tray = new Tray(nativeImage.createFromPath(path.join(__dirname, "frontend/assets/icon-blue.png")));
tray.on("click", () => { showMainWin(); });
+ tray.setToolTip("SimMusic");
createWindow();
if (!app.requestSingleInstanceLock()) {
app.exit();
@@ -65,30 +66,9 @@ app.whenReady().then(() => {
// 处理窗口事件
-let lyricsShowing = false;
-let lyricsInterval; // 就这么写了有时候还是不生效 估计是elec的bug 暂时修不好=(
ipcMain.handle("winOps", (_event, args) => {
return SimMusicWindows[args[0]][args[1]]();
});
-ipcMain.handle("toggleLyrics", (_event, isShow) => {
- if (isShow || isShow === false) {lyricsShowing = !isShow;}
- if (lyricsShowing) {
- SimMusicWindows.lrcWin.webContents.send("setHidden", "text", true);
- setTimeout(() => {SimMusicWindows.lrcWin.hide();}, 100);
- lyricsShowing = false;
- clearInterval(lyricsInterval);
- } else {
- SimMusicWindows.lrcWin.show();
- SimMusicWindows.lrcWin.setIgnoreMouseEvents("true", {forward: true});
- SimMusicWindows.lrcWin.setSkipTaskbar(true);
- SimMusicWindows.lrcWin.setAlwaysOnTop(false);
- SimMusicWindows.lrcWin.setAlwaysOnTop(true);
- lyricsShowing = true;
- setTimeout(() => {SimMusicWindows.lrcWin.webContents.send("setHidden", "text", false);}, 400);
- lyricsInterval = setInterval(() => {if (SimMusicWindows.lrcWi) SimMusicWindows.lrcWin.setAlwaysOnTop(true);}, 100);
- }
- return lyricsShowing;
-});
ipcMain.handle("restart", () => {
app.exit();
app.relaunch();
@@ -160,6 +140,24 @@ ipcMain.handle("musicPause", () => {
// 桌面歌词
+let lyricsShowing = false;
+ipcMain.handle("toggleLyrics", (_event, isShow) => {
+ if (isShow || isShow === false) {lyricsShowing = !isShow;}
+ if (lyricsShowing) {
+ SimMusicWindows.lrcWin.webContents.send("setHidden", "text", true);
+ setTimeout(() => {SimMusicWindows.lrcWin.hide();}, 100);
+ lyricsShowing = false;
+ } else {
+ SimMusicWindows.lrcWin.show();
+ SimMusicWindows.lrcWin.setIgnoreMouseEvents("true", {forward: true});
+ SimMusicWindows.lrcWin.setSkipTaskbar(true);
+ SimMusicWindows.lrcWin.setAlwaysOnTop(false);
+ SimMusicWindows.lrcWin.setAlwaysOnTop(true);
+ lyricsShowing = true;
+ setTimeout(() => {SimMusicWindows.lrcWin.webContents.send("setHidden", "text", false);}, 400);
+ }
+ return lyricsShowing;
+});
ipcMain.handle("lrcUpdate", (_event, lrc) => {
SimMusicWindows.lrcWin.webContents.send("lrcUpdate", lrc);
});
@@ -175,16 +173,73 @@ ipcMain.handle("updateDesktopLyricsConfig", (_event, isProtected) => {
});
+
+// 迷你模式
+let isMiniMode = false;
+ipcMain.handle("toggleMini", () => {
+ const { width, height } = screen.getPrimaryDisplay().workAreaSize;
+ SimMusicWindows.mainWin.hide();
+ if (isMiniMode) {
+ setTimeout(() => {
+ SimMusicWindows.mainWin.setMinimumSize(1000, 700);
+ SimMusicWindows.mainWin.setSize(1000, 700);
+ SimMusicWindows.mainWin.setPosition(width / 2 - 500, height / 2 - 350);
+ SimMusicWindows.mainWin.setResizable(true);
+ SimMusicWindows.mainWin.setAlwaysOnTop(false);
+ SimMusicWindows.mainWin.setSkipTaskbar(false);
+ SimMusicWindows.mainWin.show();
+ }, 500);
+ return isMiniMode = false;
+ } else {
+ setTimeout(() => {
+ SimMusicWindows.mainWin.unmaximize();
+ SimMusicWindows.mainWin.setMinimumSize(340, 60);
+ SimMusicWindows.mainWin.setSize(340, 60);
+ SimMusicWindows.mainWin.setResizable(false);
+ SimMusicWindows.mainWin.setAlwaysOnTop(true);
+ SimMusicWindows.mainWin.setSkipTaskbar(true);
+ SimMusicWindows.mainWin.setPosition(width - 360, height - 90);
+ SimMusicWindows.mainWin.show();
+ }, 500);
+ return isMiniMode = true;
+ }
+});
+
+
+
// 主窗口调用
ipcMain.handle("pickFolder", () => {
return dialog.showOpenDialogSync(SimMusicWindows.mainWin, {
- title: "导入目录",
+ title: "选择目录 - SimMusic",
defaultPath: "C:\\",
- buttonLabel: "导入",
+ buttonLabel: "使用此目录",
properties: ["openDirectory"],
});
});
ipcMain.handle("openDevtools", () => {
SimMusicWindows.mainWin.webContents.openDevTools();
- SimMusicWindows.lrcWin.webContents.openDevTools();
+ // 傻逼谷歌搞个宋体当默认代码字体 怎么想的 给你眼珠子扣下来踩两脚
+ SimMusicWindows.mainWin.webContents.once("devtools-opened", () => {
+ const css = `
+ :root {
+ --sys-color-base: var(--ref-palette-neutral100);
+ --source-code-font-family: consolas;
+ --source-code-font-size: 12px;
+ --monospace-font-family: consolas;
+ --monospace-font-size: 12px;
+ --default-font-family: system-ui, sans-serif;
+ --default-font-size: 12px;
+ }
+ .-theme-with-dark-background {
+ --sys-color-base: var(--ref-palette-secondary25);
+ }
+ body {
+ --default-font-family: system-ui,sans-serif;
+ }`;
+ SimMusicWindows.mainWin.webContents.devToolsWebContents.executeJavaScript(`
+ const overriddenStyle = document.createElement('style');
+ overriddenStyle.innerHTML = '${css.replaceAll('\n', ' ')}';
+ document.body.append(overriddenStyle);
+ document.body.classList.remove('platform-windows');`);
+ });
});
\ No newline at end of file
diff --git a/src/package.json b/src/package.json
index 1e4326f..0f55890 100644
--- a/src/package.json
+++ b/src/package.json
@@ -1,11 +1,12 @@
{
- "name": "sim-music",
- "version": "1.0.0",
- "description": "SimMusic",
- "main": "main.js",
- "author": "Simsv Studio",
- "dependencies": {
- "music-metadata": "^7.13.5",
- "node-id3": "^0.2.6"
- }
+ "name": "sim-music",
+ "version": "1.0.0",
+ "description": "SimMusic",
+ "main": "main.js",
+ "author": "Simsv Studio",
+ "dependencies": {
+ "fflate": "^0.8.2",
+ "music-metadata": "^7.13.5",
+ "node-id3": "^0.2.6"
+ }
}