fix(player): friendly fallback when a video can't be embedded
The in-app IFrame player showed YouTube's bare 'Video unavailable' screen with no way out when a video couldn't be embedded (auto-generated Topic art-tracks disable embedding -> error 101/150; removed/private -> 100). Catch the IFrame onError and overlay a clear message + 'Open on YouTube' button, with a tailored note for embedding-disabled videos.
This commit is contained in:
parent
4ea2043f3d
commit
ef4bdee1f7
4 changed files with 48 additions and 5 deletions
|
|
@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||
import type { TFunction } from "i18next";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ArrowLeft, Check, CheckCheck, SkipBack, SkipForward, X } from "lucide-react";
|
||||
import { AlertTriangle, ArrowLeft, Check, CheckCheck, ExternalLink, SkipBack, SkipForward, X } from "lucide-react";
|
||||
import Avatar from "./Avatar";
|
||||
import AddToPlaylist from "./AddToPlaylist";
|
||||
import { api, type Video } from "../lib/api";
|
||||
|
|
@ -237,12 +237,17 @@ export default function PlayerModal({
|
|||
const navigated = currentVideoId !== active.id;
|
||||
// Title/author of a navigated-to video, read from the player (free, no API call).
|
||||
const [liveData, setLiveData] = useState<{ title?: string; author?: string } | null>(null);
|
||||
// IFrame Player API error code, when the current video can't play in the embed (e.g. an
|
||||
// auto-generated Topic "Art Track" with embedding disabled → 101/150, or removed/private →
|
||||
// 100). We overlay a friendly message + "Open on YouTube" instead of YouTube's bare error.
|
||||
const [playerError, setPlayerError] = useState<number | null>(null);
|
||||
|
||||
const loadVideo = (id: string, start: number | null) => {
|
||||
const p = playerRef.current;
|
||||
if (!p || typeof p.loadVideoById !== "function") return;
|
||||
// Explicit start wins; otherwise resume the active item, start others at 0.
|
||||
const startSeconds = start != null ? start : id === active.id ? resumeAt : 0;
|
||||
setPlayerError(null);
|
||||
p.loadVideoById({ videoId: id, startSeconds: startSeconds || 0 });
|
||||
currentIdRef.current = id;
|
||||
setCurrentVideoId(id);
|
||||
|
|
@ -330,6 +335,7 @@ export default function PlayerModal({
|
|||
let cancelled = false;
|
||||
const id = active.id;
|
||||
autoMarkedRef.current = false;
|
||||
setPlayerError(null);
|
||||
|
||||
// Auto-watch only applies to the active item — not to other videos the player
|
||||
// navigated to via description links.
|
||||
|
|
@ -388,6 +394,9 @@ export default function PlayerModal({
|
|||
if (queue && index < queue.length - 1) setPlayingId(queue[index + 1].id);
|
||||
}
|
||||
},
|
||||
// The embed couldn't play this video (embedding disabled, removed, private…).
|
||||
// Surface our own message + an "Open on YouTube" escape hatch.
|
||||
onError: (e: any) => setPlayerError(typeof e?.data === "number" ? e.data : -1),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -427,8 +436,30 @@ export default function PlayerModal({
|
|||
className="glass-card relative w-full max-w-4xl max-h-full overflow-y-auto rounded-2xl shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="aspect-video w-full bg-black rounded-t-2xl overflow-hidden">
|
||||
<div className="relative aspect-video w-full bg-black rounded-t-2xl overflow-hidden">
|
||||
<div ref={mountRef} className="w-full h-full" />
|
||||
{playerError != null && (
|
||||
<div className="absolute inset-0 grid place-items-center bg-bg/95 p-6 text-center">
|
||||
<div className="max-w-sm">
|
||||
<AlertTriangle className="w-8 h-8 text-amber-500 mx-auto mb-3" />
|
||||
<h3 className="text-base font-semibold">{t("player.unavailableTitle")}</h3>
|
||||
<p className="text-sm text-muted mt-1.5 mb-4">
|
||||
{playerError === 101 || playerError === 150
|
||||
? t("player.embedDisabledBody")
|
||||
: t("player.unavailableBody")}
|
||||
</p>
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=${currentVideoId}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-semibold bg-accent text-accent-fg hover:opacity-90 transition"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
{t("player.openOnYoutube")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasQueue && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue