676541457b
- 播放器:统一 PlayItem 队列,顺序 / 列表循环 / 单曲循环 / 随机,滚动歌词含翻译,Media Session,队列与进度存 localStorage - 在线播放音质可在设置中选择;曲库已有的歌优先播放本地文件 - 自建歌单:混排在线曲目与本地文件,拖动排序,一键下载曲库缺失曲目 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import type { QualityKey, Source } from './api.ts';
|
|
|
|
export const SOURCE_LABEL: Record<Source, string> = { netease: '网易云', qq: 'QQ 音乐' };
|
|
|
|
export const QUALITY_BADGE: Record<QualityKey, string> = {
|
|
standard: '128K',
|
|
high: '320K',
|
|
lossless: 'FLAC',
|
|
hires: 'HI-RES',
|
|
master: 'MASTER',
|
|
spatial: 'SPATIAL',
|
|
effect: 'EFFECT',
|
|
};
|
|
|
|
export const QUALITY_LABEL: Record<QualityKey, string> = {
|
|
standard: '标准',
|
|
high: '极高',
|
|
lossless: '无损',
|
|
hires: 'Hi-Res',
|
|
master: '超清母带',
|
|
spatial: '空间音频',
|
|
effect: '高清臻品',
|
|
};
|
|
|
|
const QUALITY_RANK: QualityKey[] = ['effect', 'spatial', 'master', 'hires', 'lossless', 'high', 'standard'];
|
|
|
|
export function isGold(q: string | null | undefined): boolean {
|
|
return q === 'hires' || q === 'master' || q === 'spatial' || q === 'effect';
|
|
}
|
|
|
|
/** 该曲目声明的最佳音质(用于徽章) */
|
|
export function bestQuality(qs: QualityKey[] | undefined): QualityKey | undefined {
|
|
if (!qs?.length) return undefined;
|
|
return QUALITY_RANK.find((q) => qs.includes(q));
|
|
}
|
|
|
|
export function formatDuration(ms?: number | null): string {
|
|
if (!ms) return '--:--';
|
|
const s = Math.round(ms / 1000);
|
|
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
|
|
}
|
|
|
|
/** 总时长:1 小时 12 分 / 38 分钟 */
|
|
export function formatTotal(ms?: number | null): string {
|
|
if (!ms) return '';
|
|
const min = Math.round(ms / 60000);
|
|
return min >= 60 ? `${Math.floor(min / 60)} 小时 ${min % 60} 分` : `${Math.max(1, min)} 分钟`;
|
|
}
|
|
|
|
export function formatSeconds(sec: number): string {
|
|
if (!Number.isFinite(sec)) return '0:00';
|
|
const s = Math.floor(sec);
|
|
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
|
|
}
|
|
|
|
export function formatSize(bytes?: number | null): string {
|
|
if (!bytes) return '—';
|
|
if (bytes >= 1073741824) return `${(bytes / 1073741824).toFixed(2)} GB`;
|
|
if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
|
}
|
|
|
|
export function formatSpeed(bps: number): string {
|
|
return bps >= 1048576 ? `${(bps / 1048576).toFixed(1)} MB/s` : `${Math.round(bps / 1024)} KB/s`;
|
|
}
|
|
|
|
export function formatDate(ts?: number | string | null): string {
|
|
if (!ts) return '';
|
|
const d = new Date(ts);
|
|
return Number.isNaN(d.getTime()) ? '' : `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
}
|
|
|
|
const dtf = new Intl.DateTimeFormat('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
|
export function formatDateTime(ts: string): string {
|
|
return dtf.format(new Date(ts));
|
|
}
|
|
|
|
export function coverThumb(url: string | undefined | null, size = 120): string | undefined {
|
|
if (!url) return undefined;
|
|
const https = url.replace(/^http:/, 'https:');
|
|
if (/music\.126\.net/.test(https)) return `${https.split('?')[0]}?param=${size}y${size}`;
|
|
if (/y\.gtimg\.cn\/music\/photo_new\/T00\dR\d+x\d+/.test(https)) {
|
|
const s = size <= 150 ? 150 : size <= 300 ? 300 : 800;
|
|
return https.replace(/R\d+x\d+/, `R${s}x${s}`);
|
|
}
|
|
return https;
|
|
}
|
|
|
|
export const STAGE_LABEL: Record<string, string> = {
|
|
resolve: '解析音源',
|
|
metadata: '获取信息',
|
|
download: '下载中',
|
|
lyric: '保存歌词',
|
|
cover: '获取封面',
|
|
tag: '写入标签',
|
|
};
|