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.
Add optional vip?: boolean to the Song interface so downstream clients
(e.g. PowerfulTS) can mark copyright-restricted songs that non-VIP users
can only play as a trial fragment, before playback starts.
- provider.ts: add optional vip?: boolean (backward compatible)
- netease.ts: extract mapNeteaseSongs() pure fn; map fee to vip
(1=VIP, 4=album-only). fee=8 (free low-quality) is excluded because
it plays in full, just at lower quality.
- qq.ts: mapQqSongs() maps pay.payplay/paytrackprice to vip (one fix
covers all callers); getDailyRecommendSongs inline mapping too.
- tests: vip mapping assertions for netease fee (1/4=vip, 0/8=free) and
qq pay fields.