feat: add audio quality selector (standard to jymaster/Hi-Res)

- MusicProvider interface: setQuality/getQuality methods
- NeteaseProvider: passes quality level to /song/url/v1
- Default: exhigh (320kbps MP3)
- Settings page: 6-option grid selector
- GET/POST /api/music/quality endpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.6 committed 2026-03-30 03:37:26 +08:00
1 parent 0439bd047f
commit b74c8e8add
5 files changed
+142 -4

No files matched your search

+22 -2
View File
@@ -54,10 +54,21 @@ export function parseLyrics(lrc: string, tlyric?: string): LyricLine[] {
return lines.sort((a, b) => a.time - b.time);
}
// NetEase quality levels: standard(128k) higher(192k) exhigh(320k) lossless(flac) hires(hi-res) jyeffect jymaster
export const NETEASE_QUALITY_LEVELS = [
{ value: "standard", label: "标准 (128kbps)", bitrate: 128 },
{ value: "higher", label: "较高 (192kbps)", bitrate: 192 },
{ value: "exhigh", label: "极高 (320kbps)", bitrate: 320 },
{ value: "lossless", label: "无损 (FLAC)", bitrate: 900 },
{ value: "hires", label: "Hi-Res", bitrate: 1500 },
{ value: "jymaster", label: "超清母带", bitrate: 4000 },
] as const;
export class NeteaseProvider implements MusicProvider {
readonly platform = "netease" as const;
private api: AxiosInstance;
private cookie = "";
private quality = "exhigh";
constructor(baseUrl: string) {
this.api = axios.create({
@@ -66,6 +77,14 @@ export class NeteaseProvider implements MusicProvider {
});
}
setQuality(quality: string): void {
this.quality = quality;
}
getQuality(): string {
return this.quality;
}
private get cookieParams(): Record<string, string> {
return this.cookie ? { cookie: this.cookie } : {};
}
@@ -110,9 +129,10 @@ export class NeteaseProvider implements MusicProvider {
return { songs, playlists, albums: [] };
}
async getSongUrl(songId: string): Promise<string | null> {
async getSongUrl(songId: string, quality?: string): Promise<string | null> {
const level = quality ?? this.quality;
const res = await this.api.get("/song/url/v1", {
params: { id: songId, level: "exhigh", ...this.cookieParams },
params: { id: songId, level, ...this.cookieParams },
});
return res.data?.data?.[0]?.url ?? null;
}
+3 -1
View File
@@ -57,7 +57,9 @@ export interface MusicProvider {
readonly platform: "netease" | "qq";
search(query: string, limit?: number): Promise<SearchResult>;
getSongUrl(songId: string): Promise<string | null>;
getSongUrl(songId: string, quality?: string): Promise<string | null>;
setQuality(quality: string): void;
getQuality(): string;
getSongDetail(songId: string): Promise<Song | null>;
getPlaylistSongs(playlistId: string): Promise<Song[]>;
getRecommendPlaylists(): Promise<Playlist[]>;
+10 -1
View File
@@ -14,6 +14,7 @@ export class QQMusicProvider implements MusicProvider {
readonly platform = "qq" as const;
private api: AxiosInstance;
private cookie = "";
private quality = "exhigh";
constructor(baseUrl: string) {
this.api = axios.create({
@@ -22,6 +23,14 @@ export class QQMusicProvider implements MusicProvider {
});
}
setQuality(quality: string): void {
this.quality = quality;
}
getQuality(): string {
return this.quality;
}
private get cookieParams(): Record<string, string> {
return this.cookie ? { cookie: this.cookie } : {};
}
@@ -48,7 +57,7 @@ export class QQMusicProvider implements MusicProvider {
return { songs, playlists: [], albums: [] };
}
async getSongUrl(songId: string): Promise<string | null> {
async getSongUrl(songId: string, _quality?: string): Promise<string | null> {
const res = await this.api.get("/getMusicPlay", {
params: { songmid: songId, ...this.cookieParams },
});