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:
Slldyd2077 committed 2026-06-28 21:06:52 +08:00
1 parent 7b2bd0ea6a
commit fbb127a86d
8 files changed
+90 -16

No files matched your search

+16 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { parseLyrics, mapNeteaseAlbums, mapNeteaseSongs } from "./netease.js";
import { parseLyrics, mapNeteaseAlbums, mapNeteaseSongs, parseNeteaseTrial } from "./netease.js";
describe("NetEase adapter", () => {
it("parses LRC format lyrics", () => {
@@ -77,4 +77,19 @@ describe("NetEase adapter", () => {
]);
expect(out[0]).toMatchObject({ artist: "B", album: "Al2", coverUrl: "p2", vip: false });
});
it("parseNeteaseTrial maps freeTrialInfo to trial seconds", () => {
// 无试听(VIP/免费)
expect(parseNeteaseTrial({})).toBeUndefined();
expect(parseNeteaseTrial({ freeTrialInfo: null })).toBeUndefined();
// 标准秒
expect(parseNeteaseTrial({ freeTrialInfo: { start: 0, end: 30 } })).toBe(30);
expect(parseNeteaseTrial({ freeTrialInfo: { start: 5, end: 35 } })).toBe(30);
// 别名容忍 begin/trialBegin
expect(parseNeteaseTrial({ freeTrialInfo: { begin: 0, end: 30 } })).toBe(30);
// 毫秒兜底(end>1000)
expect(parseNeteaseTrial({ freeTrialInfo: { start: 0, end: 30000 } })).toBe(30);
// 异常 end<=start
expect(parseNeteaseTrial({ freeTrialInfo: { start: 0, end: 0 } })).toBeUndefined();
});
});