mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 13:02: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
+16
-3
@@ -2,6 +2,7 @@ import axios, { type AxiosInstance } from "axios";
|
||||
import type {
|
||||
MusicProvider,
|
||||
Song,
|
||||
SongUrlResult,
|
||||
Playlist,
|
||||
PlaylistDetail,
|
||||
LyricLine,
|
||||
@@ -58,6 +59,18 @@ export function mapQqSongs(raw: any[] | null | undefined): Song[] {
|
||||
}).filter((s) => s.id);
|
||||
}
|
||||
|
||||
/** 解析 QQ 试听标记 → 试听秒数;非试听(VIP/免费)返回 undefined。
|
||||
* 字段 isTryout===1 / tryout===true + tryBegin/tryEnd;容忍 begin/start 别名 + 毫秒兜底。 */
|
||||
export function parseQqTrial(playUrl: any): number | undefined {
|
||||
if (!playUrl || typeof playUrl !== "object") return undefined;
|
||||
if (playUrl.isTryout !== 1 && playUrl.tryout !== true) return undefined;
|
||||
const begin = Number(playUrl.tryBegin ?? playUrl.begin ?? playUrl.start ?? 0);
|
||||
const end = Number(playUrl.tryEnd ?? playUrl.end);
|
||||
if (!Number.isFinite(end) || end <= begin) return undefined;
|
||||
const secs = end > 1000 ? (end - begin) / 1000 : end - begin;
|
||||
return Math.round(secs);
|
||||
}
|
||||
|
||||
export function mapQqAlbums(raw: any[] | null | undefined): Album[] {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw.map((a) => {
|
||||
@@ -248,13 +261,13 @@ export class QQMusicProvider implements MusicProvider {
|
||||
return { songs, playlists: [], albums };
|
||||
}
|
||||
|
||||
async getSongUrl(songId: string, quality?: string): Promise<string | null> {
|
||||
async getSongUrl(songId: string, quality?: string): Promise<SongUrlResult | null> {
|
||||
try {
|
||||
const res = await this.api.get("/getMusicPlay", {
|
||||
params: { songmid: songId, quality: quality ?? this.quality, ...this.cookieParams },
|
||||
});
|
||||
const playUrl = res.data?.data?.playUrl?.[songId];
|
||||
if (playUrl?.url) return playUrl.url;
|
||||
if (playUrl?.url) return { url: playUrl.url, trialDuration: parseQqTrial(playUrl) };
|
||||
} catch {
|
||||
// try with songid
|
||||
try {
|
||||
@@ -262,7 +275,7 @@ export class QQMusicProvider implements MusicProvider {
|
||||
params: { songid: songId, quality: quality ?? this.quality, ...this.cookieParams },
|
||||
});
|
||||
const playUrl = res.data?.data?.playUrl?.[songId];
|
||||
if (playUrl?.url) return playUrl.url;
|
||||
if (playUrl?.url) return { url: playUrl.url, trialDuration: parseQqTrial(playUrl) };
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user