import { forwardRef, useEffect, useImperativeHandle, useRef, useState, } from 'react'; import type { Particle } from '@/api/types'; import type { ParticlePath } from '@/lib/particle-path'; import { useDownloadUrl } from '@/hooks/use-download-url'; import { useTranscriptPlayback } from '@/hooks/use-transcript-playback'; import { TranscriptOverlay } from '@/features/particles/transcript-overlay'; import { Skeleton } from '@/components/ui/skeleton'; import { CenteredWaveform } from '@/components/audio/centered-waveform'; import { useAudioSource } from '@/components/audio/use-audio-source'; import { useParticleAttachments } from '@/hooks/use-particle-attachments'; import { ParticleAttachments } from '@/features/particles/particle-attachments'; type MediaParticle = Extract; export interface MediaParticleHandle { /** Seek by delta. Returns true if seeked, false if at boundary (should navigate). */ seek: (deltaSec: number) => boolean; setPlaybackRate: (rate: number) => void; } interface MediaParticleViewProps { particle: MediaParticle; streamPath: ParticlePath; paused: boolean; onEnded: () => void; onProgress?: (ratio: number) => void; } export const MediaParticleView = forwardRef< MediaParticleHandle, MediaParticleViewProps >(function MediaParticleView( { particle, streamPath, paused, onEnded, onProgress }, ref, ) { // Prefer the worker-produced iOS-playable variant when present so desktop and // mobile read the same canonical asset, falling back to the original. Pinned // on mount (the parent keys this component by particle.id, so a new particle // remounts and re-picks): if a transcoded variant arrives via Firestore for // the same particle, swapping the