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:
parent
22b4c6b187
commit
963bb1b775
3 changed files with 64 additions and 146 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue