From da661b3bcb414de45edb00f9693d10c82683a6dc Mon Sep 17 00:00:00 2001 From: YanJi Date: Wed, 28 Aug 2024 21:53:33 +0800 Subject: [PATCH] Sync 0.1.3 --- .../assets/components/PublicConfig.js | 2 + src/frontend/assets/components/SimAP.js | 35 ++--- src/frontend/assets/components/require.js | 1 + src/frontend/assets/main.css | 15 +- src/frontend/assets/main.js | 137 +++++++++++++++--- src/frontend/lrc.html | 2 +- src/frontend/main.html | 6 + src/main.js | 4 +- src/package.json | 21 +-- 9 files changed, 169 insertions(+), 54 deletions(-) diff --git a/src/frontend/assets/components/PublicConfig.js b/src/frontend/assets/components/PublicConfig.js index 62deca3..1f8a2ff 100644 --- a/src/frontend/assets/components/PublicConfig.js +++ b/src/frontend/assets/components/PublicConfig.js @@ -7,6 +7,8 @@ const defaultConfig = { loop: 0, lrcShow: true, musicFormats: ".mp3 .wav .flac", + showLocator: true, + themeImageType: "cover", backgroundBlur: true, audioFade: true, lyricBlur: true, diff --git a/src/frontend/assets/components/SimAP.js b/src/frontend/assets/components/SimAP.js index 0cf0c99..1cad2ee 100644 --- a/src/frontend/assets/components/SimAP.js +++ b/src/frontend/assets/components/SimAP.js @@ -73,6 +73,7 @@ const switchMusic = (playConfig) => { }); SimAPControls.loadLoop(); document.title = playConfig.title + " - SimMusic"; + loadThemeImage(); // 初始化背景 document.getElementById("album").onload = () => { const themeColors = SimAPTools.getTopColors(document.getElementById("album")); @@ -163,6 +164,7 @@ const PlayerBackground = { }, animate(isInit) { requestAnimationFrame(() => {PlayerBackground.animate();}); + if (!config.getItem("backgroundBlur")) return; if (!document.body.classList.contains("playing") && !isInit) return; const ctx = PlayerBackground.ctx; ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); @@ -172,7 +174,6 @@ const PlayerBackground = { blob.y += blob.dy; if (blob.x - blob.radius < 0 || blob.x + blob.radius > window.innerWidth) blob.dx *= -1; if (blob.y - blob.radius < 0 || blob.y + blob.radius > window.innerHeight) blob.dy *= -1; - // 绘制 } PlayerBackground.drawBlobs(); }, @@ -180,16 +181,16 @@ const PlayerBackground = { document.getElementById("background").style.background = mainColor; this.mainColor = mainColor; this.blobs = []; - for (let i = 0; i < 3; i++) { - this.blobs.push({ - x: Math.random() * canvas.width, - y: Math.random() * canvas.height, - radius: Math.random() * screen.width / 3 + screen.width / 5, - color: subColors[i], - dx: ((Math.random() < 0.5) ? 1 : -1) * (Math.random() * 0.5 + 0.5), - dy: ((Math.random() < 0.5) ? 1 : -1) * (Math.random() * 0.5 + 0.5), - }); - } + for (let i = 0; i < 3; i++) { + this.blobs.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + radius: Math.random() * screen.width / 3 + screen.width / 5, + color: subColors[i], + dx: ((Math.random() < 0.5) ? 1 : -1) * (Math.random() * 0.5 + 0.5), + dy: ((Math.random() < 0.5) ? 1 : -1) * (Math.random() * 0.5 + 0.5), + }); + } PlayerBackground.drawBlobs(); }, drawBlobs() { @@ -224,13 +225,13 @@ const SimAPControls = { const isPlay = audio.paused; document.body.classList[isPlay ? "add" : "remove"]("playing"); SimAPControls.loadAudioState(); - clearInterval(this.audioFadeInterval); + clearInterval(SimAPControls.audioFadeInterval); // 音频淡入淡出处理 - if (config.getItem("audioFade")) { + if (config.getItem("audioFade") && audio.volume) { const configVolume = config.getItem("volume"); const volumeOffset = configVolume / 10; if (isPlay) audio.play(); - this.audioFadeInterval = setInterval(() => { + SimAPControls.audioFadeInterval = setInterval(() => { if (isPlay) { const newVolume = audio.volume + volumeOffset; if (newVolume > configVolume) { @@ -253,10 +254,10 @@ const SimAPControls = { }, prev() { const audio = document.getElementById("audio"); - if (!config.getItem("fastPlayback") || audio.currentTime / audio.duration < .9) this.switchIndex(-1); + if (!config.getItem("fastPlayback") || audio.currentTime / audio.duration < .9) SimAPControls.switchIndex(-1); else audio.currentTime = 0; }, - next() {this.switchIndex(1);}, + next() {SimAPControls.switchIndex(1);}, switchIndex(offset) { const list = config.getItem("playList"); const currentPlayingIndex = list.indexOf(config.getItem("currentMusic")); @@ -353,7 +354,7 @@ const SimAPUI = { hide() { if (this.playingAnimation) return; if (!document.body.classList.contains("playerShown")) return; - document.exitFullscreen(); + document.exitFullscreen().catch(() => {}); document.body.classList.remove("playerShown"); this.playingAnimation = true; setTimeout(() => { diff --git a/src/frontend/assets/components/require.js b/src/frontend/assets/components/require.js index 549b318..0c3eb56 100644 --- a/src/frontend/assets/components/require.js +++ b/src/frontend/assets/components/require.js @@ -2,5 +2,6 @@ const {ipcRenderer, shell} = require("electron"); const fs = require("fs"); const path = require("path"); const musicMetadata = require("music-metadata"); +const flacTagger = require("flac-tagger"); const nodeId3 = require("node-id3"); const fflate = require("fflate"); diff --git a/src/frontend/assets/main.css b/src/frontend/assets/main.css index 955ffb3..5f2fe8a 100644 --- a/src/frontend/assets/main.css +++ b/src/frontend/assets/main.css @@ -116,6 +116,10 @@ body:not(.disableHighlight) .right #musicListContainer .tableContainer table td> .right #musicListContainer .tableContainer table tr:active{background:rgba(0,0,0,.05);} .right #musicListContainer .tableContainer table tr.active{color:#1E9FFF;} .right #musicListContainer .tableContainer table tr.selected{background:#DAEFFF!important;} +.right #musicListContainer .musicLocator{position:absolute;right:20px;bottom:100px;height:40px;width:40px;border:1px solid rgba(0,0,0,.1);border-radius:50%;background:rgba(252,252,252,.9);backdrop-filter:blur(20px);font-size:1.2em;display:flex;align-items:center;justify-content:center;transition:all .2s;} +.right #musicListContainer .musicLocator:hover{color:#1E9FFF;} +.right #musicListContainer .musicLocator:active{color:#1E9FFF;transform:scale(.95);filter:brightness(.95);} +.right #musicListContainer .musicLocator.hidden{transform:scale(.2);opacity:0;pointer-events:none;} .right #musicListContainer .musicListErrorOverlay{position:absolute;inset:180px 0 80px 0;display:flex;align-items:center;justify-content:center;flex-direction:column;} .right #musicListContainer .musicListErrorOverlay img{width:120px;} .right #musicListContainer .musicListErrorOverlay div{opacity:.5;margin-top:10px;font-size:1.2em;text-align:center;} @@ -203,6 +207,11 @@ body:not(.disableHighlight) .right #musicListContainer .tableContainer table td> .dragOver #dropTipContainer{opacity:1;} +/* 主窗体 - 主题图片 */ +#themeImage{position:fixed;inset:0;width:100%;height:100%;object-fit:cover;opacity:.05;z-index:40;transition:opacity .2s;} +body:not(.themeImage) #themeImage{opacity:0;} +.themeImage #bottom,.themeImage .musicListTitle,.themeImage .right .page .header,.themeImage .musicLocator{background:rgba(255,255,255,.9);} + /* 主窗体 - 底部控件 */ .bottom{position:fixed;bottom:-90px;height:80px;width:100%;background:rgba(252,252,252,.9);backdrop-filter:blur(20px);transition:bottom .2s;} @@ -217,7 +226,7 @@ body:not(.disableHighlight) .right #musicListContainer .tableContainer table td> .bottom .info .img::after{content:"\EA78";font-size:2.5em;background:rgba(0,0,0,.2);position:absolute;inset:0;font-family:"icon";display:flex;align-items:center;justify-content:center;color:white;opacity:0;transition:opacity .2s;} .bottom .info .img:hover::after,.bottom .info .img:active::after{opacity:1;} .bottom .info .img>img{width:100%;height:100%;object-fit:cover;} -.bottom .info .musicInfoBottom{max-width:200px;margin-bottom:5px;} +.bottom .info .musicInfoBottom{max-width:200px;} .bottom .info .musicInfoBottom b{font-size:1.1em;} .bottom .info .musicInfoBottom>div{position:relative;} .bottom .info .musicInfoBottom>div div{transition:opacity .2s,transform .2s;} @@ -233,7 +242,7 @@ body:not(.miniModeStatus) .bottom .info .musicInfoBottom>div #miniModeStatus{tra /* 主窗体 - 播放内页 */ -#playPage{position:fixed;top:120vh;left:0;width:100%;height:100%;background:white;transition:top .3s;overflow:hidden;user-select:none;} +#playPage{position:fixed;top:120vh;left:0;width:100%;height:100%;background:white;transition:top .3s;overflow:hidden;user-select:none;z-index:50;} .playerShown #playPage{top:0;} @@ -250,7 +259,7 @@ body:not(.miniModeStatus) .bottom .info .musicInfoBottom>div #miniModeStatus{tra .miniMode .bottom .info{top:0;bottom:0!important;left:0;width:170px;bottom:10px;} .miniMode .bottom .info .img{width:60px;height:60px;border-radius:0;margin-right:10px;-webkit-app-region:drag;} .miniMode .bottom .info .img::after{display:none;} -.miniMode .bottom .info .musicInfoBottom{font-size:1.1em;max-width:calc(100px / .75);zoom:.75;} +.miniMode .bottom .info .musicInfoBottom{font-size:1.1em;max-width:calc(100px / .75);zoom:.75;margin-bottom:5px;} .miniMode .bottom .info .musicInfoBottom>div{margin-top:-1px;} .miniMode .bottom .center{right:15px;zoom:.7;bottom:7.5px;width:220px;left:unset;} .miniMode .bottom .center .bottomListBtn{display:none;} diff --git a/src/frontend/assets/main.js b/src/frontend/assets/main.js index 739c923..80e0c5c 100644 --- a/src/frontend/assets/main.js +++ b/src/frontend/assets/main.js @@ -1,4 +1,4 @@ -SimMusicVersion = "0.1.2"; +SimMusicVersion = "0.1.3"; // 窗口处理 @@ -49,11 +49,12 @@ document.documentElement.onkeydown = e => { if (document.activeElement.tagName.toLowerCase() == "input") return; e.preventDefault(); if (document.activeElement.tagName.toLowerCase() == "input") return; + const moveOffset = e.ctrlKey ? 10 : 1; 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; - case "s": config.setItem("desktopLyricsTop", Math.min(config.getItem("desktopLyricsTop") + 2, screen.height - 100)); break; - case "d": config.setItem("desktopLyricsLeft", Math.min(config.getItem("desktopLyricsLeft") + 2, screen.width)); break; + case "w": config.setItem("desktopLyricsTop", Math.max(config.getItem("desktopLyricsTop") - moveOffset, 0)); break; + case "a": config.setItem("desktopLyricsLeft", Math.max(config.getItem("desktopLyricsLeft") - moveOffset, 0)); break; + case "s": config.setItem("desktopLyricsTop", Math.min(config.getItem("desktopLyricsTop") + moveOffset, screen.height - 100)); break; + case "d": config.setItem("desktopLyricsLeft", Math.min(config.getItem("desktopLyricsLeft") + moveOffset, screen.width)); break; } }; document.documentElement.ondragstart = e => {e.preventDefault();}; @@ -1001,7 +1002,27 @@ const PlayerController = { const currentMusic = config.getItem("currentMusic"); document.querySelectorAll(".musicListContent>tr").forEach(tr => { tr.classList[tr.dataset.file == currentMusic ? "add" : "remove"]("active"); - }) + }); + document.querySelectorAll("#musicListContainer>div").forEach(div => { + const activeMusic = div.querySelector("tr.active"); + const musicLocator = div.querySelector(".musicLocator"); + const tableContainer = div.querySelector(".tableContainer") + if (activeMusic && config.getItem("showLocator")) { + musicLocator.classList.remove("hidden"); + musicLocator.onclick = () => { + musicLocator.classList.add("hidden"); + activeMusic.scrollIntoView({block: "center", behavior: "smooth"}); + setTimeout(() => {activeMusic.classList.add("selected");}, 200); + setTimeout(() => {activeMusic.classList.remove("selected");}, 500); + }; + tableContainer.onwheel = () => { + musicLocator.classList.remove("hidden"); + }; + } else { + musicLocator.classList.add("hidden"); + tableContainer.onwheel = null; + } + }); document.querySelectorAll("#playList>div").forEach(div => { if (div.dataset.file == currentMusic) { div.classList.add("active"); @@ -1011,7 +1032,8 @@ const PlayerController = { setTimeout(() => { PlayerController.listScrollLock = false; }, 500); } } else div.classList.remove("active"); - }) + }); + loadThemeImage(); } } if (!config.getItem("lrcShow")) document.body.classList.add("hideLyrics"); @@ -1020,6 +1042,52 @@ 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; +config.listenChange("showLocator", PlayerController.loadMusicListActive); + + + +// 主界面主题图片 +function loadThemeImage() { + if (!config.getItem("themeImage")) document.body.classList.remove("themeImage"); + else { + const themeImage = document.getElementById("themeImage"); + switch (config.getItem("themeImageType")) { + case "cover": + if (config.getItem("currentMusic")) { + document.body.classList.add("themeImage"); + themeImage.src = document.getElementById("album").src; + } else { + document.body.classList.remove("themeImage"); + } + break; + case "local": + document.body.classList.add("themeImage"); + themeImage.src = config.getItem("themeImagePath"); + break; + } + themeImage.style.filter = config.getItem("themeImageBlur") ? "blur(20px)" : "none"; + } +} +document.getElementById("themeImage").onerror = () => { + document.body.classList.remove("themeImage"); +}; +config.listenChange("themeImage", loadThemeImage); +config.listenChange("themeImageType", value => { + config.setItem("themeImagePath", ""); + if (value == "local") { + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "image/*"; + fileInput.multiple = false; + fileInput.click(); + fileInput.onchange = e => { + const file = e.target.files[0]; + if (file) config.setItem("themeImagePath", file.path); + }; + } +}); +config.listenChange("themeImagePath", loadThemeImage); +config.listenChange("themeImageBlur", loadThemeImage); @@ -1147,21 +1215,41 @@ const DownloadController = { 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) + let metadataResult,mediaFormat,fileExtension; + try { + mediaFormat = (await musicMetadata.parseFile(tempPath)).format.container.toLowerCase(); + switch (mediaFormat) { + case "flac": + const tagMap = { + title: lastMusicIndex[file].title, + artist: lastMusicIndex[file].artist, + album: lastMusicIndex[file].album, + lyrics: lastMusicIndex[file].lyrics ? lastMusicIndex[file].lyrics : await ExtensionConfig[fileScheme].player.getLyrics(file), + }; + await flacTagger.writeFlacTags({ tagMap, picture: { buffer: Buffer.from(coverArrBuffer) } }, tempPath); + metadataResult = true; + fileExtension = "flac"; + break; + default: + 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); + fileExtension = "mp3"; + break; } - }, tempPath); + } catch (err){console.log(err)} if (metadataResult) { - const renameResult = this.renameDownloadFile(destination, fileName) + const renameResult = this.renameDownloadFile(destination, fileName, fileExtension) if (renameResult) { updateDownloadStatus("success", "曲目下载成功", 100); element.dataset.fileName = renameResult; @@ -1180,10 +1268,10 @@ const DownloadController = { }; xhr.send(); }, - renameDownloadFile(dir, filename) { + renameDownloadFile(dir, filename, ext) { try { const originalFile = path.join(dir, `${filename}.simtemp`); - const baseTargetFile = path.join(dir, `${filename}.mp3`); + const baseTargetFile = path.join(dir, `${filename}.${ext}`); let targetFile = baseTargetFile; let count = 1; // 序号递增 @@ -1234,6 +1322,11 @@ const SettingsPage = { {type: "title", text: "音频扫描"}, {type: "input", text: "音频格式", description: "扫描本地音乐时的音频文件扩展名,以空格分隔。", configItem: "musicFormats"}, {type: "button", text: "清除音频索引", description: "若您更改了音频元数据,可在此删除索引数据以重新从文件读取。", button: "清除", onclick: () => { SimMusicTools.writeMusicIndex({}, () => { alert("索引数据已清除,按「确定」重载此应用生效。", () => { ipcRenderer.invoke("restart"); }); }); }}, + {type: "title", text: "歌单界面"}, + {type: "boolean", text: "显示「曲目定位」按钮", configItem: "showLocator"}, + {type: "boolean", text: "启用主题图片", description: "在 SimMusic 主界面显示主题图片。", configItem: "themeImage"}, + {type: "select", text: "选择主题图片", options: [["cover", "曲目封面"], ["local", "本地文件"]], configItem: "themeImageType", attachTo: "themeImage"}, + {type: "boolean", text: "图片模糊效果", configItem: "themeImageBlur", attachTo: "themeImage"}, {type: "title", text: "播放界面"}, {type: "boolean", text: "背景动态混色", description: "关闭后可减少播放页对硬件资源的占用。", configItem: "backgroundBlur"}, {type: "boolean", text: "3D 特效", badges: ["experimental"], description: "在播放页的歌曲信息、播放列表与歌词视图使用 3D 视觉效果。", configItem: "3dEffect"}, diff --git a/src/frontend/lrc.html b/src/frontend/lrc.html index 06437a2..3087458 100644 --- a/src/frontend/lrc.html +++ b/src/frontend/lrc.html @@ -40,7 +40,7 @@ // 移动窗口 function moveWindow(e) { config.setItem("desktopLyricsTop", e.pageY); - config.setItem("desktopLyricsLeft", Math.max(e.pageX, )); + config.setItem("desktopLyricsLeft", e.pageX); reloadConfig(); } diff --git a/src/frontend/main.html b/src/frontend/main.html index 0ad3ba9..6d0b57a 100644 --- a/src/frontend/main.html +++ b/src/frontend/main.html @@ -80,6 +80,7 @@ +
@@ -97,6 +98,7 @@
+ @@ -139,6 +141,7 @@ electron/electron Borewit/music-metadata Zazama/node-id3 + the1812/flac-tagger zhujin917/3sqrt7-context-menu 101arrowz/fflate lokesh/color-thief @@ -191,6 +194,9 @@ + + +