--- name: composable-svelte-media description: Audio, video, and voice components for Composable Svelte. Use when implementing audio players, video embeds, or voice input. Covers AudioPlayer (Web Audio API), VideoEmbed (YouTube/Vimeo/Twitch), and VoiceInput (MediaRecorder) from @composable-svelte/media package. --- # Composable Svelte Media Package Audio playback, video embedding, and voice input components. --- ## PACKAGE OVERVIEW **Package**: `@composable-svelte/media` **Purpose**: Rich interactive media components for audio, video, and voice. **Technology Stack**: - **Web Audio API**: High-performance audio playback - **MediaRecorder API**: Voice recording and processing - **Platform Integration**: YouTube, Vimeo, Twitch, Dailymotion, Wistia, etc. **Core Components**: - `AudioPlayer` - Full-featured audio player with playlists - `VideoEmbed` - Platform-agnostic video embedding - `VoiceInput` - Voice recording with push-to-talk **State Management**: All components follow Composable Architecture patterns with dedicated reducers and type-safe actions. --- ## AUDIO PLAYER **Purpose**: Full-featured audio player with playlist support, shuffle, loop modes, and visualizations. ### Quick Start ```typescript import { createStore } from '@composable-svelte/core'; import { MinimalAudioPlayer, FullAudioPlayer, audioPlayerReducer, createInitialAudioPlayerState } from '@composable-svelte/media'; // Create player store const playerStore = createStore({ initialState: createInitialAudioPlayerState({ tracks: [ { id: '1', title: 'Summer Breeze', artist: 'Jazz Ensemble', url: '/audio/track1.mp3', duration: 245 }, { id: '2', title: 'Midnight Drive', artist: 'Synthwave Collective', url: '/audio/track2.mp3', duration: 312 } ] }), reducer: audioPlayerReducer, dependencies: {} }); // Render player ``` ### Component Variants **MinimalAudioPlayer**: - Compact UI (play/pause, track info, progress bar) - Best for embedded players - No playlist UI **FullAudioPlayer**: - Complete controls (play/pause, skip, shuffle, loop, volume) - Playlist view - Audio visualizer - Best for dedicated music players **PlaylistView**: - Standalone playlist component - Drag-and-drop reordering - Track search/filter - Use with either player variant ### Props **MinimalAudioPlayer**: - `playerStore: Store` - Player store (required) **FullAudioPlayer**: - `playerStore: Store` - Player store (required) - `showVisualizer: boolean` - Show audio visualizer (default: true) - `showPlaylist: boolean` - Show playlist UI (default: true) ### State Interface ```typescript interface AudioPlayerState { // Playback isPlaying: boolean; currentTime: number; duration: number; volume: number; // 0-100 isMuted: boolean; // Playlist tracks: AudioTrack[]; currentTrackIndex: number; queue: string[]; // Track IDs // Modes loopMode: 'none' | 'one' | 'all'; shuffle: boolean; shuffleOrder: number[] | null; // UI State isLoading: boolean; isSeeking: boolean; error: string | null; // Visualizer visualizerData: Uint8Array | null; } interface AudioTrack { id: string; title: string; artist: string; url: string; duration: number; albumArt?: string; album?: string; } ``` ### Actions ```typescript type AudioPlayerAction = // Playback Control | { type: 'play' } | { type: 'pause' } | { type: 'togglePlayPause' } | { type: 'stop' } | { type: 'seek'; time: number } // Track Navigation | { type: 'nextTrack' } | { type: 'previousTrack' } | { type: 'selectTrack'; trackIndex: number } // Volume | { type: 'setVolume'; volume: number } | { type: 'toggleMute' } // Modes | { type: 'toggleShuffle' } | { type: 'cycleLoopMode' } | { type: 'setLoopMode'; mode: 'none' | 'one' | 'all' } // Playlist | { type: 'addTrack'; track: AudioTrack } | { type: 'removeTrack'; trackId: string } | { type: 'clearPlaylist' } // Internal Events | { type: 'timeUpdate'; time: number } | { type: 'trackEnded' } | { type: 'loadingStarted' } | { type: 'loadingCompleted'; duration: number } | { type: 'errorOccurred'; error: string }; ``` ### Complete Example ```typescript
{#if $playerStore.error}
{$playerStore.error}
{/if}
``` --- ## VIDEO EMBED **Purpose**: Platform-agnostic video embedding for YouTube, Vimeo, Twitch, Dailymotion, and more. ### Quick Start ```typescript import { VideoEmbed } from '@composable-svelte/media'; ``` ### Supported Platforms - **YouTube** (youtube.com, youtu.be) - **Vimeo** (vimeo.com) - **Twitch** (twitch.tv - videos and clips) - **Dailymotion** (dailymotion.com) - **Wistia** (wistia.com) - **Generic video** (mp4, webm, ogg via HTML5 video element) ### Props - `url: string` - Video URL (required) - `aspectRatio: '16:9' | '4:3' | '1:1' | '21:9'` - Aspect ratio (default: '16:9') - `autoplay: boolean` - Auto-play video (default: false) - `muted: boolean` - Start muted (default: false) - `controls: boolean` - Show controls (default: true) - `loop: boolean` - Loop video (default: false) - `startTime: number` - Start position in seconds (optional) - `class: string` - Custom CSS class (optional) ### Utility Functions ```typescript import { detectVideo, extractVideosFromMarkdown, getPlatformConfig, getSupportedPlatforms } from '@composable-svelte/media'; // Detect platform from URL const platform = detectVideo('https://www.youtube.com/watch?v=abc123'); // Returns: 'youtube' // Extract all videos from markdown const videos = extractVideosFromMarkdown(markdownText); // Returns: [{ url: '...', platform: 'youtube', id: 'abc123' }, ...] // Get platform configuration const config = getPlatformConfig('youtube'); // Returns: { name: 'YouTube', embedTemplate: '...', ... } // List all supported platforms const platforms = getSupportedPlatforms(); // Returns: ['youtube', 'vimeo', 'twitch', ...] ``` ### Examples ```typescript ``` ### Markdown Integration ```typescript {#each videos as video} {/each} ``` --- ## VOICE INPUT **Purpose**: Voice recording with push-to-talk and conversation modes, real-time transcription support. ### Quick Start ```typescript import { createStore } from '@composable-svelte/core'; import { VoiceInput, voiceInputReducer, createInitialVoiceInputState } from '@composable-svelte/media'; // Create voice input store const voiceStore = createStore({ initialState: createInitialVoiceInputState({ mode: 'push-to-talk' }), reducer: voiceInputReducer, dependencies: { onAudioData: async (audioBlob) => { // Send to transcription service const formData = new FormData(); formData.append('audio', audioBlob); const response = await fetch('/api/transcribe', { method: 'POST', body: formData }); const { text } = await response.json(); return text; } } }); ``` ### Recording Modes **Push-to-Talk**: - Hold button to record - Release to stop - Best for short messages - Lower latency **Conversation**: - Toggle recording on/off - Best for long-form speech - Automatic silence detection (optional) ### Props - `voiceStore: Store` - Voice input store (required) - `showWaveform: boolean` - Show audio waveform (default: true) - `showTimer: boolean` - Show recording timer (default: true) - `class: string` - Custom CSS class (optional) ### State Interface ```typescript interface VoiceInputState { // Recording isRecording: boolean; isPaused: boolean; mode: 'push-to-talk' | 'conversation'; // Audio audioBlob: Blob | null; audioUrl: string | null; duration: number; // Recording duration in seconds // Transcription isTranscribing: boolean; transcript: string | null; transcriptError: string | null; // Visualization waveformData: Uint8Array | null; volumeLevel: number; // 0-100 // Error handling error: string | null; permissionDenied: boolean; } ``` ### Actions ```typescript type VoiceInputAction = // Recording | { type: 'startRecording' } | { type: 'stopRecording' } | { type: 'pauseRecording' } | { type: 'resumeRecording' } | { type: 'cancelRecording' } // Mode | { type: 'setMode'; mode: 'push-to-talk' | 'conversation' } // Transcription | { type: 'transcriptionStarted' } | { type: 'transcriptionCompleted'; transcript: string } | { type: 'transcriptionFailed'; error: string } // Internal Events | { type: 'recordingStarted' } | { type: 'recordingStopped'; audioBlob: Blob; duration: number } | { type: 'audioDataAvailable'; data: Uint8Array } | { type: 'volumeChanged'; level: number } | { type: 'errorOccurred'; error: string } | { type: 'permissionDenied' }; ``` ### Dependencies ```typescript interface VoiceInputDependencies { // Transcription handler (optional) onAudioData?: (audioBlob: Blob) => Promise; // Audio processing (optional) onAudioProcessed?: (audioBlob: Blob) => Promise; } ``` ### Complete Example ```typescript
{#if $voiceStore.transcript}
Transcript:

{$voiceStore.transcript}

{/if} {#if $voiceStore.error}
{$voiceStore.error}
{/if} {#if $voiceStore.permissionDenied}
Microphone access denied
{/if}
``` ### Browser Permissions Voice input requires microphone permissions. Handle permission flow: ```typescript // Check permission before recording if ($voiceStore.permissionDenied) { // Show permission request UI alert('Please grant microphone access to use voice input'); } ``` --- ## AUDIO MANAGER **Purpose**: Low-level audio management for custom implementations. ### API ```typescript import { AudioManager, createAudioManager, getAudioManager, deleteAudioManager } from '@composable-svelte/media'; // Create manager const manager = createAudioManager({ id: 'my-player', onTimeUpdate: (time) => console.log('Time:', time), onEnded: () => console.log('Ended'), onError: (error) => console.error('Error:', error) }); // Load and play await manager.load('/audio/track.mp3'); manager.play(); // Get existing manager const existing = getAudioManager('my-player'); // Cleanup deleteAudioManager('my-player'); ``` --- ## COMPONENT SELECTION GUIDE **When to use each component**: **AudioPlayer**: - Music streaming apps - Podcast players - Audio courses - Meditation apps - Need playlist management **VideoEmbed**: - Blog posts with videos - Video galleries - Educational content - Marketing pages - Platform-agnostic video **VoiceInput**: - Voice commands - Audio messages - Voice notes - Transcription apps - Accessibility features --- ## CROSS-REFERENCES **Related Skills**: - **composable-svelte-core**: Store, reducer, Effect system - **composable-svelte-chat**: StreamingChat (can integrate with VoiceInput) - **composable-svelte-code**: CodeEditor, syntax highlighting - **composable-svelte-components**: UI components (Button, Input, etc.) **When to Use Each Package**: - **media**: Audio players, video embeds, voice input - **chat**: Real-time chat, streaming responses - **code**: Code editors, syntax highlighting, visual programming - **graphics**: 3D scenes, WebGPU/WebGL rendering - **charts**: 2D data visualization --- ## TESTING PATTERNS ### AudioPlayer Testing ```typescript import { TestStore } from '@composable-svelte/core'; import { audioPlayerReducer, createInitialAudioPlayerState } from '@composable-svelte/media'; const store = new TestStore({ initialState: createInitialAudioPlayerState({ tracks: [ { id: '1', title: 'Track 1', url: '/audio/1.mp3', duration: 180 } ] }), reducer: audioPlayerReducer, dependencies: {} }); // Test play await store.send({ type: 'play' }, (state) => { expect(state.isPlaying).toBe(true); }); // Test track change await store.send({ type: 'nextTrack' }, (state) => { expect(state.currentTrackIndex).toBe(1); }); ``` ### VoiceInput Testing ```typescript import { TestStore } from '@composable-svelte/core'; import { voiceInputReducer, createInitialVoiceInputState } from '@composable-svelte/media'; const store = new TestStore({ initialState: createInitialVoiceInputState(), reducer: voiceInputReducer, dependencies: { onAudioData: vi.fn((blob) => Promise.resolve('Test transcript')) } }); // Test recording start await store.send({ type: 'startRecording' }); await store.receive({ type: 'recordingStarted' }, (state) => { expect(state.isRecording).toBe(true); }); ``` --- ## TROUBLESHOOTING **AudioPlayer not playing**: - Check audio URL is accessible - Verify browser autoplay policy (may require user interaction) - Ensure audio format is supported (mp3, wav, ogg recommended) **VideoEmbed not loading**: - Verify URL format matches platform requirements - Check platform embed permissions (some require API keys) - Ensure platform allows embedding (some videos are restricted) **VoiceInput permission denied**: - User must grant microphone access - HTTPS required (except localhost) - Check browser compatibility (MediaRecorder API)