Skip to content

Commit

Permalink
Sync 0.0.3-alpha
Browse files Browse the repository at this point in the history
  • Loading branch information
YanJi314 committed Aug 13, 2024
1 parent 9f9871e commit 48ffbf2
Show file tree
Hide file tree
Showing 19 changed files with 523 additions and 183 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SimMusic 2024
专注本地音乐播放
高颜值模块化音频播放器

官网 & 软件下载:https://simsv.com/products#/simmusic
# Downloads
https://simsv.com/products#/simmusic
13 changes: 13 additions & 0 deletions src/frontend/assets/components/PublicConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
}
Expand Down
17 changes: 11 additions & 6 deletions src/frontend/assets/components/SimAP.css
Original file line number Diff line number Diff line change
@@ -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;}
Expand Down Expand Up @@ -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;}
Expand 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;}
.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;}
49 changes: 39 additions & 10 deletions src/frontend/assets/components/SimAP.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
// 初始化背景
Expand Down Expand Up @@ -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(); }
Expand All @@ -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 }], });
Expand All @@ -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);},
Expand Down Expand Up @@ -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");
}
Expand All @@ -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;
Expand All @@ -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");
}
}

Expand All @@ -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;
}
});

Expand All @@ -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"));
Expand All @@ -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);
1 change: 1 addition & 0 deletions src/frontend/assets/components/SimLRC.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);}
3 changes: 2 additions & 1 deletion src/frontend/assets/components/SimLRC.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions src/frontend/assets/components/SimProgress.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/assets/components/dialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ <h1>提示</h1>
window.close();
}
document.documentElement.onkeydown = e => {
if (e.ctrlKey && e.key != "a") e.preventDefault();
if ((e.ctrlKey && ["i", "I", "r", "R"].includes(e.key)) || e.key == "Tab") e.preventDefault();
if (document.activeElement.tagName.toLowerCase() != "input") e.preventDefault();
if (e.key == "Enter") submitDialog();
if (e.key == "Escape") cancelDialog();
}
};
</script>
</body>
</html>
3 changes: 2 additions & 1 deletion src/frontend/assets/components/require.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const {ipcRenderer, shell} = require("electron");
const fs = require("fs");
const path = require("path");
const musicMetadata = require('music-metadata');
const musicMetadata = require("music-metadata");
const nodeId3 = require("node-id3");
1 change: 0 additions & 1 deletion src/frontend/assets/desktop.css

This file was deleted.

45 changes: 37 additions & 8 deletions src/frontend/assets/extensions/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,24 @@ const FileExtensionTools = {
});
return list;
} catch { return []; }
}
},
formatTime(ms) {
const totalSeconds = Math.floor(ms / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const milliseconds = ms % 1000;
const formattedMinutes = minutes.toString().padStart(2, '0');
const formattedSeconds = seconds.toString().padStart(2, '0');
const formattedMilliseconds = milliseconds.toString();
return `${formattedMinutes}:${formattedSeconds}.${formattedMilliseconds}`;
},
fileMenuItem: [
{type: ["single"], content: { label: "在资源管理器显示", click() {shell.showItemInFolder(getCurrentSelected()[0])} }}
]
}



