From 1f4be7c10b116ce8a8cc0b4d52c54c287446914f Mon Sep 17 00:00:00 2001
From: HenryDu8133 <813367384@qq.com>
Date: Sat, 1 Aug 2026 07:18:14 +0800
Subject: [PATCH] feat(console): split audio files into music and beep folders
Add an Audio/music path for uploaded music, keep the existing general audio directory as the Beep group, and update the console and music page so the file manager now shows Configuration, Music, Beep, and Station with matching upload and preview routes.
---
CloudServer/public/index.html | 36 +++++++++++++++---
CloudServer/public/music.html | 6 +--
CloudServer/server.js | 71 +++++++++++++++++++++++++++++------
3 files changed, 92 insertions(+), 21 deletions(-)
diff --git a/CloudServer/public/index.html b/CloudServer/public/index.html
index e0db4d4..b271f12 100644
--- a/CloudServer/public/index.html
+++ b/CloudServer/public/index.html
@@ -811,6 +811,8 @@
+
Home
/
@@ -1151,8 +1159,9 @@
if (currentPath === 'root') {
const folders = [
{ id: 'config', name: 'Configuration', count: allFiles.filter(f => f.type === 'config').length },
- { id: 'general', name: 'General Audio', count: allFiles.filter(f => f.type === 'general').length },
- { id: 'station', name: 'Station Audio', count: allFiles.filter(f => f.type === 'station').length }
+ { id: 'music', name: 'Music', count: allFiles.filter(f => f.type === 'music').length },
+ { id: 'beep', name: 'Beep', count: allFiles.filter(f => f.type === 'beep').length },
+ { id: 'station', name: 'Station', count: allFiles.filter(f => f.type === 'station').length }
];
folders.forEach(f => {
@@ -1175,6 +1184,12 @@
if (files.length === 0) container.innerHTML = 'Empty folder
';
files.forEach(f => {
+ const typeLabelMap = {
+ config: 'Configuration',
+ music: 'Music',
+ beep: 'Beep',
+ station: 'Station'
+ };
const row = document.createElement('div');
row.className = 'file-row';
// Click to preview
@@ -1186,7 +1201,7 @@
row.innerHTML = `
${f.name.endsWith('.txt') ? ICONS.file : ICONS.audio}${f.name}
- ${f.type}
+ ${typeLabelMap[f.type] || f.type}
${ICONS.trash}
@@ -1248,7 +1263,8 @@
function triggerUpload() {
if (currentPath === 'config') document.getElementById('upload-config').click();
- else if (currentPath === 'general') document.getElementById('upload-general').click();
+ else if (currentPath === 'music') document.getElementById('upload-music').click();
+ else if (currentPath === 'beep') document.getElementById('upload-general').click();
else if (currentPath === 'station') document.getElementById('upload-station').click();
}
@@ -1347,8 +1363,16 @@
function initAudioPlayer(path) {
if (audioInstance) { audioInstance.pause(); audioInstance = null; }
+ let source = '/' + path;
+ if (path.startsWith('Audio/music/')) {
+ source = `/media/audio/music/${encodeURIComponent(path.slice('Audio/music/'.length))}`;
+ } else if (path.startsWith('Audio/general/')) {
+ source = `/media/audio/general/${encodeURIComponent(path.slice('Audio/general/'.length))}`;
+ } else if (path.startsWith('Audio/station/')) {
+ source = `/media/audio/station/${encodeURIComponent(path.slice('Audio/station/'.length))}`;
+ }
- const audio = new Audio('/' + path);
+ const audio = new Audio(source);
audioInstance = audio;
const btn = document.getElementById('audio-play-btn');
diff --git a/CloudServer/public/music.html b/CloudServer/public/music.html
index fbb3b70..f7d4e34 100644
--- a/CloudServer/public/music.html
+++ b/CloudServer/public/music.html
@@ -180,7 +180,7 @@
未选择
- 选择音乐(来自 /opt/CC-StationBroadcast/Audio/general)
+ 选择音乐(来自 /opt/CC-StationBroadcast/Audio/music)
刷新
@@ -316,7 +316,7 @@
if (tracks.length === 0) {
sel.innerHTML = '(无可用音频) ';
onTrackChange();
- setStatus("没有可用音频(请上传到 /opt/CC-StationBroadcast/Audio/general)");
+ setStatus("没有可用音频(请上传到 /opt/CC-StationBroadcast/Audio/music)");
return;
}
sel.appendChild(new Option("请选择…", ""));
@@ -339,7 +339,7 @@
setStatus(`正在上传 ${file.name}…`);
try {
- const resp = await fetch("/api/upload/general-audio", {
+ const resp = await fetch("/api/upload/music-audio", {
method: "POST",
body: formData
});
diff --git a/CloudServer/server.js b/CloudServer/server.js
index cd40f7d..3b7aaac 100644
--- a/CloudServer/server.js
+++ b/CloudServer/server.js
@@ -39,6 +39,7 @@ if (!fs.existsSync(localHlsDir)) fs.mkdirSync(localHlsDir, { recursive: true });
const uploadDir = path.join(__dirname, 'public', 'uploads');
const audioRootDir = path.join(appRootDir, 'Audio');
+const audioMusicDir = path.join(audioRootDir, 'music');
const audioGeneralDir = path.join(audioRootDir, 'general');
const audioStationDir = path.join(audioRootDir, 'station');
const listFilePath = path.join(appRootDir, 'list.txt');
@@ -48,6 +49,7 @@ const MUSIC_AUDIO_EXTENSIONS = [...STREAMABLE_AUDIO_EXTENSIONS, '.dfpwm'];
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true });
if (!fs.existsSync(audioRootDir)) fs.mkdirSync(audioRootDir, { recursive: true });
+if (!fs.existsSync(audioMusicDir)) fs.mkdirSync(audioMusicDir, { recursive: true });
if (!fs.existsSync(audioGeneralDir)) fs.mkdirSync(audioGeneralDir, { recursive: true });
if (!fs.existsSync(audioStationDir)) fs.mkdirSync(audioStationDir, { recursive: true });
if (!fs.existsSync(tempBroadcastDir)) fs.mkdirSync(tempBroadcastDir, { recursive: true });
@@ -57,6 +59,8 @@ const storage = multer.diskStorage({
destination: function (req, file, cb) {
if (file.fieldname === 'listFile') {
cb(null, appRootDir);
+ } else if (file.fieldname === 'musicAudioFiles') {
+ cb(null, audioMusicDir);
} else if (file.fieldname === 'generalAudioFiles') {
cb(null, audioGeneralDir);
} else if (file.fieldname === 'stationAudioFiles') {
@@ -106,6 +110,10 @@ app.get('/media/audio/general/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
sendBinaryFile(res, path.join(audioGeneralDir, name));
});
+app.get('/media/audio/music/:file', (req, res) => {
+ const name = path.basename(String(req.params.file || ''));
+ sendBinaryFile(res, path.join(audioMusicDir, name));
+});
app.get('/media/audio/station/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
@@ -122,6 +130,7 @@ app.get('/media', (req, res) => {
success: true,
message: 'Use a concrete media path',
routes: [
+ '/media/audio/music/:file',
'/media/audio/general/:file',
'/media/audio/station/:file',
'/media/temp/:file'
@@ -481,15 +490,21 @@ function getManagedMediaUrl(kind, fileName) {
function listMusicTracks() {
const out = new Set();
- if (!fs.existsSync(audioGeneralDir)) return [];
+ const scanDirs = [audioMusicDir];
+ if (fs.existsSync(audioMusicDir) && fs.readdirSync(audioMusicDir).length === 0) {
+ scanDirs.push(audioGeneralDir);
+ }
- for (const file of fs.readdirSync(audioGeneralDir)) {
- if (!file || file.startsWith('.')) continue;
- const full = path.join(audioGeneralDir, file);
- if (!fs.existsSync(full) || !fs.statSync(full).isFile()) continue;
- if (!hasSupportedAudioExtension(file)) continue;
- const base = normalizeBaseName(file);
- if (base) out.add(base);
+ for (const dir of scanDirs) {
+ if (!fs.existsSync(dir)) continue;
+ for (const file of fs.readdirSync(dir)) {
+ if (!file || file.startsWith('.')) continue;
+ const full = path.join(dir, file);
+ if (!fs.existsSync(full) || !fs.statSync(full).isFile()) continue;
+ if (!hasSupportedAudioExtension(file)) continue;
+ const base = normalizeBaseName(file);
+ if (base) out.add(base);
+ }
}
return Array.from(out).sort((a, b) => a.localeCompare(b));
@@ -533,6 +548,7 @@ function getPlaybackAudioByBaseName(baseName) {
for (const ext of STREAMABLE_AUDIO_EXTENSIONS) {
const candidates = [
+ path.join(audioMusicDir, `${base}${ext}`),
path.join(audioGeneralDir, `${base}${ext}`),
path.join(audioStationDir, `${base}${ext}`)
];
@@ -546,13 +562,18 @@ function getPlaybackAudioByBaseName(baseName) {
return {
sourcePath: p,
audioFormat: 'stream',
- streamUrl: getManagedMediaUrl(p.startsWith(audioStationDir) ? 'audio/station' : 'audio/general', path.basename(p)),
+ streamUrl: getManagedMediaUrl(
+ p.startsWith(audioStationDir) ? 'audio/station' :
+ (p.startsWith(audioMusicDir) ? 'audio/music' : 'audio/general'),
+ path.basename(p)
+ ),
durationSec
};
}
}
for (const p of [
+ path.join(audioMusicDir, `${base}.dfpwm`),
path.join(audioGeneralDir, `${base}.dfpwm`),
path.join(audioStationDir, `${base}.dfpwm`)
]) {
@@ -725,7 +746,9 @@ function resolveManagedPath(inputPath) {
}
const prefixes = [
+ { prefix: 'Audio/music/', baseDir: audioMusicDir, logicalPrefix: 'Audio/music/' },
{ prefix: 'Audio/general/', baseDir: audioGeneralDir, logicalPrefix: 'Audio/general/' },
+ { prefix: 'uploads/audio/music/', baseDir: audioMusicDir, logicalPrefix: 'Audio/music/' },
{ prefix: 'uploads/audio/general/', baseDir: audioGeneralDir, logicalPrefix: 'Audio/general/' },
{ prefix: 'Audio/station/', baseDir: audioStationDir, logicalPrefix: 'Audio/station/' },
{ prefix: 'uploads/audio/station/', baseDir: audioStationDir, logicalPrefix: 'Audio/station/' }
@@ -758,6 +781,24 @@ function getManagedFiles() {
});
}
+ if (fs.existsSync(audioMusicDir)) {
+ fs.readdirSync(audioMusicDir).forEach(file => {
+ if (file.endsWith('.dfpwm')) {
+ const wavName = file.replace(/\.dfpwm$/, '.wav');
+ if (fs.existsSync(path.join(audioMusicDir, wavName))) return;
+ }
+ const filePath = path.join(audioMusicDir, file);
+ const stats = fs.statSync(filePath);
+ files.push({
+ name: file,
+ path: `Audio/music/${file}`,
+ type: 'music',
+ size: stats.size,
+ mtime: stats.mtimeMs
+ });
+ });
+ }
+
if (fs.existsSync(audioGeneralDir)) {
fs.readdirSync(audioGeneralDir).forEach(file => {
if (file.endsWith('.dfpwm')) {
@@ -769,7 +810,7 @@ function getManagedFiles() {
files.push({
name: file,
path: `Audio/general/${file}`,
- type: 'general',
+ type: 'beep',
size: stats.size,
mtime: stats.mtimeMs
});
@@ -1847,8 +1888,14 @@ app.post('/api/upload/list', upload.single('listFile'), (req, res) => {
app.post('/api/upload/general-audio', upload.array('generalAudioFiles'), (req, res) => {
const files = req.files || [];
- log('SUCCESS', `Uploaded ${files.length} general audio files`);
- res.json({ success: true, message: `${files.length} general audio files uploaded` });
+ log('SUCCESS', `Uploaded ${files.length} beep audio files`);
+ res.json({ success: true, message: `${files.length} beep audio files uploaded` });
+});
+
+app.post('/api/upload/music-audio', upload.array('musicAudioFiles'), (req, res) => {
+ const files = req.files || [];
+ log('SUCCESS', `Uploaded ${files.length} music audio files`);
+ res.json({ success: true, message: `${files.length} music audio files uploaded` });
});
app.post('/api/upload/station-audio', upload.array('stationAudioFiles'), (req, res) => {