diff --git a/src/components/layout/PlayerBar.tsx b/src/components/layout/PlayerBar.tsx
index a5efc21..204dc4d 100644
--- a/src/components/layout/PlayerBar.tsx
+++ b/src/components/layout/PlayerBar.tsx
@@ -1,82 +1,32 @@
-import { useState } from 'react'
-import { Play, Pause, Volume2, VolumeX, SkipBack, SkipForward, Maximize2, Minimize2 } from 'lucide-react'
+import { Play, Pause, Volume2, VolumeX, SkipBack, SkipForward } from 'lucide-react'
import { usePlayerStore } from '@/stores/player'
import { formatDuration } from '@/lib/utils'
-import { getEmbedInfo } from '@/lib/embed'
import { Avatar } from '@/components/ui/Avatar'
export function PlayerBar() {
const { current, isPlaying, isExternal, progress, duration, volume, toggle, seek, setVolume } = usePlayerStore()
- const [expanded, setExpanded] = useState(false)
if (!current) return null
- const embedInfo = isExternal ? getEmbedInfo(current.audio_url) : null
-
- // External content: show expandable embed player
- if (isExternal && embedInfo && isPlaying) {
- return (
-
- {expanded && (
-
-
-
- )}
-
-
-
- {current.cover_url ? (
-

- ) : (
-
- )}
-
-
-
-
{current.title}
-
- {current.creator?.username} ยท via {embedInfo.platform === 'youtube' ? 'YouTube' : embedInfo.platform === 'dailymotion' ? 'Dailymotion' : 'SoundCloud'}
-
-
-
-
-
-
-
- )
- }
-
- // Native audio player
return (
- {/* Progress bar */}
-
{
- const rect = e.currentTarget.getBoundingClientRect()
- const pct = (e.clientX - rect.left) / rect.width
- seek(pct * duration)
- }}>
-
-
-
+ {/* Progress bar (native audio only) */}
+ {!isExternal && (
+
{
+ const rect = e.currentTarget.getBoundingClientRect()
+ const pct = (e.clientX - rect.left) / rect.width
+ seek(pct * duration)
+ }}>
+
+
+
+ )}
{/* Track info */}
@@ -101,37 +51,49 @@ export function PlayerBar() {
{/* Controls */}
-
+ {!isExternal && (
+
+ )}
-
+ {!isExternal && (
+
+ )}
{/* Volume & time */}
-
- {formatDuration(progress)} / {formatDuration(duration)}
-
-
- setVolume(parseFloat(e.target.value))}
- className="w-20"
- />
+ {!isExternal ? (
+ <>
+
+ {formatDuration(progress)} / {formatDuration(duration)}
+
+
+ setVolume(parseFloat(e.target.value))}
+ className="w-20"
+ />
+ >
+ ) : (
+
+ {duration > 0 && formatDuration(duration)}
+
+ )}
diff --git a/src/pages/PodcastDetail.tsx b/src/pages/PodcastDetail.tsx
index 604d104..c5df1c0 100644
--- a/src/pages/PodcastDetail.tsx
+++ b/src/pages/PodcastDetail.tsx
@@ -4,7 +4,6 @@ import { Play, Pause, Heart, MessageCircle, Clock, Share2 } from 'lucide-react'
import { supabase } from '@/lib/supabase'
import { useAuthStore } from '@/stores/auth'
import { usePlayerStore } from '@/stores/player'
-import { getEmbedInfo } from '@/lib/embed'
import type { Podcast, Comment } from '@/types'
import { formatDuration, timeAgo } from '@/lib/utils'
import { Avatar } from '@/components/ui/Avatar'
@@ -164,23 +163,6 @@ export function PodcastDetail() {
- {/* Embedded player for external content */}
- {(() => {
- const embed = getEmbedInfo(podcast.audio_url)
- if (!embed) return null
- return (
-
-
-
- )
- })()}
-
{podcast.description && (
Description
diff --git a/src/stores/player.ts b/src/stores/player.ts
index 516f1bf..a3a94cc 100644
--- a/src/stores/player.ts
+++ b/src/stores/player.ts
@@ -1,6 +1,26 @@
import { create } from 'zustand'
import type { Podcast } from '@/types'
-import { isExternalUrl } from '@/lib/embed'
+import { isExternalUrl, getEmbedInfo } from '@/lib/embed'
+
+// Hidden iframe manager for external content
+let hiddenIframe: HTMLIFrameElement | null = null
+
+function destroyIframe() {
+ if (hiddenIframe) {
+ hiddenIframe.remove()
+ hiddenIframe = null
+ }
+}
+
+function createHiddenIframe(embedUrl: string) {
+ destroyIframe()
+ const iframe = document.createElement('iframe')
+ iframe.src = embedUrl
+ iframe.allow = 'autoplay; encrypted-media'
+ iframe.style.cssText = 'position:fixed;width:1px;height:1px;left:-10px;top:-10px;opacity:0;pointer-events:none;'
+ document.body.appendChild(iframe)
+ hiddenIframe = iframe
+}
interface PlayerState {
current: Podcast | null
@@ -33,8 +53,11 @@ export const usePlayerStore = create
((set, get) => ({
const { audio, current } = get()
const external = isExternalUrl(podcast.audio_url)
+ // Resume same podcast
if (current?.id === podcast.id) {
if (external) {
+ const embed = getEmbedInfo(podcast.audio_url)
+ if (embed) createHiddenIframe(embed.embedUrl)
set({ isPlaying: true })
return
}
@@ -45,13 +68,17 @@ export const usePlayerStore = create((set, get) => ({
}
}
+ // Stop previous
if (audio) {
audio.pause()
audio.removeAttribute('src')
}
+ destroyIframe()
- // External URLs are handled by embed iframe, not HTMLAudioElement
+ // External: play via hidden iframe
if (external) {
+ const embed = getEmbedInfo(podcast.audio_url)
+ if (embed) createHiddenIframe(embed.embedUrl)
set({ audio: null, current: podcast, isPlaying: true, isExternal: true, progress: 0, duration: podcast.duration_seconds || 0 })
return
}
@@ -74,7 +101,17 @@ export const usePlayerStore = create((set, get) => ({
},
toggle: () => {
- const { audio, isPlaying } = get()
+ const { audio, isPlaying, isExternal, current } = get()
+ if (isExternal) {
+ if (isPlaying) {
+ destroyIframe()
+ } else {
+ const embed = current ? getEmbedInfo(current.audio_url) : null
+ if (embed) createHiddenIframe(embed.embedUrl)
+ }
+ set({ isPlaying: !isPlaying })
+ return
+ }
if (!audio) return
if (isPlaying) {
audio.pause()
@@ -85,6 +122,8 @@ export const usePlayerStore = create((set, get) => ({
},
pause: () => {
+ const { isExternal } = get()
+ if (isExternal) destroyIframe()
get().audio?.pause()
set({ isPlaying: false })
},