Watch Together: Embed-Fehlerbehandlung, klickbare Queue, Video-Titel
- YouTube onError Handler: Erkennt Error 101/150 (Embedding deaktiviert), zeigt Fehlermeldung + "Auf YouTube oeffnen" Link, auto-skip nach 3s - Queue-Items klickbar fuer Host (play_video mit Index) - Video-Titel werden via noembed.com oEmbed API geholt - Server-Endpoint: GET /api/watch-together/video-info?url=... - "Hinzufuegen" Button zeigt Ladezustand waehrend Titel-Fetch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e4895a792c
commit
09813b626f
7 changed files with 4967 additions and 4838 deletions
|
|
@ -74,6 +74,8 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
|||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [duration, setDuration] = useState(0);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [playerError, setPlayerError] = useState<string | null>(null);
|
||||
const [addingToQueue, setAddingToQueue] = useState(false);
|
||||
|
||||
// ── Refs ──
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
|
@ -183,6 +185,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
|||
// ── Load video ──
|
||||
const loadVideo = useCallback((url: string) => {
|
||||
destroyPlayer();
|
||||
setPlayerError(null);
|
||||
const parsed = parseVideoUrl(url);
|
||||
if (!parsed) return;
|
||||
|
||||
|
|
@ -220,6 +223,26 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
|||
}
|
||||
}
|
||||
},
|
||||
onError: (ev: any) => {
|
||||
const code = ev.data;
|
||||
// 101 or 150 = embedding disabled by video owner
|
||||
// 100 = video not found
|
||||
// 5 = HTML5 player error
|
||||
if (code === 101 || code === 150) {
|
||||
setPlayerError('Embedding deaktiviert \u2014 Video kann nur auf YouTube angesehen werden.');
|
||||
} else if (code === 100) {
|
||||
setPlayerError('Video nicht gefunden oder entfernt.');
|
||||
} else {
|
||||
setPlayerError('Wiedergabefehler \u2014 Video wird \u00FCbersprungen.');
|
||||
}
|
||||
// Auto-skip after 3 seconds if host
|
||||
setTimeout(() => {
|
||||
const room = currentRoomRef.current;
|
||||
if (room && clientIdRef.current === room.hostId) {
|
||||
wsSend({ type: 'skip' });
|
||||
}
|
||||
}, 3000);
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
|
@ -456,10 +479,23 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
|||
}, [wsSend, destroyPlayer]);
|
||||
|
||||
// ── Add to queue ──
|
||||
const addToQueue = useCallback(() => {
|
||||
if (!queueUrl.trim()) return;
|
||||
wsSend({ type: 'add_to_queue', url: queueUrl.trim() });
|
||||
const addToQueue = useCallback(async () => {
|
||||
const url = queueUrl.trim();
|
||||
if (!url) return;
|
||||
setQueueUrl('');
|
||||
setAddingToQueue(true);
|
||||
|
||||
let title = '';
|
||||
try {
|
||||
const resp = await fetch(`/api/watch-together/video-info?url=${encodeURIComponent(url)}`);
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
title = data.title || '';
|
||||
}
|
||||
} catch { /* use URL as fallback */ }
|
||||
|
||||
wsSend({ type: 'add_to_queue', url, title: title || undefined });
|
||||
setAddingToQueue(false);
|
||||
}, [queueUrl, wsSend]);
|
||||
|
||||
// ── Remove from queue ──
|
||||
|
|
@ -592,6 +628,23 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
|||
style={currentVideoTypeRef.current === 'direct' ? {} : { display: 'none' }}
|
||||
playsInline
|
||||
/>
|
||||
{playerError && (
|
||||
<div className="wt-player-error">
|
||||
<div className="wt-error-icon">{'\u26A0\uFE0F'}</div>
|
||||
<p>{playerError}</p>
|
||||
{currentRoom.currentVideo?.url && (
|
||||
<a
|
||||
className="wt-yt-link"
|
||||
href={currentRoom.currentVideo.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Auf YouTube öffnen ↗
|
||||
</a>
|
||||
)}
|
||||
<p className="wt-skip-info">Wird in 3 Sekunden übersprungen...</p>
|
||||
</div>
|
||||
)}
|
||||
{!currentRoom.currentVideo && (
|
||||
<div className="wt-player-placeholder">
|
||||
<div className="wt-placeholder-icon">{'\uD83C\uDFAC'}</div>
|
||||
|
|
@ -653,7 +706,9 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
|||
currentRoom.queue.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`wt-queue-item${currentRoom.currentVideo?.url === item.url ? ' playing' : ''}`}
|
||||
className={`wt-queue-item${currentRoom.currentVideo?.url === item.url ? ' playing' : ''}${isHost ? ' clickable' : ''}`}
|
||||
onClick={() => isHost && wsSend({ type: 'play_video', index: i })}
|
||||
title={isHost ? 'Klicken zum Abspielen' : undefined}
|
||||
>
|
||||
<div className="wt-queue-item-info">
|
||||
<div className="wt-queue-item-title">{item.title || item.url}</div>
|
||||
|
|
@ -676,7 +731,9 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
|||
onChange={e => setQueueUrl(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') addToQueue(); }}
|
||||
/>
|
||||
<button className="wt-btn wt-queue-add-btn" onClick={addToQueue}>Hinzufuegen</button>
|
||||
<button className="wt-btn wt-queue-add-btn" onClick={addToQueue} disabled={addingToQueue}>
|
||||
{addingToQueue ? 'Laden...' : 'Hinzufuegen'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue