Skip to content

Commit dca75f4

Browse files
committed
Sync 0.0.2-alpha
1 parent b68edc5 commit dca75f4

9 files changed

Lines changed: 447 additions & 165 deletions

File tree

src/frontend/assets/components/PublicConfig.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
const defaultConfig = {
3-
folderLists: [],
43
musicLists: {},
54
playList: [],
65
currentMusic: null,
@@ -12,6 +11,8 @@ const defaultConfig = {
1211
lyricBlur: true,
1312
lyricSize: 1.5,
1413
lyricSpace: .5,
14+
extensions: ["assets/extensions/local.json"],
15+
extensionCache: {},
1516
}
1617

1718
const configListeners = {};
@@ -27,7 +28,7 @@ const config = {
2728
const config = JSON.parse(data);
2829
if (config[key] || config[key] === false || config[key] === 0) return config[key];
2930
return defaultConfig[key];
30-
} catch(_ignore) {
31+
} catch {
3132
alert("配置文件损坏,程序将无法正常运行。");
3233
}
3334
},
@@ -42,7 +43,7 @@ const config = {
4243
config[key] = value;
4344
const newConfig = JSON.stringify(config);
4445
localStorage.SimMusicConfig = newConfig;
45-
} catch(_ignore) {
46+
} catch {
4647
alert("配置文件损坏,程序将无法正常运行。");
4748
}
4849
if (configListeners[key]) configListeners[key](value);

