Skip to content

Commit 5d8a764

Browse files
committed
feat: support 302 redirect music URLs in player
1 parent c435ed1 commit 5d8a764

1 file changed

Lines changed: 64 additions & 4 deletions

File tree

src/components/widget/MusicPlayer.svelte

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,59 @@
134134
let audio: HTMLAudioElement;
135135
let progressBar: HTMLElement;
136136
let volumeBar: HTMLElement;
137+
let loadRequestId = 0;
138+
const resolvedUrlCache = new Map<string, string>();
139+
140+
function isHttpUrl(url: string): boolean {
141+
return url.startsWith("http://") || url.startsWith("https://");
142+
}
143+
144+
async function resolvePlayableUrl(url: string): Promise<string> {
145+
const normalizedUrl = getAssetPath(url);
146+
147+
// 本地资源不需要做重定向解析
148+
if (!isHttpUrl(normalizedUrl)) {
149+
return normalizedUrl;
150+
}
151+
152+
const cachedUrl = resolvedUrlCache.get(normalizedUrl);
153+
if (cachedUrl) {
154+
return cachedUrl;
155+
}
156+
157+
// 先走自动重定向,拿到最终 response.url
158+
try {
159+
const followed = await fetch(normalizedUrl, {
160+
method: "GET",
161+
redirect: "follow",
162+
cache: "no-store",
163+
mode: "cors",
164+
});
165+
if (followed.ok && followed.url) {
166+
resolvedUrlCache.set(normalizedUrl, followed.url);
167+
return followed.url;
168+
}
169+
} catch {}
170+
171+
// 再尝试手动重定向读取 location 头(部分 API 允许)
172+
try {
173+
const manual = await fetch(normalizedUrl, {
174+
method: "GET",
175+
redirect: "manual",
176+
cache: "no-store",
177+
mode: "cors",
178+
});
179+
const location = manual.headers.get("location");
180+
if (location) {
181+
const finalUrl = new URL(location, normalizedUrl).href;
182+
resolvedUrlCache.set(normalizedUrl, finalUrl);
183+
return finalUrl;
184+
}
185+
} catch {}
186+
187+
// 解析失败时回退原始地址,让 audio 自己处理重定向
188+
return normalizedUrl;
189+
}
137190
138191
async function fetchMetingPlaylist() {
139192
if (!meting_api || !meting_id) return;
@@ -251,12 +304,12 @@
251304
playSong(newIndex);
252305
}
253306
254-
function playSong(index: number) {
307+
async function playSong(index: number) {
255308
if (index < 0 || index >= playlist.length) return;
256309
const wasPlaying = isPlaying;
257310
currentIndex = index;
258311
if (audio) audio.pause();
259-
loadSong(playlist[currentIndex]);
312+
await loadSong(playlist[currentIndex]);
260313
if (wasPlaying || !isPlaying) {
261314
setTimeout(() => {
262315
if (!audio) return;
@@ -288,9 +341,10 @@
288341
}
289342
290343
291-
function loadSong(song: typeof currentSong) {
344+
async function loadSong(song: typeof currentSong) {
292345
if (!song || !audio) return;
293346
currentSong = { ...song };
347+
const currentRequestId = ++loadRequestId;
294348
295349
// 如果没有封面,使用默认封面
296350
if (!currentSong.cover) {
@@ -309,7 +363,13 @@
309363
audio.addEventListener("loadeddata", handleLoadSuccess, { once: true });
310364
audio.addEventListener("error", handleLoadError, { once: true });
311365
audio.addEventListener("loadstart", handleLoadStart, { once: true });
312-
audio.src = getAssetPath(song.url);
366+
367+
const playableUrl = await resolvePlayableUrl(song.url);
368+
369+
// 避免切歌时旧请求覆盖当前歌曲
370+
if (currentRequestId !== loadRequestId) return;
371+
372+
audio.src = playableUrl;
313373
audio.load();
314374
} else {
315375
isLoading = false;

0 commit comments

Comments
 (0)