feat: P7 在线播放(播放队列、媒体控制)与自建歌单
- 播放器:统一 PlayItem 队列,顺序 / 列表循环 / 单曲循环 / 随机,滚动歌词含翻译,Media Session,队列与进度存 localStorage - 在线播放音质可在设置中选择;曲库已有的歌优先播放本地文件 - 自建歌单:混排在线曲目与本地文件,拖动排序,一键下载曲库缺失曲目 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-6
@@ -1,19 +1,26 @@
|
||||
// 试听代理:解析出 320K 音源后由服务端转发(支持 Range),绕过防盗链与混合内容
|
||||
// 在线播放代理:按设置的播放音质解析音源后由服务端转发(支持 Range),绕过防盗链与混合内容
|
||||
import { Readable } from 'node:stream';
|
||||
import type { Context } from 'koa';
|
||||
import { UA } from './core/net/http.ts';
|
||||
import { getProvider } from './core/providers/index.ts';
|
||||
import type { QualityKey } from './core/quality.ts';
|
||||
import { TtlCache } from './core/util.ts';
|
||||
import { getSettings } from './settings.ts';
|
||||
|
||||
const urlCache = new TtlCache<{ url: string; ext: string }>(3 * 60 * 1000, 200);
|
||||
// 音源直链通常有几分钟到几小时的有效期;拖动进度条时每个 Range 请求都会命中缓存
|
||||
const urlCache = new TtlCache<{ url: string; ext: string; quality: QualityKey }>(3 * 60 * 1000, 200);
|
||||
const MIME: Record<string, string> = { mp3: 'audio/mpeg', flac: 'audio/flac', m4a: 'audio/mp4', ogg: 'audio/ogg' };
|
||||
|
||||
export async function proxyStream(ctx: Context, source: string, id: string) {
|
||||
const provider = getProvider(source);
|
||||
const audio = await urlCache.getOrSet(`${source}:${id}`, async () => {
|
||||
const r = await provider.resolveAudio(id, 'high');
|
||||
return { url: r.url, ext: r.ext };
|
||||
});
|
||||
const quality = getSettings().playQuality;
|
||||
const audio = await urlCache
|
||||
.getOrSet(`${source}:${id}:${quality}`, async () => {
|
||||
const r = await provider.resolveAudio(id, quality);
|
||||
return { url: r.url, ext: r.ext, quality: r.quality };
|
||||
})
|
||||
// 版权下架等情况很常见,播放器会自动跳过,不必按服务端错误记日志
|
||||
.catch((e: Error) => ctx.throw(404, e.message));
|
||||
|
||||
const controller = new AbortController();
|
||||
ctx.req.on('close', () => controller.abort());
|
||||
@@ -26,6 +33,7 @@ export async function proxyStream(ctx: Context, source: string, id: string) {
|
||||
ctx.set('Content-Type', MIME[audio.ext] || upstream.headers.get('content-type') || 'application/octet-stream');
|
||||
ctx.set('Accept-Ranges', 'bytes');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
ctx.set('X-Audio-Quality', audio.quality);
|
||||
for (const h of ['content-length', 'content-range']) {
|
||||
const v = upstream.headers.get(h);
|
||||
if (v) ctx.set(h, v);
|
||||
|
||||
Reference in New Issue
Block a user