/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client'; import { useEffect, useRef } from 'react'; import ReactDOM from 'react-dom/client'; interface Song { id: string; name: string; artist: string; album?: string; pic?: string; platform: 'netease' | 'qq' | 'kuwo'; } interface LyricLine { time: number; text: string; } interface LyricsPiPWindowProps { currentSong: Song | null; lyrics: LyricLine[]; currentLyricIndex: number; isPlaying: boolean; currentTime: number; opacity: number; minimized: boolean; onOpacityChange: (opacity: number) => void; onMinimizedChange: (minimized: boolean) => void; onClose: () => void; } interface PiPLyricsContentProps { currentSong: Song | null; lyrics: LyricLine[]; currentLyricIndex: number; opacity: number; minimized: boolean; onOpacityChange: (opacity: number) => void; onMinimizedChange: (minimized: boolean) => void; onClose: () => void; } // PiP 窗口内容组件 const PiPLyricsContent = ({ currentSong, lyrics, currentLyricIndex, opacity, minimized, onOpacityChange, onMinimizedChange, onClose, }: PiPLyricsContentProps) => { const lyricsContainerRef = useRef(null); // 自动滚动到当前歌词 useEffect(() => { if (lyricsContainerRef.current && currentLyricIndex >= 0 && !minimized) { const container = lyricsContainerRef.current; const currentLine = container.children[currentLyricIndex] as HTMLElement; if (currentLine) { const containerHeight = container.clientHeight; const lineTop = currentLine.offsetTop; const lineHeight = currentLine.clientHeight; const scrollTop = lineTop - containerHeight / 2 + lineHeight / 2; container.scrollTo({ top: scrollTop, behavior: 'smooth' }); } } }, [currentLyricIndex, minimized]); return (
{/* 头部:歌曲信息 + 控制按钮 */}
{currentSong ? `${currentSong.name} - ${currentSong.artist}` : '暂无播放'}
{/* 透明度滑块 */} onOpacityChange(parseFloat(e.target.value))} style={{ width: '60px', cursor: 'pointer' }} title={`透明度: ${Math.round(opacity * 100)}%`} /> {/* 最小化按钮 */} {/* 关闭按钮 */}
{/* 歌词内容 */} {minimized ? ( // 最小化模式:仅显示当前歌词
{lyrics.length > 0 && currentLyricIndex >= 0 ? lyrics[currentLyricIndex]?.text || '♪' : currentSong ? '暂无歌词' : '请播放歌曲'}
) : ( // 完整模式:显示所有歌词
{lyrics.length > 0 ? ( lyrics.map((line, index) => (
{line.text}
)) ) : (
{currentSong ? '暂无歌词' : '请播放歌曲'}
)}
)}
); }; // 复制样式表到 PiP 窗口 const copyStylesToPiPWindow = (pipWin: Window) => { const styleSheets = Array.from(document.styleSheets); styleSheets.forEach((styleSheet) => { try { const cssRules = Array.from(styleSheet.cssRules) .map((rule) => rule.cssText) .join(''); const style = pipWin.document.createElement('style'); style.textContent = cssRules; pipWin.document.head.appendChild(style); } catch (e) { // 跨域样式表使用 link 标签 if ((styleSheet as any).href) { const link = pipWin.document.createElement('link'); link.rel = 'stylesheet'; link.href = (styleSheet as any).href; pipWin.document.head.appendChild(link); } } }); }; // 主组件:管理 PiP 窗口 export default function LyricsPiPWindow({ currentSong, lyrics, currentLyricIndex, isPlaying, currentTime, opacity, minimized, onOpacityChange, onMinimizedChange, onClose, }: LyricsPiPWindowProps) { const pipWindowRef = useRef(null); const rootRef = useRef(null); // 渲染 PiP 内容 const renderPiPContent = (pipWin: Window) => { const container = pipWin.document.createElement('div'); container.id = 'pip-lyrics-root'; pipWin.document.body.appendChild(container); // 设置 body 样式 pipWin.document.body.style.margin = '0'; pipWin.document.body.style.padding = '0'; pipWin.document.body.style.overflow = 'hidden'; // 使用 ReactDOM 渲染组件到 PiP 窗口 const root = ReactDOM.createRoot(container); rootRef.current = root; root.render( { window.postMessage({ type: 'PIP_OPACITY_CHANGE', opacity: newOpacity }, '*'); }} onMinimizedChange={(newMinimized) => { window.postMessage({ type: 'PIP_MINIMIZED_CHANGE', minimized: newMinimized }, '*'); }} onClose={() => { window.postMessage({ type: 'PIP_CLOSE' }, '*'); }} /> ); }; // 更新 PiP 内容 useEffect(() => { if (pipWindowRef.current && !pipWindowRef.current.closed && rootRef.current) { rootRef.current.render( { window.postMessage({ type: 'PIP_OPACITY_CHANGE', opacity: newOpacity }, '*'); }} onMinimizedChange={(newMinimized) => { window.postMessage({ type: 'PIP_MINIMIZED_CHANGE', minimized: newMinimized }, '*'); }} onClose={() => { window.postMessage({ type: 'PIP_CLOSE' }, '*'); }} /> ); } }, [currentSong, lyrics, currentLyricIndex, opacity, minimized]); // 打开 PiP 窗口 useEffect(() => { const openPiPWindow = async () => { if (!('documentPictureInPicture' in window)) { console.error('浏览器不支持 Document Picture-in-Picture API'); return; } try { const pipWin = await (window as any).documentPictureInPicture.requestWindow({ width: 400, height: 300, }); pipWindowRef.current = pipWin; // 复制样式表到 PiP 窗口 copyStylesToPiPWindow(pipWin); // 监听窗口关闭 pipWin.addEventListener('pagehide', () => { if (rootRef.current) { rootRef.current.unmount(); rootRef.current = null; } pipWindowRef.current = null; onClose(); }); // 渲染内容 renderPiPContent(pipWin); } catch (error) { console.error('打开画中画窗口失败:', error); onClose(); } }; openPiPWindow(); // 清理函数 return () => { if (pipWindowRef.current && !pipWindowRef.current.closed) { pipWindowRef.current.close(); } if (rootRef.current) { rootRef.current.unmount(); rootRef.current = null; } }; }, []); // 只在组件挂载时执行一次 return null; // 此组件不渲染任何内容到主窗口 }