From 963bb1b775baebae2cb9abd15608f73191333387 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 7 Mar 2026 22:23:43 +0100 Subject: [PATCH] Watch Together: Videos bleiben in Queue mit Watched-Haken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- server/src/plugins/watch-together/index.ts | 70 ++++++----------- .../watch-together/WatchTogetherTab.tsx | 78 +++++++------------ .../plugins/watch-together/watch-together.css | 62 +++------------ 3 files changed, 64 insertions(+), 146 deletions(-) diff --git a/server/src/plugins/watch-together/index.ts b/server/src/plugins/watch-together/index.ts index 4bb9bec..12fcf00 100644 --- a/server/src/plugins/watch-together/index.ts +++ b/server/src/plugins/watch-together/index.ts @@ -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 { playing: room.playing, currentTime, updatedAt: Date.now(), - history: room.history, }; } @@ -101,7 +93,6 @@ function serializeRoom(room: Room): Record { 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 { currentTime: 0, lastSyncAt: Date.now(), queue: [], - history: [], }; rooms.set(roomId, room); @@ -283,19 +273,19 @@ async function handleMessage(client: WtClient, msg: any): Promise { } 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 { 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 { 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; } diff --git a/web/src/plugins/watch-together/WatchTogetherTab.tsx b/web/src/plugins/watch-together/WatchTogetherTab.tsx index be3e6f2..49cc431 100644 --- a/web/src/plugins/watch-together/WatchTogetherTab.tsx +++ b/web/src/plugins/watch-together/WatchTogetherTab.tsx @@ -20,8 +20,7 @@ interface RoomState { currentVideo: { url: string; title: string } | null; playing: boolean; currentTime: number; - queue: Array<{ url: string; title: string; addedBy: string }>; - history: Array<{ url: string; title: string; addedBy: string; playedAt: string }>; + queue: Array<{ url: string; title: string; addedBy: string; watched: boolean }>; } interface JoinModal { @@ -77,7 +76,6 @@ export default function WatchTogetherTab({ data }: { data: any }) { const [currentTime, setCurrentTime] = useState(0); const [playerError, setPlayerError] = useState(null); const [addingToQueue, setAddingToQueue] = useState(false); - const [showHistory, setShowHistory] = useState(false); // ── Refs ── const wsRef = useRef(null); @@ -299,7 +297,6 @@ export default function WatchTogetherTab({ data }: { data: any }) { playing: r.playing || false, currentTime: r.currentTime || 0, queue: r.queue || [], - history: r.history || [], }); break; } @@ -315,7 +312,6 @@ 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); @@ -370,7 +366,6 @@ 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; } @@ -708,52 +703,39 @@ export default function WatchTogetherTab({ data }: { data: any }) { {currentRoom.queue.length === 0 ? (
Keine Videos in der Warteschlange
) : ( - currentRoom.queue.map((item, i) => ( -
isHost && wsSend({ type: 'play_video', index: i })} - title={isHost ? 'Klicken zum Abspielen' : undefined} - > -
- {item.url.match(/youtu/) && ( - - )} -
-
{item.title || item.url}
-
{item.addedBy}
+ currentRoom.queue.map((item, i) => { + const isCurrent = currentRoom.currentVideo?.url === item.url; + return ( +
isHost && wsSend({ type: 'play_video', index: i })} + title={isHost ? 'Klicken zum Abspielen' : undefined} + > +
+ {item.watched && !isCurrent && {'\u2713'}} + {item.url.match(/youtu/) && ( + + )} +
+
{item.title || item.url}
+
{item.addedBy}
+
+ {isHost && ( + + )}
- {isHost && ( - - )} -
- )) + ); + }) )}
- {currentRoom.history && currentRoom.history.length > 0 && ( -
- - {showHistory && ( -
- {[...currentRoom.history].reverse().map((item, i) => ( -
-
{item.title || item.url}
-
{item.addedBy}
-
- ))} -
- )} -
- )}