diff --git a/README.md b/README.md index 0aca4da..48401b1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # SimMusic 2024 -专注本地音乐播放。 +高颜值模块化音频播放器。 -官网 & 软件下载:https://simsv.com/products#/simmusic \ No newline at end of file +# Downloads +https://simsv.com/products#/simmusic diff --git a/src/frontend/assets/components/PublicConfig.js b/src/frontend/assets/components/PublicConfig.js index 6e6b119..3720c88 100644 --- a/src/frontend/assets/components/PublicConfig.js +++ b/src/frontend/assets/components/PublicConfig.js @@ -7,10 +7,23 @@ const defaultConfig = { loop: 0, lrcShow: true, musicFormats: ".mp3 .wav .flac", + listDomCache: true, backgroundBlur: true, lyricBlur: true, lyricSize: 1.5, + lyricTranslation: .8, lyricSpace: .5, + lyricMultiLang: true, + leftBarWidth: 200, + autoDesktopLyrics: false, + desktopLyricsProtection: true, + desktopLyricsAutoHide: true, + desktopLyricsColor: "#1E9FFF", + desktopLyricsStroke: "#1672B8", + desktopLyricsSize: 30, + desktopLyricsWidth: 700, + desktopLyricsTop: screen.height - 300, + desktopLyricsLeft: screen.width / 2, extensions: ["assets/extensions/local.json"], extensionCache: {}, } diff --git a/src/frontend/assets/components/SimAP.css b/src/frontend/assets/components/SimAP.css index 23a258b..10a0b79 100644 --- a/src/frontend/assets/components/SimAP.css +++ b/src/frontend/assets/components/SimAP.css @@ -1,6 +1,7 @@ -#background{filter:blur(100px);z-index:-1;top:-200px;left:-200px;right:-200px;bottom:-200px;position:absolute;pointer-events:none;opacity:.15;transition:background .3s;} +#background{filter:blur(100px);z-index:-1;top:-200px;left:-200px;right:-200px;bottom:-200px;position:absolute;pointer-events:none;opacity:.2;transition:background .3s;} #background>div{width:max(40vw,40vh);height:max(40vw,40vh);animation-duration:20s;animation-iteration-count:infinite;animation-timing-function:cubic-bezier(0.1, 0, 0.9, 1);position:absolute;border-radius:100%;opacity:.8;transition:background .3s;} +#background>section{position:absolute;inset:0;background:linear-gradient(135deg, rgba(255,255,255,.1), rgba(255,255,255,.5));} .disableBackgroundBlur #background{filter:none;} .disableBackgroundBlur #background>div{display:none;} body:not(.playing) #background>div{animation-play-state:paused;} @@ -39,8 +40,8 @@ body:not(.playing) #background>div{animation-play-state:paused;} .playing .controls .buttons>.play>i:last-child,.playing .bottom .center>.play>i:last-child{opacity:1;transform:none;} /* 音量控制 */ .volume .controls .buttons>div{width:0;opacity:0!important;} -.volume .controls .buttons>.volBtn{width:200px;color:rgba(0,0,0,.7);background:rgba(0,0,0,.05)!important;transform:none!important;opacity:1!important;mask:unset;border-radius:100px;} -.volume .controls .buttons>.volBtn>i{right:140px;} +.volume .controls .buttons>.volBtn{width:180px;color:rgba(0,0,0,.7);background:rgba(0,0,0,.05)!important;transform:none!important;opacity:1!important;mask:unset;border-radius:100px;} +.volume .controls .buttons>.volBtn>i{right:120px;} .volume .controls .buttons>.volBtn>i:hover{color:var(--SimAPTheme);} .controls .buttons>.volBtn>div{width:calc(100% - 85px);position:absolute;margin:auto 0;top:0;bottom:0;right:30px;opacity:0;pointer-events:none;transition:opacity .3s;} .volume .controls .buttons>.volBtn>div{opacity:1;pointer-events:all;} @@ -61,10 +62,14 @@ body:not(.hideLyrics) .lyricsBtn{color:var(--SimAPTheme);opacity:.7;} .list::before,.list::after{content:"";display:block;height:50%;} .hideList .list{transform:scale(.6);opacity:0;pointer-events:none;} body:not(.hideList) .listBtn{color:var(--SimAPTheme);opacity:.7;} -.list>div{width:100%;padding:10px;border-radius:10px;display:flex;align-items:center;transition:background .2s;} +.list>div{width:100%;padding:0 10px;height:80px;border-radius:10px;display:flex;align-items:center;transition:background .2s;} .list>div:hover{background:rgba(0,0,0,.025);} .list>div.active,.list>div:active{background:rgba(0,0,0,.05);} +.list>div.removed{transition:all .3s,opacity .15s;height:0;background:rgba(0,0,0,.025);opacity:0;transform:scaleX(.9) scaleY(.5);} .list>div>img{min-width:60px;height:60px;border-radius:5px;margin-right:10px;background:white;} -.list>div>div{width:calc(100% - 70px);} +.list>div>div{width:calc(100% - 100px);} .list>div>div>b{display:block;width:100%;font-size:1.1em;} -.list>div>div>span{display:block;width:100%;opacity:.8;font-size:.9em;} \ No newline at end of file +.list>div>div>span{display:block;width:100%;opacity:.8;font-size:.9em;} +.list>div i{opacity:.3;width:30px;height:30px;display:flex;align-items:center;justify-content:center;transition:opacity .2s;} +.list>div i:hover{opacity:.8;} +.list>div.active i{opacity:0;pointer-events:none;} diff --git a/src/frontend/assets/components/SimAP.js b/src/frontend/assets/components/SimAP.js index fabeb42..f0c5ab1 100644 --- a/src/frontend/assets/components/SimAP.js +++ b/src/frontend/assets/components/SimAP.js @@ -63,7 +63,7 @@ const switchMusic = (playConfig) => { document.querySelector(".musicInfo>div").innerText = document.querySelector(".musicInfoBottom>div").innerText = playConfig.artist; document.getElementById("audio").src = playConfig.audio; document.getElementById("audio").currentTime = 0; - if (playConfig.play) setTimeout(() => {document.body.classList.add("playing");}); + if (playConfig.play) setTimeout(() => {document.body.classList.add("playing");SimAPControls.loadAudioState();}); SimAPControls.loadLoop(); document.title = playConfig.title + " - SimMusic"; // 初始化背景 @@ -92,8 +92,7 @@ const switchMusic = (playConfig) => { SimAPProgress.setValue(audio.currentTime); SimAPProgressBottom.setValue(audio.currentTime); current.innerText = SimAPTools.formatTime(audio.currentTime); document.body.classList[!audio.paused ? "add" : "remove"]("playing"); - navigator.mediaSession.playbackState = audio.paused ? "paused" : "playing"; - ipcRenderer.invoke(audio.paused ? "musicPause" : "musicPlay"); + SimAPControls.loadAudioState(); }; audio.onended = () => { if (config.getItem("loop") == 1) { audio.duration = 0; audio.play(); } @@ -102,7 +101,6 @@ const switchMusic = (playConfig) => { audio.onerror = () => { document.body.classList.add("withCurrentMusic"); shell.beep(); - setTimeout(() => {SimAPControls.next();}, 5000); }; // 系统级控件 navigator.mediaSession.metadata = new MediaMetadata({ title: playConfig.title, artist: playConfig.artist, artwork: [{ src: playConfig.album }], }); @@ -112,18 +110,27 @@ const switchMusic = (playConfig) => { navigator.mediaSession.setActionHandler("nexttrack", SimAPControls.next); // 初始化歌词 const slrc = new SimLRC(playConfig.lyrics); - slrc.render(document.querySelector(".lyrics>div"), audio, {align: "left", lineSpace: config.getItem("lyricSpace"), activeColor: "var(--SimAPTheme)", normalColor: "rgba(0,0,0,.4)", callback: txt => { - ipcRenderer.invoke("lrcUpdate", audio.currentTime, txt); - }}); + slrc.render(document.querySelector(".lyrics>div"), audio, { + align: "left", + lineSpace: config.getItem("lyricSpace"), + activeColor: "var(--SimAPTheme)", + normalColor: "rgba(0,0,0,.4)", + multiLangSupport: config.getItem("lyricMultiLang"), + callback: txt => { ipcRenderer.invoke("lrcUpdate", txt); } + }); SimAPControls.loadConfig(); }; const SimAPControls = { + loadAudioState() { + const playing = document.body.classList.contains("playing"); + navigator.mediaSession.playbackState = playing ? "playing" : "paused"; + ipcRenderer.invoke(playing ? "musicPlay" : "musicPause"); + }, togglePlay() { document.body.classList[audio.paused ? "add" : "remove"]("playing"); audio[audio.paused ? "play" : "pause"](); - navigator.mediaSession.playbackState = audio.paused ? "paused" : "playing"; - ipcRenderer.invoke(audio.paused ? "musicPause" : "musicPlay"); + SimAPControls.loadAudioState(); }, prev() {this.switchIndex(-1);}, next() {this.switchIndex(1);}, @@ -195,6 +202,7 @@ const SimAPControls = { loadConfig() { document.querySelector(".SimLRC").style.setProperty("--lineSpace", config.getItem("lyricSpace") + "em"); document.querySelector(".lyrics").style.setProperty("--lrcSize", config.getItem("lyricSize") + "em"); + document.querySelector(".lyrics").style.setProperty("--lrcTranslation", config.getItem("lyricTranslation") + "em"); document.body.classList[config.getItem("backgroundBlur") ? "remove" : "add"]("disableBackgroundBlur"); document.body.classList[config.getItem("lyricBlur") ? "remove" : "add"]("disableLyricsBlur"); } @@ -203,6 +211,7 @@ const SimAPControls = { const SimAPUI = { show() { if (this.playingAnimation) return; + if (document.body.classList.contains("playerShown")) return; if (!config.getItem("playList").length || !document.getElementById("album").src) return; document.getElementById("playPage").hidden = false; this.playingAnimation = true; @@ -213,16 +222,24 @@ const SimAPUI = { document.querySelector(".list div.active").scrollIntoView({block: "center"}); document.querySelector(".lyrics div.active").scrollIntoView({block: "center"}); this.playingAnimation = false; + this.toggleDesktopLyrics(); + addEventListener("visibilitychange", this.toggleDesktopLyrics); }, 50); }, hide() { if (this.playingAnimation) return; + if (!document.body.classList.contains("playerShown")) return; document.body.classList.remove("playerShown"); this.playingAnimation = true; setTimeout(() => { + this.toggleDesktopLyrics(); + removeEventListener("visibilitychange", this.toggleDesktopLyrics); document.getElementById("playPage").hidden = true; this.playingAnimation = false; }, 300); + }, + toggleDesktopLyrics() { + if (config.getItem("desktopLyricsAutoHide") && WindowStatus.lyricsWin) ipcRenderer.invoke("toggleLyrics"); } } @@ -246,6 +263,9 @@ document.documentElement.addEventListener("keydown", event => { case "ArrowLeft": audio.currentTime = Math.max(0, audio.currentTime - 5); break; + case "Escape": + SimAPUI.hide(); + break; } }); @@ -261,9 +281,17 @@ const loadVolumeUi = () => { } loadVolumeUi(); config.listenChange("volume", loadVolumeUi); -SimAPVolume.onchange = SimAPVolumeBottom.onchange = value => { config.setItem("volume", value); } +SimAPVolume.ondrag = SimAPVolumeBottom.ondrag = value => { config.setItem("volume", value); } document.body.onpointerdown = () => {document.body.classList.remove("volume");}; document.querySelector(".volBtn").onpointerdown = e => {e.stopPropagation();}; +const handleWheel = e => { + e.preventDefault(); + const value = config.getItem("volume"); + config.setItem("volume", e.deltaY > 0 ? Math.max(0, config.getItem("volume") - .05) : Math.min(1, config.getItem("volume") + .05)); +}; +document.addEventListener("wheel", e => { if (document.body.classList.contains("volume")) handleWheel(e); }, {passive: false}); +document.querySelector(".volBtn").onwheel = () => { document.body.classList.add("volume"); }; +document.querySelector(".volBtnBottom").onwheel = e => { if (!document.body.classList.contains("volume")) handleWheel(e); }; const SimAPProgress = new SimProgress(document.getElementById("progressBar")); @@ -274,3 +302,4 @@ config.listenChange("backgroundBlur", SimAPControls.loadConfig); config.listenChange("lyricBlur", SimAPControls.loadConfig); config.listenChange("lyricSize", SimAPControls.loadConfig); config.listenChange("lyricSpace", SimAPControls.loadConfig); +config.listenChange("lyricTranslation", SimAPControls.loadConfig); diff --git a/src/frontend/assets/components/SimLRC.css b/src/frontend/assets/components/SimLRC.css index aa3afaf..5a75544 100644 --- a/src/frontend/assets/components/SimLRC.css +++ b/src/frontend/assets/components/SimLRC.css @@ -6,3 +6,4 @@ .SimLRC>div.active{color:var(--activeColor);transform:scale(1);margin:var(--lineSpace) .2em;} .SimLRC>div:hover{color:var(--hoverColor);} .SimLRC>div>span,.SimLRC>div>small{display:block;} +.SimLRC>div>small{font-size:var(--lrcTranslation);} \ No newline at end of file diff --git a/src/frontend/assets/components/SimLRC.js b/src/frontend/assets/components/SimLRC.js index 120b362..cb2b29e 100644 --- a/src/frontend/assets/components/SimLRC.js +++ b/src/frontend/assets/components/SimLRC.js @@ -84,7 +84,7 @@ class SimLRC { let div = lrcEles[index]; if (div.dataset.stamp <= currentTime && (!div.nextElementSibling || div.nextElementSibling.dataset.stamp > currentTime)) { // 执行回调 - if (!div.classList.contains("active") && options.callback) options.callback(div.innerText); + if (!div.classList.contains("active") && options.callback) options.callback(div.querySelector("span") ? div.querySelector("span").innerText : div.innerText); if (!div.classList.contains("active") || forceScroll) { // 取消用户滚动模式 if (forceScroll) { @@ -130,6 +130,7 @@ class SimLRC { setTimeout(() => {container.querySelector("div.active").scrollIntoView({block: "center", behavior: "smooth"});}); // 处理用户滚动 const handleUserScroll = () => { + if (document.body.classList.contains("volume")) return; clearTimeout(this.scrollTimeoutId); this.scrollTimeoutId = setTimeout(() => { container.classList.remove("scrolling"); diff --git a/src/frontend/assets/components/SimProgress.js b/src/frontend/assets/components/SimProgress.js index b60c89d..aaae9b2 100644 --- a/src/frontend/assets/components/SimProgress.js +++ b/src/frontend/assets/components/SimProgress.js @@ -13,6 +13,7 @@ class SimProgress { const progress = Math.min(Math.max(clickX / element.clientWidth, 0), 1); element.style.setProperty("--SimProgressWidth", progress * 100 + "%"); this.value = this.min + (this.max - this.min) * progress; + if (this.ondrag) this.ondrag(this.value); } // 鼠标事件 element.addEventListener("mousedown", () => { diff --git a/src/frontend/assets/components/dialog.html b/src/frontend/assets/components/dialog.html index 3e81789..d58287f 100644 --- a/src/frontend/assets/components/dialog.html +++ b/src/frontend/assets/components/dialog.html @@ -53,10 +53,11 @@