/**************** 左侧导航 ****************/
// 如果你懒,这个字段可以不写,这样插件就没有左侧导航功能(你可以参考下面的写搜索功能)
ExtensionConfig.file.musicList = {
Expand All @@ -54,7 +68,7 @@ ExtensionConfig.file.musicList = {
// 内置config读取可用getItem
const lists = config.getItem("folderLists");
// 由于数据格式由开发者自行定义,重复导入 & 其他错误需要开发者自行处理
if (dir.split("\\").length == 2) return alert("您不能导入磁盘根目录。");
if (dir.split("\\").length == 2 && !dir.split("\\")[1]) return alert("您不能导入磁盘根目录。");
if (lists.includes(dir)) return alert("此目录已被添加到目录列表中。");
lists.push(dir);
// 内置config写入可用setItem
Expand Down Expand Up @@ -82,6 +96,10 @@ ExtensionConfig.file.musicList = {
{ label: "查看歌曲", click() {element.click();} },
{ label: "在资源管理器中显示", click() {shell.openPath(name);} },
{ type: "separator" },
{ label: "添加到歌单", submenu: MusicList.getMenuItems(listName => {
MusicList.importToMusicList(listName, FileExtensionTools.scanMusic(name));
MusicList.switchList(listName, true);
}) },
{ label: "从列表中移除", click() {
confirm(`目录「${folderName}」将从 SimMusic 目录列表中移除,但不会从文件系统中删除。是否继续?`, () => {
const lists = config.getItem("folderLists");
Expand Down Expand Up @@ -109,7 +127,7 @@ ExtensionConfig.file.musicList = {
document.getElementById("musicListDir").innerText = name;
// 统一调用renderMusicList即可,第二个参数需要传入一个用于识别“当前歌单”的唯一的参数,推荐使用插件名+歌单id以防重复
// 如果你的scanMusic必须是异步的,可以先renderMusicList([], id)以切换界面,再renderMusicList(list, id),id一样就可以
renderMusicList(FileExtensionTools.scanMusic(name), "folder-" + name);
renderMusicList(FileExtensionTools.scanMusic(name), "folder-" + name, false, false, "当前目录为空", FileExtensionTools.fileMenuItem);
// 这个用于把当前歌单标蓝,放在renderMusicList函数后运行,推荐借鉴我的写法在renderList函数里自己设一个dataset,然后遍历dataset
document.querySelectorAll(".left .leftBar div").forEach(ele => {
if (ele.dataset.folderName != name) ele.classList.remove("active");
Expand All @@ -126,7 +144,7 @@ ExtensionConfig.file.musicList = {
ExtensionConfig.file.readMetadata = async (file) => {
file = file.replace("file:", "");
try {
const metadata = await musicMetadata.parseFile(file)
const metadata = await musicMetadata.parseFile(file);
let nativeLyrics;
for (const tagType in metadata.native) {
if (metadata.native[tagType].forEach) metadata.native[tagType].forEach(tag => {
Expand All @@ -143,7 +161,7 @@ ExtensionConfig.file.readMetadata = async (file) => {
album: metadata.common.album ? metadata.common.album : file.split("\\")[file.split("\\").length - 2],
time: metadata.format.duration,
cover: metadataCover ? metadataCover : "",
lyrics: metadata.common.lyrics || nativeLyrics,
lyrics: nativeLyrics ? nativeLyrics : "",
};
} catch {
return {};
Expand All @@ -165,7 +183,16 @@ ExtensionConfig.file.player = {
const lastDotIndex = file.lastIndexOf(".");
lrcPath = file.substring(0, lastDotIndex) + ".lrc";
try {return fs.readFileSync(lrcPath, "utf8");}
catch {return "";}
catch {
let id3Lyrics = "";
const id3LyricsArray = await nodeId3.Promise.read(file);
if (id3LyricsArray && id3LyricsArray.synchronisedLyrics && id3LyricsArray.synchronisedLyrics[0]) {
id3LyricsArray.synchronisedLyrics[0].synchronisedText.forEach(obj => {
id3Lyrics += `[${FileExtensionTools.formatTime(obj.timeStamp)}]${obj.text}\n`;
});
}
return id3Lyrics;
}
},
};

Expand All @@ -180,8 +207,10 @@ ExtensionConfig.file.search = async keyword => {
fileArray.forEach(file => {
if (SimMusicTools.getTitleFromPath(file).includes(keyword)) resultArray.push(file);
else if (lastMusicIndex[file]) {
if (Object.values(lastMusicIndex[file]).join(" ").includes(keyword)) resultArray.push(file);
const songInfo = lastMusicIndex[file];
const songInfoString = songInfo.title + songInfo.album + songInfo.artist;
if (songInfoString.includes(keyword)) resultArray.push(file);
}
});
return resultArray;
return {files: resultArray, menu: FileExtensionTools.fileMenuItem};
}
2 changes: 1 addition & 1 deletion src/frontend/assets/extensions/local.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"url": "assets/extensions/local.js",
"isDev": true,
"scheme": "file",
"version": "1.0.0"
"version": "1.0.3"
}
Loading

0 comments on commit 48ffbf2

Please sign in to comment.