Watch Together: Videos bleiben in Queue mit Watched-Haken

Statt Videos nach dem Abspielen aus der Warteschlange zu entfernen,
bleiben sie drin und werden mit einem gruenen Haken markiert.
Separate History-Section entfernt — die Queue IST die History.
Videos bleiben klickbar zum erneuten Abspielen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-07 22:23:43 +01:00
parent 22b4c6b187
commit 963bb1b775
3 changed files with 64 additions and 146 deletions

View file

@ -10,13 +10,7 @@ interface QueueItem {
url: string;
title: string;
addedBy: string;
}
interface HistoryItem {
url: string;
title: string;
addedBy: string;
playedAt: string; // ISO timestamp
watched: boolean;
}
interface RoomMember {
@ -36,7 +30,6 @@ interface Room {
currentTime: number;
lastSyncAt: number;
queue: QueueItem[];
history: HistoryItem[];
}
interface WtClient {
@ -86,7 +79,6 @@ function getPlaybackState(room: Room): Record<string, any> {
playing: room.playing,
currentTime,
updatedAt: Date.now(),
history: room.history,
};
}
@ -101,7 +93,6 @@ function serializeRoom(room: Room): Record<string, any> {
playing: room.playing,
currentTime: room.currentTime + (room.playing ? (Date.now() - room.lastSyncAt) / 1000 : 0),
queue: room.queue,
history: room.history,
};
}
@ -213,7 +204,6 @@ async function handleMessage(client: WtClient, msg: any): Promise<void> {
currentTime: 0,
lastSyncAt: Date.now(),
queue: [],
history: [],
};
rooms.set(roomId, room);
@ -283,19 +273,19 @@ async function handleMessage(client: WtClient, msg: any): Promise<void> {
}
if (!title) title = url;
room.queue.push({ url, title, addedBy: client.name });
sendToRoom(room.id, { type: 'queue_updated', queue: room.queue });
const queueItem: QueueItem = { url, title, addedBy: client.name, watched: false };
room.queue.push(queueItem);
// Auto-play first item if nothing is currently playing
if (!room.currentVideo) {
const item = room.queue.shift()!;
room.currentVideo = { url: item.url, title: item.title };
queueItem.watched = true;
room.currentVideo = { url: queueItem.url, title: queueItem.title };
room.playing = true;
room.currentTime = 0;
room.lastSyncAt = Date.now();
sendToRoom(room.id, getPlaybackState(room));
sendToRoom(room.id, { type: 'queue_updated', queue: room.queue });
}
sendToRoom(room.id, { type: 'queue_updated', queue: room.queue });
break;
}
@ -346,34 +336,24 @@ async function handleMessage(client: WtClient, msg: any): Promise<void> {
return;
}
// Track current video in history before replacing
if (room.currentVideo) {
room.history.push({
url: room.currentVideo.url,
title: room.currentVideo.title,
addedBy: '',
playedAt: new Date().toISOString(),
});
if (room.history.length > 50) room.history = room.history.slice(-50);
}
const index = msg.index != null ? Number(msg.index) : undefined;
if (index !== undefined) {
if (index < 0 || index >= room.queue.length) {
sendTo(client, { type: 'error', code: 'INVALID_INDEX', message: 'Ungültiger Index.' });
return;
}
const [item] = room.queue.splice(index, 1);
room.currentVideo = { url: item.url, title: item.title };
room.queue[index].watched = true;
room.currentVideo = { url: room.queue[index].url, title: room.queue[index].title };
} else {
// No index — play from queue head or error if nothing available
if (!room.currentVideo && room.queue.length === 0) {
sendTo(client, { type: 'error', code: 'QUEUE_EMPTY', message: 'Warteschlange ist leer und kein Video ausgewählt.' });
// No index — play next unwatched or error
const nextItem = room.queue.find(q => !q.watched);
if (!room.currentVideo && !nextItem) {
sendTo(client, { type: 'error', code: 'QUEUE_EMPTY', message: 'Keine ungesehenen Videos in der Warteschlange.' });
return;
}
if (!room.currentVideo && room.queue.length > 0) {
const item = room.queue.shift()!;
room.currentVideo = { url: item.url, title: item.title };
if (nextItem) {
nextItem.watched = true;
room.currentVideo = { url: nextItem.url, title: nextItem.title };
}
}
@ -437,20 +417,16 @@ async function handleMessage(client: WtClient, msg: any): Promise<void> {
sendTo(client, { type: 'error', code: 'NOT_HOST', message: 'Nur der Host kann die Wiedergabe steuern.' });
return;
}
// Track current video in history before skipping
// Mark current video as watched in queue
if (room.currentVideo) {
room.history.push({
url: room.currentVideo.url,
title: room.currentVideo.title,
addedBy: '',
playedAt: new Date().toISOString(),
});
// Keep history max 50 items
if (room.history.length > 50) room.history = room.history.slice(-50);
const currentItem = room.queue.find(q => q.url === room.currentVideo!.url);
if (currentItem) currentItem.watched = true;
}
if (room.queue.length > 0) {
const item = room.queue.shift()!;
room.currentVideo = { url: item.url, title: item.title };
// Find next unwatched video
const nextItem = room.queue.find(q => !q.watched);
if (nextItem) {
nextItem.watched = true;
room.currentVideo = { url: nextItem.url, title: nextItem.title };
} else {
room.currentVideo = null;
}