feat: 本地音乐播放器
This commit is contained in:
parent
9eb68a69d7
commit
4cd8cfa523
@ -1,3 +1,4 @@
|
||||
{
|
||||
port: 3000,
|
||||
musicDir: '/Users/xion/dev/router-template/music'
|
||||
}
|
14
package.json
14
package.json
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "router",
|
||||
"name": "music-server",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
@ -9,12 +9,12 @@
|
||||
"test": "tsx test/**/*.ts",
|
||||
"dev:watch": "cross-env NODE_ENV=development concurrently -n \"Watch,Dev\" -c \"green,blue\" \"npm run watch\" \"sleep 1 && npm run dev\" ",
|
||||
"build": "rimraf dist && rollup -c rollup.config.mjs",
|
||||
"deploy": "rsync -avz --delete ./dist/ --exclude='app.config.json5' light:~/apps/router/dist",
|
||||
"deploy": "rsync -avz --delete ./dist/ --exclude='app.config.json5' light:~/apps/music-server/dist",
|
||||
"clean": "rm -rf dist",
|
||||
"reload": "ssh light pm2 restart router",
|
||||
"reload": "ssh light pm2 restart music-server",
|
||||
"pub": "npm run build && npm run deploy && npm run reload",
|
||||
"deploy:nova": "rsync -avz --delete ./dist/ --exclude='app.config.json5' nova:~/apps/router/dist",
|
||||
"start": "pm2 start dist/app.mjs --name router"
|
||||
"deploy:nova": "rsync -avz --delete ./dist/ --exclude='app.config.json5' nova:~/apps/music-server/dist",
|
||||
"start": "pm2 start dist/app.mjs --name music-server"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
@ -27,7 +27,7 @@
|
||||
"src"
|
||||
],
|
||||
"dependencies": {
|
||||
"@kevisual/router": "^0.0.6-alpha-5",
|
||||
"@kevisual/router": "^0.0.6",
|
||||
"dayjs": "^1.11.13",
|
||||
"formidable": "^3.5.2",
|
||||
"json5": "^2.2.3",
|
||||
@ -44,7 +44,7 @@
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/formidable": "^3.4.5",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/node": "^22.13.5",
|
||||
"concurrently": "^9.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"nodemon": "^3.1.9",
|
||||
|
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.6-alpha-5
|
||||
version: 0.0.6-alpha-5
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
@ -55,8 +55,8 @@ importers:
|
||||
specifier: ^4.17.12
|
||||
version: 4.17.12
|
||||
'@types/node':
|
||||
specifier: ^22.13.4
|
||||
version: 22.13.4
|
||||
specifier: ^22.13.5
|
||||
version: 22.13.5
|
||||
concurrently:
|
||||
specifier: ^9.1.2
|
||||
version: 9.1.2
|
||||
@ -261,8 +261,8 @@ packages:
|
||||
'@jridgewell/sourcemap-codec@1.5.0':
|
||||
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||
|
||||
'@kevisual/router@0.0.6-alpha-5':
|
||||
resolution: {integrity: sha512-YT9cxzzFKjWyE05MYlvhuAp16ymgmwThSMHrr2PNbmnZiYgUqm3O4j8cny40lOhZB4Jy/4nQb9Ql2laL+mZ4zg==}
|
||||
'@kevisual/router@0.0.6':
|
||||
resolution: {integrity: sha512-7FQUY87Zy5A4V30OAggRbGpO/Asd7SUpnhHv8mlxnSFFTto25xpXmjHYp12mu/HJTsHM7RTaxVEyD1DeP44D2A==}
|
||||
|
||||
'@kevisual/use-config@1.0.7':
|
||||
resolution: {integrity: sha512-2W1iXdiypugQVgjAz8AGWDVUIcBtegdzLV0FPKq1Rm065yB1EWcI0u0d6qFaAw1RWqtT8o0GT3sR3tzg7nWdjA==}
|
||||
@ -493,8 +493,8 @@ packages:
|
||||
'@types/node-forge@1.3.11':
|
||||
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
|
||||
|
||||
'@types/node@22.13.4':
|
||||
resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==}
|
||||
'@types/node@22.13.5':
|
||||
resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==}
|
||||
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
@ -1930,7 +1930,7 @@ snapshots:
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||
|
||||
'@kevisual/router@0.0.6-alpha-5':
|
||||
'@kevisual/router@0.0.6':
|
||||
dependencies:
|
||||
path-to-regexp: 8.2.0
|
||||
selfsigned: 2.4.1
|
||||
@ -2134,16 +2134,16 @@ snapshots:
|
||||
|
||||
'@types/formidable@3.4.5':
|
||||
dependencies:
|
||||
'@types/node': 22.13.4
|
||||
'@types/node': 22.13.5
|
||||
|
||||
'@types/fs-extra@8.1.5':
|
||||
dependencies:
|
||||
'@types/node': 22.13.4
|
||||
'@types/node': 22.13.5
|
||||
|
||||
'@types/glob@7.2.0':
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 22.13.4
|
||||
'@types/node': 22.13.5
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
dependencies:
|
||||
@ -2155,9 +2155,9 @@ snapshots:
|
||||
|
||||
'@types/node-forge@1.3.11':
|
||||
dependencies:
|
||||
'@types/node': 22.13.4
|
||||
'@types/node': 22.13.5
|
||||
|
||||
'@types/node@22.13.4':
|
||||
'@types/node@22.13.5':
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import { useContextKey, useContext } from '@kevisual/use-config/context';
|
||||
import { MusicService } from './services/index.ts';
|
||||
|
||||
const init = () => {
|
||||
return new App();
|
||||
};
|
||||
export const musicService = new MusicService();
|
||||
|
||||
export const app = useContextKey('app', init);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { app } from './app.ts';
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
import './demo-route.ts';
|
||||
import './routes/index.ts';
|
||||
import { middleware } from './simple-routes/upload.ts';
|
||||
const config = useConfig();
|
||||
|
||||
app.listen(config.port, () => {
|
||||
console.log(`server is running at http://localhost:${config.port}`);
|
||||
});
|
||||
app.server.on(middleware);
|
||||
|
38
src/libs/local-music-lib.ts
Normal file
38
src/libs/local-music-lib.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { exec } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
export function playMusic(musicPath: string): void {
|
||||
const musicFilePath = path.resolve(musicPath);
|
||||
|
||||
let command = '';
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
command = `start ${musicFilePath}`; // Windows
|
||||
break;
|
||||
case 'darwin':
|
||||
command = `afplay ${musicFilePath}`; // macOS
|
||||
break;
|
||||
case 'linux':
|
||||
command = `mpg123 ${musicFilePath}`; // Linux
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported platform');
|
||||
}
|
||||
exec(command);
|
||||
}
|
||||
|
||||
export function stopMusic(): void {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
exec('taskkill /IM mpg123.exe /F'); // Windows
|
||||
break;
|
||||
case 'darwin':
|
||||
exec('killall afplay'); // macOS
|
||||
break;
|
||||
case 'linux':
|
||||
exec('killall mpg123'); // Linux
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported platform');
|
||||
}
|
||||
}
|
37
src/libs/music-lib.ts
Normal file
37
src/libs/music-lib.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { exec } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
export function playMusic(musicPath: string): void {
|
||||
const musicFilePath = path.resolve(musicPath);
|
||||
let command = '';
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
command = `start ${musicFilePath}`; // Windows
|
||||
break;
|
||||
case 'darwin':
|
||||
command = `afplay ${musicFilePath}`; // macOS
|
||||
break;
|
||||
case 'linux':
|
||||
command = `mpg123 ${musicFilePath}`; // Linux
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported platform');
|
||||
}
|
||||
exec(command);
|
||||
}
|
||||
|
||||
export function stopMusic(): void {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
exec('taskkill /IM mpg123.exe /F'); // Windows
|
||||
break;
|
||||
case 'darwin':
|
||||
exec('killall afplay'); // macOS
|
||||
break;
|
||||
case 'linux':
|
||||
exec('killall mpg123'); // Linux
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported platform');
|
||||
}
|
||||
}
|
16
src/modules/config.ts
Normal file
16
src/modules/config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import path from 'path';
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
import fs from 'fs';
|
||||
export const configPath = path.join(process.cwd(), 'app.config.json5');
|
||||
|
||||
export const saveConfig = (config: any) => {
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
};
|
||||
|
||||
export const getConfig = () => {
|
||||
return useConfig();
|
||||
};
|
||||
|
||||
export const setConfig = (config: any) => {
|
||||
saveConfig(config);
|
||||
};
|
1
src/routes/index.ts
Normal file
1
src/routes/index.ts
Normal file
@ -0,0 +1 @@
|
||||
import './music/list.ts';
|
39
src/routes/music/list.ts
Normal file
39
src/routes/music/list.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { app, musicService } from '../../app.ts';
|
||||
|
||||
// 配置音乐文件夹
|
||||
// 获取音乐列表
|
||||
console.log(`http://localhost:3000/api/router?path=music&key=musicDirSet&musicDir="C:\\Users\\Administrator\\Music"`);
|
||||
app
|
||||
.route({
|
||||
path: 'music',
|
||||
key: 'musicDirSet',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const musicDir = ctx.query.musicDir;
|
||||
console.log('musicDir', musicDir);
|
||||
|
||||
const res = musicService.setMusicDir(musicDir);
|
||||
if (res.code === 200) {
|
||||
ctx.body = 'success';
|
||||
} else {
|
||||
throw ctx.throw(res.code, res.message);
|
||||
}
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
// 获取音乐列表
|
||||
|
||||
console.log(`http://localhost:3000/api/router?path=music&key=list`);
|
||||
app
|
||||
.route({
|
||||
path: 'music',
|
||||
key: 'list',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const musicList = await musicService.getMusicList();
|
||||
ctx.body = {
|
||||
musicDir: musicService.musicDir,
|
||||
musicList,
|
||||
};
|
||||
})
|
||||
.addTo(app);
|
55
src/services/index.ts
Normal file
55
src/services/index.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { setConfig } from '@/modules/config.ts';
|
||||
import { useConfig, fileIsExist } from '@kevisual/use-config';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// 只获取文件列表
|
||||
const getMusicList = async (musicDir: string) => {
|
||||
const musicList = fs.readdirSync(musicDir);
|
||||
// 只获取文件列表
|
||||
const fileList = musicList.filter((item) => {
|
||||
return fs.statSync(path.join(musicDir, item)).isFile();
|
||||
});
|
||||
return fileList;
|
||||
};
|
||||
|
||||
const config = useConfig();
|
||||
export class MusicService {
|
||||
musicDir: string;
|
||||
constructor() {
|
||||
this.musicDir = config.musicDir || '';
|
||||
}
|
||||
setMusicDir(musicDir: string) {
|
||||
musicDir = musicDir.replace(/"/g, '');
|
||||
const _musicDir = path.resolve(musicDir);
|
||||
if (fileIsExist(_musicDir)) {
|
||||
this.musicDir = musicDir;
|
||||
setConfig({
|
||||
musicDir: _musicDir,
|
||||
});
|
||||
return {
|
||||
code: 200,
|
||||
message: 'musicDir is set success',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
code: 400,
|
||||
message: 'musicDir is not exist',
|
||||
};
|
||||
}
|
||||
}
|
||||
async getMusicList() {
|
||||
const musicList = await getMusicList(this.musicDir);
|
||||
return musicList;
|
||||
}
|
||||
getRealPath(file: string) {
|
||||
// file 如果有空格,则需要转义
|
||||
// 如果有"" 则移除
|
||||
const realPath = path.join(this.musicDir, file.replace(/"/g, ''));
|
||||
console.log('realPath', realPath);
|
||||
if (fileIsExist(realPath)) {
|
||||
return realPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
39
src/simple-routes/upload.ts
Normal file
39
src/simple-routes/upload.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { musicService } from '@/app.ts';
|
||||
import { SimpleRouter } from '@kevisual/router/simple';
|
||||
import fs from 'fs';
|
||||
import http from 'http';
|
||||
export const simpleRouter = new SimpleRouter();
|
||||
|
||||
// http://localhost:3000/files?file="蔡依林 - 倒带.mp3"
|
||||
simpleRouter.get('/files', async (req, res) => {
|
||||
const url = req.url;
|
||||
const urlObj = new URL(url, 'http://localhost:3000');
|
||||
const file = urlObj.searchParams.get('file');
|
||||
console.log(file);
|
||||
if (!file) {
|
||||
res.writeHead(500, { 'Content-Type': 'text/html' });
|
||||
res.write('File params is required\n');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
if (!musicService.musicDir) {
|
||||
res.writeHead(500, { 'Content-Type': 'text/html' });
|
||||
res.write('Music dir is not set\n');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const realPath = musicService.getRealPath(file);
|
||||
if (realPath) {
|
||||
res.writeHead(200, { 'Content-Type': 'audio/mpeg' });
|
||||
const readStream = fs.createReadStream(realPath);
|
||||
readStream.pipe(res);
|
||||
} else {
|
||||
res.writeHead(500, { 'Content-Type': 'text/html' });
|
||||
res.write('File is not exist\n');
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
export const middleware = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
return simpleRouter.parse(req, res);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user