add youtube iframe
This commit is contained in:
parent
a7c4aa5608
commit
49dcfb28de
@ -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<HTMLDivElement>(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 && (
|
||||
<div
|
||||
className={`fixed z-[60] rounded-xl overflow-hidden shadow-[0_4px_20px_rgba(0,0,0,0.3)] transition-all duration-300 ${
|
||||
ytExpanded
|
||||
? 'inset-4 bottom-[106px]'
|
||||
: 'bottom-[106px] right-4'
|
||||
}`}
|
||||
style={ytExpanded ? {} : { width: 200, height: 150 }}
|
||||
>
|
||||
<div id="yt-player-portal" className="w-full h-full [&_iframe]:!w-full [&_iframe]:!h-full [&_iframe]:!border-0" />
|
||||
{/* Gradient overlay to hide YouTube title bar */}
|
||||
<div className="absolute inset-x-0 top-0 h-10 bg-gradient-to-b from-black/80 to-transparent pointer-events-none" />
|
||||
<button
|
||||
onClick={() => setYtExpanded(!ytExpanded)}
|
||||
className="absolute top-2 right-2 z-10 w-8 h-8 rounded-lg bg-black/60 hover:bg-black/80 flex items-center justify-center text-white transition-colors cursor-pointer"
|
||||
>
|
||||
{ytExpanded ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-[#1E1B33] text-white shadow-[0_-2px_20px_rgba(0,0,0,0.3)]">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
{/* Main row */}
|
||||
@ -192,5 +217,6 @@ export function PlayerBar() {
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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<PlayerState>((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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user