feat: 本地音乐播放器
This commit is contained in:
parent
9eb68a69d7
commit
4cd8cfa523
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
port: 3000,
|
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",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
@ -9,12 +9,12 @@
|
|||||||
"test": "tsx test/**/*.ts",
|
"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\" ",
|
"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",
|
"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",
|
"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",
|
"pub": "npm run build && npm run deploy && npm run reload",
|
||||||
"deploy:nova": "rsync -avz --delete ./dist/ --exclude='app.config.json5' nova:~/apps/router/dist",
|
"deploy:nova": "rsync -avz --delete ./dist/ --exclude='app.config.json5' nova:~/apps/music-server/dist",
|
||||||
"start": "pm2 start dist/app.mjs --name router"
|
"start": "pm2 start dist/app.mjs --name music-server"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||||
@ -27,7 +27,7 @@
|
|||||||
"src"
|
"src"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kevisual/router": "^0.0.6-alpha-5",
|
"@kevisual/router": "^0.0.6",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"formidable": "^3.5.2",
|
"formidable": "^3.5.2",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
@ -44,7 +44,7 @@
|
|||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/formidable": "^3.4.5",
|
"@types/formidable": "^3.4.5",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.13.4",
|
"@types/node": "^22.13.5",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"nodemon": "^3.1.9",
|
"nodemon": "^3.1.9",
|
||||||
|
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/router':
|
'@kevisual/router':
|
||||||
specifier: ^0.0.6-alpha-5
|
specifier: ^0.0.6
|
||||||
version: 0.0.6-alpha-5
|
version: 0.0.6
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
@ -55,8 +55,8 @@ importers:
|
|||||||
specifier: ^4.17.12
|
specifier: ^4.17.12
|
||||||
version: 4.17.12
|
version: 4.17.12
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.13.4
|
specifier: ^22.13.5
|
||||||
version: 22.13.4
|
version: 22.13.5
|
||||||
concurrently:
|
concurrently:
|
||||||
specifier: ^9.1.2
|
specifier: ^9.1.2
|
||||||
version: 9.1.2
|
version: 9.1.2
|
||||||
@ -261,8 +261,8 @@ packages:
|
|||||||
'@jridgewell/sourcemap-codec@1.5.0':
|
'@jridgewell/sourcemap-codec@1.5.0':
|
||||||
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||||
|
|
||||||
'@kevisual/router@0.0.6-alpha-5':
|
'@kevisual/router@0.0.6':
|
||||||
resolution: {integrity: sha512-YT9cxzzFKjWyE05MYlvhuAp16ymgmwThSMHrr2PNbmnZiYgUqm3O4j8cny40lOhZB4Jy/4nQb9Ql2laL+mZ4zg==}
|
resolution: {integrity: sha512-7FQUY87Zy5A4V30OAggRbGpO/Asd7SUpnhHv8mlxnSFFTto25xpXmjHYp12mu/HJTsHM7RTaxVEyD1DeP44D2A==}
|
||||||
|
|
||||||
'@kevisual/use-config@1.0.7':
|
'@kevisual/use-config@1.0.7':
|
||||||
resolution: {integrity: sha512-2W1iXdiypugQVgjAz8AGWDVUIcBtegdzLV0FPKq1Rm065yB1EWcI0u0d6qFaAw1RWqtT8o0GT3sR3tzg7nWdjA==}
|
resolution: {integrity: sha512-2W1iXdiypugQVgjAz8AGWDVUIcBtegdzLV0FPKq1Rm065yB1EWcI0u0d6qFaAw1RWqtT8o0GT3sR3tzg7nWdjA==}
|
||||||
@ -493,8 +493,8 @@ packages:
|
|||||||
'@types/node-forge@1.3.11':
|
'@types/node-forge@1.3.11':
|
||||||
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
|
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
|
||||||
|
|
||||||
'@types/node@22.13.4':
|
'@types/node@22.13.5':
|
||||||
resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==}
|
resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==}
|
||||||
|
|
||||||
'@types/resolve@1.20.2':
|
'@types/resolve@1.20.2':
|
||||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||||
@ -1930,7 +1930,7 @@ snapshots:
|
|||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.0': {}
|
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||||
|
|
||||||
'@kevisual/router@0.0.6-alpha-5':
|
'@kevisual/router@0.0.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
path-to-regexp: 8.2.0
|
path-to-regexp: 8.2.0
|
||||||
selfsigned: 2.4.1
|
selfsigned: 2.4.1
|
||||||
@ -2134,16 +2134,16 @@ snapshots:
|
|||||||
|
|
||||||
'@types/formidable@3.4.5':
|
'@types/formidable@3.4.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.4
|
'@types/node': 22.13.5
|
||||||
|
|
||||||
'@types/fs-extra@8.1.5':
|
'@types/fs-extra@8.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.4
|
'@types/node': 22.13.5
|
||||||
|
|
||||||
'@types/glob@7.2.0':
|
'@types/glob@7.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimatch': 5.1.2
|
'@types/minimatch': 5.1.2
|
||||||
'@types/node': 22.13.4
|
'@types/node': 22.13.5
|
||||||
|
|
||||||
'@types/lodash-es@4.17.12':
|
'@types/lodash-es@4.17.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2155,9 +2155,9 @@ snapshots:
|
|||||||
|
|
||||||
'@types/node-forge@1.3.11':
|
'@types/node-forge@1.3.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.4
|
'@types/node': 22.13.5
|
||||||
|
|
||||||
'@types/node@22.13.4':
|
'@types/node@22.13.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.20.0
|
undici-types: 6.20.0
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { App } from '@kevisual/router';
|
import { App } from '@kevisual/router';
|
||||||
import { useContextKey, useContext } from '@kevisual/use-config/context';
|
import { useContextKey, useContext } from '@kevisual/use-config/context';
|
||||||
|
import { MusicService } from './services/index.ts';
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
return new App();
|
return new App();
|
||||||
};
|
};
|
||||||
|
export const musicService = new MusicService();
|
||||||
|
|
||||||
export const app = useContextKey('app', init);
|
export const app = useContextKey('app', init);
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { app } from './app.ts';
|
import { app } from './app.ts';
|
||||||
import { useConfig } from '@kevisual/use-config';
|
import { useConfig } from '@kevisual/use-config';
|
||||||
import './demo-route.ts';
|
import './demo-route.ts';
|
||||||
|
import './routes/index.ts';
|
||||||
|
import { middleware } from './simple-routes/upload.ts';
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
|
|
||||||
app.listen(config.port, () => {
|
app.listen(config.port, () => {
|
||||||
console.log(`server is running at http://localhost:${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