import { create } from 'zustand' import type { Podcast } from '@/types' 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 isPlaying: boolean isExternal: boolean progress: number duration: number volume: number audio: HTMLAudioElement | null play: (podcast: Podcast) => void toggle: () => void pause: () => void seek: (time: number) => void setVolume: (vol: number) => void setProgress: (progress: number) => void setDuration: (duration: number) => void } export const usePlayerStore = create((set, get) => ({ current: null, isPlaying: false, isExternal: false, progress: 0, duration: 0, volume: 0.8, audio: null, play: (podcast) => { 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 } if (audio) { audio.play() set({ isPlaying: true }) return } } // Stop previous if (audio) { audio.pause() audio.removeAttribute('src') } destroyIframe() // 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 } const newAudio = new Audio(podcast.audio_url) newAudio.volume = get().volume newAudio.addEventListener('timeupdate', () => { set({ progress: newAudio.currentTime }) }) newAudio.addEventListener('loadedmetadata', () => { set({ duration: newAudio.duration }) }) newAudio.addEventListener('ended', () => { set({ isPlaying: false, progress: 0 }) }) newAudio.play() set({ audio: newAudio, current: podcast, isPlaying: true, isExternal: false, progress: 0 }) }, toggle: () => { 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() } else { audio.play() } set({ isPlaying: !isPlaying }) }, pause: () => { const { isExternal } = get() if (isExternal) destroyIframe() get().audio?.pause() set({ isPlaying: false }) }, seek: (time) => { const { audio } = get() if (!audio) return audio.currentTime = time set({ progress: time }) }, setVolume: (vol) => { const { audio } = get() if (audio) audio.volume = vol set({ volume: vol }) }, setProgress: (progress) => set({ progress }), setDuration: (duration) => set({ duration }), }))