src/frontend/assets/components/SimAP.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const switchMusic = (playConfig) => {
6262
document.querySelector(".musicInfo>b").innerText = document.querySelector(".musicInfoBottom>b").innerText = playConfig.title;
6363
document.querySelector(".musicInfo>div").innerText = document.querySelector(".musicInfoBottom>div").innerText = playConfig.artist;
6464
document.getElementById("audio").src = playConfig.audio;
65+
document.getElementById("audio").currentTime = 0;
6566
if (playConfig.play) setTimeout(() => {document.body.classList.add("playing");});
6667
SimAPControls.loadLoop();
6768
document.title = playConfig.title + " - SimMusic";
@@ -72,7 +73,7 @@ const switchMusic = (playConfig) => {
7273
document.getElementById("animationCenter").style.background = `rgb(${themeColors[1] ? themeColors[1].join(",") : "255,255,255"})`;
7374
document.getElementById("animationLeft").style.background = `rgba(${themeColors[2] ? themeColors[2].join(",") : "255,255,255"},.8)`;
7475
document.getElementById("animationRight").style.background = `rgba(${themeColors[3] ? themeColors[3].join(",") : "255,255,255"},.6)`;
75-
const themeColorNum = 255 / (themeColors[0][0] + themeColors[0][1] + themeColors[0][2] + 1);
76+
const themeColorNum = Math.min( 255 / (themeColors[0][0] + themeColors[0][1] + themeColors[0][2] + 1), 1);
7677
document.body.style.setProperty("--SimAPTheme", `rgb(${themeColors[0].map(num => num * themeColorNum).join(",")})`);
7778
}
7879
// 初始化音频控件
@@ -202,6 +203,7 @@ const SimAPControls = {
202203
const SimAPUI = {
203204
show() {
204205
if (this.playingAnimation) return;
206+
if (!config.getItem("playList").length || !document.getElementById("album").src) return;
205207
document.getElementById("playPage").hidden = false;
206208
this.playingAnimation = true;
207209
setTimeout(() => {
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
2+
/*
3+
* SimMusic 内置本地音乐加载器
4+
* 亦可用作扩展开发示例以添加其他音乐源
5+
* 若无特殊说明,基本所有的file变量格式都是“scheme: + <id>”,自己开发时候请不要忘了添加scheme:前缀
6+
*/
7+
8+
9+
/**************** 基础配置 ****************/
10+
// ExtensionRuntime在加载时会自动添加json中的scheme字段到ExtensionConfig下,所以无需担心ExtensionConfig.xxx是否存在
11+
ExtensionConfig.file.uiName = "目录";
12+
// 当没有config.setItem时,调用config.getItem会返回defaultConfig中的值
13+
defaultConfig["folderLists"] = [];
14+
15+
16+
/**************** 工具函数 ****************/
17+
// 这些函数是插件自己需要的函数,个人推荐const一个object然后都用它存放,防止和主程序内置函数名冲突
18+
const FileExtensionTools = {
19+
scanMusic(directory) {
20+
try {
21+
const supportedExtensions = config.getItem("musicFormats").split(" ");
22+
let list = [];
23+
fs.readdirSync(directory).forEach(file => {
24+
const fullPath = path.join(directory, file);
25+
if (fs.statSync(fullPath).isDirectory()) {
26+
list = list.concat(this.scanMusic(fullPath));
27+
} else {
28+
const ext = path.extname(fullPath).toLowerCase();
29+
if (supportedExtensions.includes(ext)) {
30+
// 请务必以插件定义的“scheme:”开头,不然扫描元数据和获取播放链接的时候不知道问哪个插件调方法
31+
// 后面的随意,例如在线歌曲可以使用“xxmusic:114514”,用歌曲id来,你喜欢就好
32+
// 一首歌的内部id应该是唯一的(用于播放和元数据索引),不然歌曲索引的时候会重复请求数据,消耗无意义的资源
33+
list.push("file:" + fullPath);
34+
}
35+
}
36+
});
37+
return list;
38+
} catch { return []; }
39+
}
40+
}
41+
42+
43+
/**************** 左侧导航 ****************/
44+
// 如果你懒,这个字段可以不写,这样插件就没有左侧导航功能(你可以参考下面的写搜索功能)
45+
ExtensionConfig.file.musicList = {
46+
// 这个函数用于处理用户点击歌单“加号”的事件
47+
// 如果没有(例如你的插件是自动同步一个用户的所有歌单),可以不写,这样加号图标就不会显示
48+
add(callback) {
49+
// 这里自己实现添加逻辑,简单输入可直接调内置的 prompt(placeholder:str, callback:function) 方法
50+
ipcRenderer.invoke("pickFolder")
51+
.then(dir => {
52+
if (!dir || !dir[0]) return;
53+
dir = dir[0].trim().replaceAll("/", "\\");
54+
// 内置config读取可用getItem
55+
const lists = config.getItem("folderLists");
56+
// 由于数据格式由开发者自行定义,重复导入 & 其他错误需要开发者自行处理
57+
if (dir.split("\\").length == 2) return alert("您不能导入磁盘根目录。");
58+
if (lists.includes(dir)) return alert("此目录已被添加到目录列表中。");
59+
lists.push(dir);
60+
// 内置config写入可用setItem
61+
config.setItem("folderLists", lists);
62+
// 导入成功后需开发者自行调用callback以更新左侧显示内容(必须),switchList以打开刚才导入的歌单(可选)
63+
callback();
64+
ExtensionConfig.file.musicList.switchList(dir);
65+
});
66+
},
67+
// 这个函数用于渲染左侧的歌单列表
68+
renderList(container) {
69+
const lists = config.getItem("folderLists");
70+
lists.forEach(name => {
71+
const spilitted = name.split("\\");
72+
const folderName = spilitted[spilitted.length - 1];
73+
// 创建一个div即可,可以不需要有类名
74+
const element = document.createElement("div");
75+
element.innerText = folderName;
76+
element.dataset.folderName = name;
77+
// 处理点击,一般直接switchList即可
78+
element.onclick = () => {this.switchList(name);};
79+
// 创建右键菜单,具体使用方法参考 zhujin917/3sqrt7-context-menu/README.md
80+
element.oncontextmenu = event => {
81+
new ContextMenu([
82+
{ label: "查看歌曲", click() {element.click();} },
83+
{ label: "在资源管理器中显示", click() {shell.openPath(name);} },
84+
{ type: "separator" },
85+
{ label: "从列表中移除", click() {
86+
confirm(`目录「${folderName}」将从 SimMusic 目录列表中移除,但不会从文件系统中删除。是否继续?`, () => {
87+
const lists = config.getItem("folderLists");
88+
lists.splice(lists.indexOf(name), 1);
89+
config.setItem("folderLists", lists);
90+
if (element.classList.contains("active")) switchRightPage("rightPlaceholder");
91+
element.remove();
92+
});
93+
} },
94+
]).popup([event.clientX, event.clientY]);
95+
};
96+
// 把div附加到左侧界面,container会由ExtensionRuntime自动传入,无需担心是否存在
97+
container.appendChild(element);
98+
});
99+
},
100+
// 这个函数用于切换歌单
101+
switchList(name) {
102+
const spilitted = name.split("\\");
103+
// 由于搜索和歌单共用界面,需要先隐藏搜索顶栏、显示歌单顶栏,因此这两行请勿删除
104+
document.querySelector(".musicListTitle").hidden = false;
105+
document.querySelector(".searchTitle").hidden = true;
106+
// 这仨是渲染右侧顶部界面使用,分别为标题和目录,如果你的插件没有“目录”的概念(例如在线歌单)把第二行hidden=true然后删掉第三行就可以了
107+
document.getElementById("musicListName").innerText = spilitted[spilitted.length - 1];
108+
document.getElementById("folderDir").hidden = false;
109+
document.getElementById("musicListDir").innerText = name;
110+
// 统一调用renderMusicList即可,第二个参数需要传入一个用于识别“当前歌单”的唯一的参数,推荐使用插件名+歌单id以防重复
111+
// 如果你的scanMusic必须是异步的,可以先renderMusicList([], id)以切换界面,再renderMusicList(list, id),id一样就可以
112+
renderMusicList(FileExtensionTools.scanMusic(name), "folder-" + name);
113+
// 这个用于把当前歌单标蓝,放在renderMusicList函数后运行,推荐借鉴我的写法在renderList函数里自己设一个dataset,然后遍历dataset
114+
document.querySelectorAll(".left .leftBar div").forEach(ele => {
115+
if (ele.dataset.folderName != name) ele.classList.remove("active");
116+
else ele.classList.add("active");
117+
});
118+
},
119+
};
120+
121+
122+
/**************** 获取数据 ****************/
123+
// 这个函数用于读取音乐元数据,不管你是本地还是在线,无所谓你咋获取,最后都调callback(data)就行。
124+
// 如果是在线的用fetch就更好做,直接修改我musicmetadata的promise就得
125+
//【注意:读取失败可以返回null,各字段值可以没有】
126+
ExtensionConfig.file.readMetadata = async (file) => {
127+
file = file.replace("file:", "");
128+
try {
129+
const metadata = await musicMetadata.parseFile(file)
130+
let nativeLyrics;
131+
for (const tagType in metadata.native) {
132+
if (metadata.native[tagType].forEach) metadata.native[tagType].forEach(tag => {
133+
if (tag.value && tag.value.text && tag.value.text.match && tag.value.text.match(/\[\d+\:\d+\.\d+\]/g)) {
134+
nativeLyrics = tag.value.text;
135+
}
136+
});
137+
}
138+
const metadataArtist = metadata.common.artists ? metadata.common.artists.join(", ") : null || metadata.common.artist;
139+
const metadataCover = metadata.common.picture ? metadata.common.picture[0] ? metadata.common.picture[0].data : null : null;
140+
return {
141+
title: metadata.common.title,
142+
artist: metadataArtist,
143+
album: metadata.common.album ? metadata.common.album : file.split("\\")[file.split("\\").length - 2],
144+
time: metadata.format.duration,
145+
cover: metadataCover ? metadataCover : "",
146+
lyrics: metadata.common.lyrics || nativeLyrics,
147+
};
148+
} catch {
149+
return {};
150+
}
151+
};
152+
153+
154+
/**************** 歌曲播放 ****************/
155+
ExtensionConfig.file.player = {
156+
// 这个函数用于获取播放地址,返回值可以是本地文件地址 / http(s)地址 / blob地址 / base64 dataurl,不成功可以用空参数调callback
157+
//【注意:读取失败return可以用空串】
158+
async getPlayUrl(file) {
159+
return file.replace("file:", "");
160+
},
161+
// 这个函数用于(在本地索引没有歌词的情况下获取歌词),例如在线播放时把歌词全部写到索引不太现实,就会调用这个方法直接读取
162+
//【注意:读取失败return可以用空串】
163+
async getLyrics(file) {
164+
file = file.replace("file:", "");
165+
const lastDotIndex = file.lastIndexOf(".");
166+
lrcPath = file.substring(0, lastDotIndex) + ".lrc";
167+
try {return fs.readFileSync(lrcPath, "utf8");}
168+
catch {return "";}
169+
},
170+
};
171+
172+
173+
/**************** 歌曲搜索 ****************/
174+
ExtensionConfig.file.search = async keyword => {
175+
let fileArray = [];
176+
let resultArray = [];
177+
config.getItem("folderLists").forEach(folder => {
178+
fileArray = fileArray.concat(FileExtensionTools.scanMusic(folder));
179+
});
180+
fileArray.forEach(file => {
181+
if (SimMusicTools.getTitleFromPath(file).includes(keyword)) resultArray.push(file);
182+
else if (lastMusicIndex[file]) {
183+
if (Object.values(lastMusicIndex[file]).join(" ").includes(keyword)) resultArray.push(file);
184+
}
185+
});
186+
return resultArray;
187+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "本地歌曲",
3+
"url": "assets/extensions/local.js",
4+
"isDev": true,
5+
"scheme": "file",
6+
"version": "1.0.0"
7+
}

src/frontend/assets/icon-error.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)