Feature: Watch Together - History, Titel-Fetch, Next-Button

Server:
- Video-History Tracking (max 50 Einträge pro Raum)
- History wird bei Skip und Play-Video gespeichert
- Server-seitiger Titel-Fetch via noembed.com als Fallback

Client:
- Aufklappbare History-Sektion im Queue-Panel
- "Weiter" Button mit Text-Label statt nur Icon
- YouTube-Thumbnails in der Warteschlange
- History in RoomState integriert

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-07 15:31:56 +01:00
parent a1a1f31c8e
commit 6c57419959
3 changed files with 169 additions and 6 deletions

View file

@ -21,6 +21,7 @@ interface RoomState {
playing: boolean;
currentTime: number;
queue: Array<{ url: string; title: string; addedBy: string }>;
history: Array<{ url: string; title: string; addedBy: string; playedAt: string }>;
}
interface JoinModal {
@ -76,6 +77,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
const [currentTime, setCurrentTime] = useState(0);
const [playerError, setPlayerError] = useState<string | null>(null);
const [addingToQueue, setAddingToQueue] = useState(false);
const [showHistory, setShowHistory] = useState(false);
// ── Refs ──
const wsRef = useRef<WebSocket | null>(null);
@ -297,6 +299,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
playing: r.playing || false,
currentTime: r.currentTime || 0,
queue: r.queue || [],
history: r.history || [],
});
break;
}
@ -312,6 +315,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
playing: r.playing || false,
currentTime: r.currentTime || 0,
queue: r.queue || [],
history: r.history || [],
});
if (r.currentVideo?.url) {
setTimeout(() => loadVideo(r.currentVideo.url), 100);
@ -366,6 +370,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
currentVideo: newVideo || null,
playing: msg.playing,
currentTime: msg.currentTime ?? prev.currentTime,
history: msg.history ?? prev.history,
} : prev);
break;
}
@ -659,8 +664,8 @@ export default function WatchTogetherTab({ data }: { data: any }) {
<button className="wt-ctrl-btn" onClick={togglePlay} disabled={!currentRoom.currentVideo} title={currentRoom.playing ? 'Pause' : 'Abspielen'}>
{currentRoom.playing ? '\u23F8' : '\u25B6'}
</button>
<button className="wt-ctrl-btn" onClick={skip} disabled={!currentRoom.currentVideo} title="Weiter">
{'\u23ED'}
<button className="wt-ctrl-btn wt-next-btn" onClick={skip} disabled={!currentRoom.currentVideo && currentRoom.queue.length === 0} title="Nächstes Video">
{'\u23ED'} Weiter
</button>
<input
className="wt-seek"
@ -711,8 +716,17 @@ export default function WatchTogetherTab({ data }: { data: any }) {
title={isHost ? 'Klicken zum Abspielen' : undefined}
>
<div className="wt-queue-item-info">
<div className="wt-queue-item-title">{item.title || item.url}</div>
<div className="wt-queue-item-by">{item.addedBy}</div>
{item.url.match(/youtu/) && (
<img
className="wt-queue-thumb"
src={`https://img.youtube.com/vi/${item.url.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/)?.[1]}/default.jpg`}
alt=""
/>
)}
<div className="wt-queue-item-text">
<div className="wt-queue-item-title">{item.title || item.url}</div>
<div className="wt-queue-item-by">{item.addedBy}</div>
</div>
</div>
{isHost && (
<button className="wt-queue-item-remove" onClick={() => removeFromQueue(i)} title="Entfernen">
@ -723,6 +737,23 @@ export default function WatchTogetherTab({ data }: { data: any }) {
))
)}
</div>
{currentRoom.history && currentRoom.history.length > 0 && (
<div className="wt-history-section">
<button className="wt-history-toggle" onClick={() => setShowHistory(!showHistory)}>
{showHistory ? '\u25BC' : '\u25B6'} Verlauf ({currentRoom.history.length})
</button>
{showHistory && (
<div className="wt-history-list">
{[...currentRoom.history].reverse().map((item, i) => (
<div key={i} className="wt-history-item">
<div className="wt-history-item-title">{item.title || item.url}</div>
<div className="wt-history-item-by">{item.addedBy}</div>
</div>
))}
</div>
)}
</div>
)}
<div className="wt-queue-add">
<input
className="wt-input wt-queue-input"