mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 21:12:49 +08:00
feat: resolve trial-only playback via trialDuration/effectiveDuration
VIP songs for non-VIP accounts return a ~30s trial fragment. The player used the full duration for isNearEnd, so the trial end didn't trigger auto-advance (~60s stall), and currentSong.duration stayed full, leaving the UI progress stuck.
- provider.ts: SongUrlResult {url, trialDuration?}; getSongUrl signature
- netease.ts: parseNeteaseTrial (freeTrialInfo start/end in seconds) + getSongUrl
- qq.ts: parseQqTrial (isTryout/tryEnd) + getSongUrl
- bilibili/youtube: getSongUrl returns {url}
- instance.ts: resolveAndPlay uses effectiveDuration = trialDuration ?? duration -> nearEnd at trial end -> native auto-advance; BotStatus.effectiveDuration
- VIP account: freeTrialInfo absent -> full duration -> full playback (no toggle)
Backward compatible (optional fields; getSongUrl has a single caller, updated).
Tests: parseTrial assertions (seconds/alias/ms-fallback). 14 pass.
This commit is contained in:
1 parent
7b2bd0ea6a
commit
fbb127a86d
8 files changed
+90
-16
No files matched your search
+18
-2
@@ -2,6 +2,7 @@ import axios, { type AxiosInstance } from "axios";
|
||||
import type {
|
||||
MusicProvider,
|
||||
Song,
|
||||
SongUrlResult,
|
||||
Playlist,
|
||||
PlaylistDetail,
|
||||
LyricLine,
|
||||
@@ -83,6 +84,18 @@ export function mapNeteaseSongs(raw: any[] | null | undefined): Song[] {
|
||||
}));
|
||||
}
|
||||
|
||||
/** 解析网易云 freeTrialInfo → 试听秒数;无片段(VIP/免费)返回 undefined。
|
||||
* 真实字段 {start,end} 单位秒;容忍 begin/trialBegin 别名 + 毫秒兜底(end>1000)。 */
|
||||
export function parseNeteaseTrial(item: any): number | undefined {
|
||||
const t = item?.freeTrialInfo;
|
||||
if (!t || typeof t !== "object") return undefined;
|
||||
const start = Number(t.start ?? t.begin ?? t.trialBegin ?? 0);
|
||||
const end = Number(t.end ?? t.trialEnd);
|
||||
if (!Number.isFinite(end) || end <= start) return undefined;
|
||||
const secs = end > 1000 ? (end - start) / 1000 : end - start;
|
||||
return Math.round(secs);
|
||||
}
|
||||
|
||||
// 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 },
|
||||
@@ -153,12 +166,15 @@ export class NeteaseProvider implements MusicProvider {
|
||||
return { songs, playlists, albums };
|
||||
}
|
||||
|
||||
async getSongUrl(songId: string, quality?: string): Promise<string | null> {
|
||||
async getSongUrl(songId: string, quality?: string): Promise<SongUrlResult | null> {
|
||||
const level = quality ?? this.quality;
|
||||
const res = await this.api.get("/song/url/v1", {
|
||||
params: { id: songId, level, ...this.cookieParams },
|
||||
});
|
||||
return res.data?.data?.[0]?.url ?? null;
|
||||
const item = res.data?.data?.[0];
|
||||
const url = item?.url;
|
||||
if (!url) return null;
|
||||
return { url, trialDuration: parseNeteaseTrial(item) };
|
||||
}
|
||||
|
||||
async getSongDetail(songId: string): Promise<Song | null> {
|
||||
|
||||
Reference in new issue
Block a user