修复音乐设备同步
This commit is contained in:
@@ -388,6 +388,16 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加缓存支持
|
||||||
|
const qualityKey = quality || '320k';
|
||||||
|
const idsKey = Array.isArray(ids) ? ids.join(',') : ids;
|
||||||
|
const cacheKey = `parse-${platform}-${idsKey}-${qualityKey}`;
|
||||||
|
const cached = serverCache.proxyRequests.get(cacheKey);
|
||||||
|
|
||||||
|
if (cached && Date.now() - cached.timestamp < serverCache.CACHE_DURATION) {
|
||||||
|
return NextResponse.json(cached.data);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await proxyRequest(`${baseUrl}/v1/parse`, {
|
const response = await proxyRequest(`${baseUrl}/v1/parse`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -398,7 +408,7 @@ export async function POST(request: NextRequest) {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
platform,
|
platform,
|
||||||
ids,
|
ids,
|
||||||
quality: quality || '320k',
|
quality: qualityKey,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -414,6 +424,9 @@ export async function POST(request: NextRequest) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 缓存成功的解析结果
|
||||||
|
serverCache.proxyRequests.set(cacheKey, { data, timestamp: Date.now() });
|
||||||
|
|
||||||
return NextResponse.json(data);
|
return NextResponse.json(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解析歌曲失败:', error);
|
console.error('解析歌曲失败:', error);
|
||||||
|
|||||||
+141
-120
@@ -40,6 +40,18 @@ interface Playlist {
|
|||||||
updateFrequency?: string;
|
updateFrequency?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DbRecord {
|
||||||
|
platform: 'netease' | 'qq' | 'kuwo';
|
||||||
|
id: string;
|
||||||
|
play_time: number;
|
||||||
|
duration: number;
|
||||||
|
save_time: number;
|
||||||
|
name: string;
|
||||||
|
artist: string;
|
||||||
|
album?: string;
|
||||||
|
pic?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function MusicPage() {
|
export default function MusicPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [currentSource, setCurrentSource] = useState<'netease' | 'qq' | 'kuwo'>('netease');
|
const [currentSource, setCurrentSource] = useState<'netease' | 'qq' | 'kuwo'>('netease');
|
||||||
@@ -115,63 +127,119 @@ export default function MusicPage() {
|
|||||||
localStorage.setItem('musicPlayState', JSON.stringify(playState));
|
localStorage.setItem('musicPlayState', JSON.stringify(playState));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从 localStorage 恢复播放状态
|
// 从 localStorage 恢复播放状态(已废弃,现在统一使用数据库)
|
||||||
const restorePlayState = async () => {
|
const restorePlayState = async () => {
|
||||||
try {
|
// 此函数已不再使用,所有状态恢复都在 initializePlayState 中完成
|
||||||
const saved = localStorage.getItem('musicPlayState');
|
};
|
||||||
if (!saved) return;
|
|
||||||
|
|
||||||
const playState = JSON.parse(saved);
|
// 页面加载时恢复播放状态和数据库记录
|
||||||
|
useEffect(() => {
|
||||||
|
const initializePlayState = async () => {
|
||||||
|
try {
|
||||||
|
console.log('=== 开始同步加载数据库记录 ===');
|
||||||
|
|
||||||
setCurrentSong(playState.currentSong);
|
// 1. 直接从 API 同步加载播放记录(阻塞等待,不使用缓存)
|
||||||
setCurrentSongIndex(playState.currentSongIndex);
|
const response = await fetch('/api/music/playrecords');
|
||||||
setSongs(playState.songs || []);
|
const dbRecords = await response.json();
|
||||||
setCurrentPlaylistTitle(playState.currentPlaylistTitle || '');
|
|
||||||
setCurrentSource(playState.currentSource || 'netease');
|
|
||||||
setCurrentView(playState.currentView || 'playlists');
|
|
||||||
setQuality(playState.quality || '320k');
|
|
||||||
setPlayMode(playState.playMode || 'loop');
|
|
||||||
setVolume(playState.volume || 100);
|
|
||||||
setLyrics(playState.lyrics || []);
|
|
||||||
setPlayRecords(playState.playRecords || []);
|
|
||||||
setPlaylist(playState.playlist || []); // 恢复播放列表
|
|
||||||
|
|
||||||
// 恢复 playlistIndex,如果没有则设置为 -1
|
console.log('=== 数据库原始数据 ===');
|
||||||
const restoredIndex = playState.playlistIndex ?? -1;
|
console.log('dbRecords:', dbRecords);
|
||||||
setPlaylistIndex(restoredIndex);
|
|
||||||
|
|
||||||
// 保存需要恢复的时间点
|
// 将数据库记录转换为前端格式
|
||||||
restoredTimeRef.current = playState.currentTime || 0;
|
const records: PlayRecord[] = [];
|
||||||
|
const songs: Song[] = [];
|
||||||
|
|
||||||
if (playState.currentSong) {
|
Object.entries(dbRecords).forEach(([key, record]) => {
|
||||||
setShowPlayer(true);
|
const dbRecord = record as DbRecord;
|
||||||
|
console.log('处理记录:', key, dbRecord);
|
||||||
|
records.push({
|
||||||
|
platform: dbRecord.platform,
|
||||||
|
id: dbRecord.id,
|
||||||
|
playTime: dbRecord.play_time,
|
||||||
|
duration: dbRecord.duration,
|
||||||
|
timestamp: dbRecord.save_time,
|
||||||
|
});
|
||||||
|
|
||||||
// 记录歌曲开始播放的时间(恢复时也需要设置)
|
songs.push({
|
||||||
songStartTimeRef.current = Date.now();
|
id: dbRecord.id,
|
||||||
|
name: dbRecord.name,
|
||||||
|
artist: dbRecord.artist,
|
||||||
|
album: dbRecord.album,
|
||||||
|
pic: dbRecord.pic,
|
||||||
|
platform: dbRecord.platform,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 获取歌曲的平台信息
|
// 按 save_time 倒序排序
|
||||||
const platform = playState.currentSong.platform || playState.currentSource || 'netease';
|
const sortedIndices = records
|
||||||
|
.map((record, index) => ({ record, index }))
|
||||||
|
.sort((a, b) => b.record.timestamp - a.record.timestamp);
|
||||||
|
|
||||||
// 重新解析歌曲获取新的播放链接
|
const sortedRecords = sortedIndices.map(item => records[item.index]);
|
||||||
try {
|
const sortedSongs = sortedIndices.map(item => songs[item.index]);
|
||||||
const response = await fetch('/api/music', {
|
|
||||||
|
// 2. 更新播放列表
|
||||||
|
if (sortedRecords.length > 0) {
|
||||||
|
setPlayRecords(sortedRecords);
|
||||||
|
setPlaylist(sortedSongs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 获取 localStorage 配置(只获取配置,不获取歌曲信息)
|
||||||
|
const savedPlayState = localStorage.getItem('musicPlayState');
|
||||||
|
const playState = savedPlayState ? JSON.parse(savedPlayState) : {};
|
||||||
|
|
||||||
|
// 恢复配置状态(不包括歌曲)
|
||||||
|
setSongs(playState.songs || []);
|
||||||
|
setCurrentPlaylistTitle(playState.currentPlaylistTitle || '');
|
||||||
|
setCurrentSource(playState.currentSource || 'netease');
|
||||||
|
setCurrentView(playState.currentView || 'playlists');
|
||||||
|
setQuality(playState.quality || '320k');
|
||||||
|
setPlayMode(playState.playMode || 'loop');
|
||||||
|
setVolume(playState.volume || 100);
|
||||||
|
|
||||||
|
// 4. 使用数据库的最新记录(歌曲和进度都从数据库获取)
|
||||||
|
if (sortedRecords.length > 0) {
|
||||||
|
const latestDbRecord = sortedRecords[0];
|
||||||
|
const latestDbSong = sortedSongs[0];
|
||||||
|
|
||||||
|
console.log('=== 从数据库加载歌曲 ===');
|
||||||
|
console.log('数据库最新歌曲:', latestDbSong);
|
||||||
|
console.log('数据库播放进度:', latestDbRecord.playTime);
|
||||||
|
console.log('数据库保存时间:', new Date(latestDbRecord.timestamp).toLocaleString());
|
||||||
|
|
||||||
|
// 使用数据库的歌曲信息
|
||||||
|
setCurrentSong(latestDbSong);
|
||||||
|
setPlaylistIndex(0);
|
||||||
|
setShowPlayer(true);
|
||||||
|
|
||||||
|
// 从数据库恢复播放进度
|
||||||
|
const dbPlayTime = latestDbRecord.playTime || 0;
|
||||||
|
songStartTimeRef.current = Date.now();
|
||||||
|
|
||||||
|
// 5. 同步解析歌曲获取播放链接(阻塞等待)
|
||||||
|
const platform = latestDbSong.platform || 'netease';
|
||||||
|
|
||||||
|
console.log('开始解析歌曲:', platform, latestDbSong.id);
|
||||||
|
|
||||||
|
const parseResponse = await fetch('/api/music', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
action: 'parse',
|
action: 'parse',
|
||||||
platform: platform,
|
platform: platform,
|
||||||
ids: playState.currentSong.id,
|
ids: latestDbSong.id,
|
||||||
quality: playState.quality || '320k',
|
quality: playState.quality || '320k',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await parseResponse.json();
|
||||||
|
|
||||||
|
console.log('解析结果:', data);
|
||||||
|
|
||||||
if (data.code === 0 && data.data?.data && data.data.data.length > 0) {
|
if (data.code === 0 && data.data?.data && data.data.data.length > 0) {
|
||||||
const songData = data.data.data[0];
|
const songData = data.data.data[0];
|
||||||
|
|
||||||
if (songData.url && songData.success) {
|
if (songData.url && songData.success) {
|
||||||
// 对于酷我音乐,使用代理
|
|
||||||
let playUrl = songData.url;
|
let playUrl = songData.url;
|
||||||
if (platform === 'kuwo') {
|
if (platform === 'kuwo') {
|
||||||
playUrl = `/api/music/proxy?url=${encodeURIComponent(songData.url)}`;
|
playUrl = `/api/music/proxy?url=${encodeURIComponent(songData.url)}`;
|
||||||
@@ -179,87 +247,27 @@ export default function MusicPage() {
|
|||||||
|
|
||||||
setCurrentSongUrl(songData.url);
|
setCurrentSongUrl(songData.url);
|
||||||
|
|
||||||
// 延迟设置音频源,等待 audio 元素加载
|
if (songData.lyrics) {
|
||||||
setTimeout(() => {
|
const parsedLyrics = parseLyric(songData.lyrics);
|
||||||
if (audioRef.current) {
|
setLyrics(parsedLyrics);
|
||||||
audioRef.current.src = playUrl;
|
}
|
||||||
|
|
||||||
// 监听多个事件以确保进度恢复
|
console.log('设置音频源:', playUrl);
|
||||||
const restoreTime = () => {
|
console.log('恢复播放进度:', dbPlayTime);
|
||||||
if (audioRef.current && restoredTimeRef.current > 0) {
|
|
||||||
audioRef.current.currentTime = restoredTimeRef.current;
|
|
||||||
restoredTimeRef.current = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听加载完成事件
|
// 6. 等待所有数据准备好后,再设置音频源和进度
|
||||||
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
if (audioRef.current) {
|
||||||
audioRef.current.addEventListener('canplay', restoreTime, { once: true });
|
audioRef.current.src = playUrl;
|
||||||
|
|
||||||
// 调用 load() 触发音频加载
|
const restoreTime = () => {
|
||||||
audioRef.current.load();
|
if (audioRef.current && dbPlayTime > 0) {
|
||||||
}
|
audioRef.current.currentTime = dbPlayTime;
|
||||||
}, 100);
|
console.log('播放进度已恢复:', audioRef.current.currentTime);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} catch (error) {
|
|
||||||
console.error('重新解析歌曲失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('恢复播放状态失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 页面加载时恢复播放状态和数据库记录
|
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
||||||
useEffect(() => {
|
audioRef.current.load();
|
||||||
const initializePlayState = async () => {
|
|
||||||
// 先恢复 localStorage 中的播放状态
|
|
||||||
restorePlayState();
|
|
||||||
|
|
||||||
// 从数据库加载播放记录
|
|
||||||
try {
|
|
||||||
const dbRecords = await getAllMusicPlayRecords();
|
|
||||||
|
|
||||||
// 将数据库记录转换为前端格式
|
|
||||||
const records: PlayRecord[] = [];
|
|
||||||
const songs: Song[] = [];
|
|
||||||
|
|
||||||
Object.entries(dbRecords).forEach(([key, record]) => {
|
|
||||||
records.push({
|
|
||||||
platform: record.platform,
|
|
||||||
id: record.id,
|
|
||||||
playTime: record.play_time,
|
|
||||||
duration: record.duration,
|
|
||||||
timestamp: record.save_time,
|
|
||||||
});
|
|
||||||
|
|
||||||
songs.push({
|
|
||||||
id: record.id,
|
|
||||||
name: record.name,
|
|
||||||
artist: record.artist,
|
|
||||||
album: record.album,
|
|
||||||
pic: record.pic,
|
|
||||||
platform: record.platform, // 添加平台信息
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新状态
|
|
||||||
if (records.length > 0) {
|
|
||||||
setPlayRecords(records);
|
|
||||||
setPlaylist(songs);
|
|
||||||
|
|
||||||
// 如果当前有正在播放的歌曲,找到它在记录中的索引
|
|
||||||
const savedPlayState = localStorage.getItem('musicPlayState');
|
|
||||||
if (savedPlayState) {
|
|
||||||
const playState = JSON.parse(savedPlayState);
|
|
||||||
if (playState.currentSong) {
|
|
||||||
const platform = playState.currentSong.platform || playState.currentSource || 'netease';
|
|
||||||
const currentIndex = records.findIndex(
|
|
||||||
r => r.platform === platform && r.id === playState.currentSong.id
|
|
||||||
);
|
|
||||||
if (currentIndex >= 0) {
|
|
||||||
setPlaylistIndex(currentIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,7 +388,7 @@ export default function MusicPage() {
|
|||||||
setPlayRecords(prev => {
|
setPlayRecords(prev => {
|
||||||
const existingIndex = prev.findIndex(r => r.platform === record.platform && r.id === record.id);
|
const existingIndex = prev.findIndex(r => r.platform === record.platform && r.id === record.id);
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
// 记录已存在,更新时间戳
|
// 记录已存在,更新时间戳但不重置播放时间
|
||||||
const updated = [...prev];
|
const updated = [...prev];
|
||||||
updated[existingIndex] = {
|
updated[existingIndex] = {
|
||||||
...updated[existingIndex],
|
...updated[existingIndex],
|
||||||
@@ -544,8 +552,9 @@ export default function MusicPage() {
|
|||||||
// 上一曲
|
// 上一曲
|
||||||
const playPrev = () => {
|
const playPrev = () => {
|
||||||
// 优先从播放列表切换
|
// 优先从播放列表切换
|
||||||
if (playlist.length > 0 && playlistIndex > 0) {
|
if (playlist.length > 0) {
|
||||||
const prevIndex = playlistIndex - 1;
|
// 如果已经是第一首,循环到最后一首
|
||||||
|
const prevIndex = playlistIndex > 0 ? playlistIndex - 1 : playlist.length - 1;
|
||||||
setPlaylistIndex(prevIndex);
|
setPlaylistIndex(prevIndex);
|
||||||
playSong(playlist[prevIndex], -1);
|
playSong(playlist[prevIndex], -1);
|
||||||
} else if (currentSongIndex > 0) {
|
} else if (currentSongIndex > 0) {
|
||||||
@@ -556,8 +565,9 @@ export default function MusicPage() {
|
|||||||
// 下一曲
|
// 下一曲
|
||||||
const playNext = () => {
|
const playNext = () => {
|
||||||
// 优先从播放列表切换
|
// 优先从播放列表切换
|
||||||
if (playlist.length > 0 && playlistIndex < playlist.length - 1) {
|
if (playlist.length > 0) {
|
||||||
const nextIndex = playlistIndex + 1;
|
// 如果已经是最后一首,循环到第一首
|
||||||
|
const nextIndex = playlistIndex < playlist.length - 1 ? playlistIndex + 1 : 0;
|
||||||
setPlaylistIndex(nextIndex);
|
setPlaylistIndex(nextIndex);
|
||||||
playSong(playlist[nextIndex], -1);
|
playSong(playlist[nextIndex], -1);
|
||||||
} else if (currentSongIndex < songs.length - 1) {
|
} else if (currentSongIndex < songs.length - 1) {
|
||||||
@@ -732,8 +742,15 @@ export default function MusicPage() {
|
|||||||
audio.currentTime = 0;
|
audio.currentTime = 0;
|
||||||
audio.play();
|
audio.play();
|
||||||
} else if (playMode === 'random') {
|
} else if (playMode === 'random') {
|
||||||
const randomIndex = Math.floor(Math.random() * songs.length);
|
// 优先从播放列表中随机选择
|
||||||
playSong(songs[randomIndex], randomIndex);
|
if (playlist.length > 0) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * playlist.length);
|
||||||
|
setPlaylistIndex(randomIndex);
|
||||||
|
playSong(playlist[randomIndex], -1);
|
||||||
|
} else if (songs.length > 0) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * songs.length);
|
||||||
|
playSong(songs[randomIndex], randomIndex);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
playNext();
|
playNext();
|
||||||
}
|
}
|
||||||
@@ -1290,7 +1307,11 @@ export default function MusicPage() {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
{/* 音量控制 */}
|
{/* 音量控制 */}
|
||||||
<div className="relative group">
|
<div
|
||||||
|
className="relative"
|
||||||
|
onMouseEnter={() => setShowVolumeSlider(true)}
|
||||||
|
onMouseLeave={() => setShowVolumeSlider(false)}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -1305,7 +1326,7 @@ export default function MusicPage() {
|
|||||||
</button>
|
</button>
|
||||||
{/* 垂直音量条 - 桌面悬浮/移动端点击 */}
|
{/* 垂直音量条 - 桌面悬浮/移动端点击 */}
|
||||||
<div
|
<div
|
||||||
className={`absolute bottom-full left-1/2 -translate-x-1/2 pb-2 transition-opacity md:opacity-0 md:group-hover:opacity-100 md:pointer-events-auto ${showVolumeSlider ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}
|
className={`absolute bottom-full left-1/2 -translate-x-1/2 pb-2 transition-opacity ${showVolumeSlider ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="bg-zinc-800/95 backdrop-blur-sm rounded-lg p-3 shadow-xl border border-white/10">
|
<div className="bg-zinc-800/95 backdrop-blur-sm rounded-lg p-3 shadow-xl border border-white/10">
|
||||||
|
|||||||
Reference in New Issue
Block a user