From 49dcfb28deec043f4ae8d206b69a7007f6a2fdd0 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Mon, 13 Apr 2026 11:02:38 +0200 Subject: [PATCH] add youtube iframe --- src/components/layout/PlayerBar.tsx | 28 +++++++++++++++++++++++++++- src/stores/player.ts | 28 +++++++++++++++++++++------- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/components/layout/PlayerBar.tsx b/src/components/layout/PlayerBar.tsx index d601f03..d32a10b 100644 --- a/src/components/layout/PlayerBar.tsx +++ b/src/components/layout/PlayerBar.tsx @@ -1,15 +1,17 @@ import { useRef, useState, useCallback } from 'react' -import { Play, Pause, Volume2, VolumeX, SkipBack, SkipForward } from 'lucide-react' +import { Play, Pause, Volume2, VolumeX, SkipBack, SkipForward, Maximize2, Minimize2 } from 'lucide-react' import { usePlayerStore } from '@/stores/player' import { formatDuration } from '@/lib/utils' import { publicUrl } from '@/lib/storage' import { Avatar } from '@/components/ui/Avatar' +import { isExternalUrl, getEmbedInfo } from '@/lib/embed' export function PlayerBar() { const { current, isPlaying, isExternal, progress, duration, volume, toggle, seek, setVolume } = usePlayerStore() const seekRef = useRef(null) const [isDragging, setIsDragging] = useState(false) const [dragPct, setDragPct] = useState(0) + const [ytExpanded, setYtExpanded] = useState(false) const calcPct = useCallback((clientX: number) => { if (!seekRef.current) return 0 @@ -19,6 +21,7 @@ export function PlayerBar() { if (!current) return null + const isYouTube = isExternal && current && isExternalUrl(current.audio_url) && getEmbedInfo(current.audio_url)?.platform === 'youtube' const pct = duration > 0 ? (progress / duration) * 100 : 0 const displayPct = isDragging ? dragPct : pct // Allow seeking for native audio and YouTube (via IFrame API) @@ -72,6 +75,28 @@ export function PlayerBar() { const currentTime = isDragging ? (dragPct / 100) * duration : progress return ( + <> + {/* YouTube player - mini or expanded */} + {isYouTube && ( +
+
+ {/* Gradient overlay to hide YouTube title bar */} +
+ +
+ )}
{/* Main row */} @@ -192,5 +217,6 @@ export function PlayerBar() {
}
+ ) } diff --git a/src/stores/player.ts b/src/stores/player.ts index 49f91c0..44fc76e 100644 --- a/src/stores/player.ts +++ b/src/stores/player.ts @@ -50,7 +50,15 @@ function destroyYtPlayer() { clearProgressInterval() try { ytPlayer?.destroy() } catch { /* ignore */ } ytPlayer = null - if (ytContainer) { ytContainer.remove(); ytContainer = null } + if (ytContainer) { + // If it's the portal, just clear contents; otherwise remove from DOM + if (ytContainer.id === 'yt-player-portal') { + ytContainer.innerHTML = '' + } else { + ytContainer.remove() + } + ytContainer = null + } } function destroyIframe() { @@ -178,20 +186,26 @@ export const usePlayerStore = create((set, get) => ({ // Wait for previous save to complete, then fetch saved progress savePrevPromise.then(() => fetchSavedProgress(podcast.id)).then((savedTime) => { loadYouTubeAPI().then(() => { - ytContainer = document.createElement('div') - ytContainer.style.cssText = 'position:fixed;width:1px;height:1px;left:-10px;top:-10px;opacity:0;pointer-events:none;' + // Use portal target if available, otherwise create visible container + const portalTarget = document.getElementById('yt-player-portal') + if (portalTarget) { + portalTarget.innerHTML = '' + ytContainer = portalTarget as HTMLDivElement + } else { + ytContainer = document.createElement('div') + ytContainer.style.cssText = 'position:fixed;bottom:90px;right:16px;width:200px;height:200px;z-index:60;border-radius:12px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,0.3);' + document.body.appendChild(ytContainer) + } const playerDiv = document.createElement('div') playerDiv.id = 'yt-player-' + Date.now() ytContainer.appendChild(playerDiv) - document.body.appendChild(ytContainer) ytPlayer = new window.YT.Player(playerDiv.id, { videoId: embed.id, playerVars: { autoplay: 1, - controls: 0, - disablekb: 1, - fs: 0, + controls: 1, + fs: 1, modestbranding: 1, rel: 0, start: savedTime > 0 ? Math.floor(savedTime) : undefined,