@waveform-playlist/recording 5.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +232 -0
- package/dist/index.d.mts +234 -0
- package/dist/index.d.ts +234 -0
- package/dist/index.js +756 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +709 -0
- package/dist/index.mjs.map +1 -0
- package/dist/worklet/recording-processor.worklet.js +64 -0
- package/dist/worklet/recording-processor.worklet.js.map +1 -0
- package/dist/worklet/recording-processor.worklet.mjs +62 -0
- package/dist/worklet/recording-processor.worklet.mjs.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/hooks/useRecording.ts","../src/utils/audioBufferUtils.ts","../src/utils/peaksGenerator.ts","../src/hooks/useMicrophoneAccess.ts","../src/hooks/useMicrophoneLevel.ts","../src/components/RecordButton.tsx","../src/components/MicrophoneSelector.tsx","../src/components/RecordingIndicator.tsx","../src/components/VUMeter.tsx"],"sourcesContent":["/**\n * @waveform-playlist/recording\n *\n * Audio recording support using AudioWorklet for waveform-playlist\n */\n\n// Hooks\nexport { useRecording, useMicrophoneAccess, useMicrophoneLevel } from './hooks';\nexport type {\n UseMicrophoneLevelOptions,\n UseMicrophoneLevelReturn,\n} from './hooks';\n\n// Components\nexport { RecordButton, MicrophoneSelector, RecordingIndicator, VUMeter } from './components';\nexport type {\n RecordButtonProps,\n MicrophoneSelectorProps,\n RecordingIndicatorProps,\n VUMeterProps,\n} from './components';\n\n// Types\nexport type {\n RecordingState,\n RecordingData,\n MicrophoneDevice,\n RecordingOptions,\n UseRecordingReturn,\n UseMicrophoneAccessReturn,\n} from './types';\n\n// Utilities\nexport { generatePeaks } from './utils/peaksGenerator';\nexport { createAudioBuffer, concatenateAudioData } from './utils/audioBufferUtils';\n","/**\n * Main recording hook using AudioWorklet\n */\n\nimport { useState, useRef, useCallback, useEffect } from 'react';\nimport { UseRecordingReturn, RecordingOptions } from '../types';\nimport { concatenateAudioData, createAudioBuffer } from '../utils/audioBufferUtils';\nimport { appendPeaks } from '../utils/peaksGenerator';\nimport { getContext } from 'tone';\n\nexport function useRecording(\n stream: MediaStream | null,\n options: RecordingOptions = {}\n): UseRecordingReturn {\n const {\n channelCount = 1,\n samplesPerPixel = 1024,\n } = options;\n\n // State\n const [isRecording, setIsRecording] = useState(false);\n const [isPaused, setIsPaused] = useState(false);\n const [duration, setDuration] = useState(0);\n const [peaks, setPeaks] = useState<Int8Array | Int16Array>(new Int16Array(0));\n const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [level, setLevel] = useState(0); // Current RMS level (0-1)\n const [peakLevel, setPeakLevel] = useState(0); // Peak level since recording started (0-1)\n\n const bits: 8 | 16 = 16; // Match the bit depth used by the final waveform\n\n // Global flag to prevent loading worklet multiple times\n // (AudioWorklet processors can only be registered once per AudioContext)\n const workletLoadedRef = useRef<boolean>(false);\n\n // Refs for AudioWorklet and data accumulation\n const workletNodeRef = useRef<AudioWorkletNode | null>(null);\n const mediaStreamSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const recordedChunksRef = useRef<Float32Array[]>([]);\n const totalSamplesRef = useRef(0);\n const animationFrameRef = useRef<number | null>(null);\n const startTimeRef = useRef<number>(0);\n const isRecordingRef = useRef<boolean>(false);\n const isPausedRef = useRef<boolean>(false);\n\n // Load AudioWorklet module\n const loadWorklet = useCallback(async () => {\n // Skip if already loaded to prevent \"already registered\" error\n if (workletLoadedRef.current) {\n return;\n }\n\n try {\n const context = getContext();\n // Load the worklet module\n // Use a relative path that works when bundled\n const workletUrl = new URL(\n './worklet/recording-processor.worklet.js',\n import.meta.url\n ).href;\n\n // Use Tone's addAudioWorkletModule for cross-browser compatibility\n await context.addAudioWorkletModule(workletUrl);\n workletLoadedRef.current = true;\n } catch (err) {\n console.error('Failed to load AudioWorklet module:', err);\n throw new Error('Failed to load recording processor');\n }\n }, []);\n\n // Start recording\n const startRecording = useCallback(async () => {\n if (!stream) {\n setError(new Error('No microphone stream available'));\n return;\n }\n\n try {\n setError(null);\n\n // Use Tone.js Context for cross-browser compatibility\n const context = getContext();\n\n // Resume AudioContext if suspended\n if (context.state === 'suspended') {\n await context.resume();\n }\n\n // Load worklet module\n await loadWorklet();\n\n // Create MediaStreamSource from Tone's context\n // Each hook creates its own source to avoid cross-context issues in Firefox\n const source = context.createMediaStreamSource(stream);\n mediaStreamSourceRef.current = source;\n\n // Create AudioWorklet node using Tone's method\n const workletNode = context.createAudioWorkletNode('recording-processor');\n workletNodeRef.current = workletNode;\n\n // Connect source to worklet (but not to destination - no monitoring)\n source.connect(workletNode);\n\n //Listen for audio data from worklet\n workletNode.port.onmessage = (event: MessageEvent) => {\n const { samples } = event.data;\n\n // Accumulate samples\n recordedChunksRef.current.push(samples);\n totalSamplesRef.current += samples.length;\n\n // Update peaks incrementally for live waveform visualization\n setPeaks((prevPeaks) =>\n appendPeaks(\n prevPeaks,\n samples,\n samplesPerPixel,\n totalSamplesRef.current - samples.length,\n bits\n )\n );\n\n // Note: VU meter levels come from useMicrophoneLevel (AnalyserNode)\n // We don't update level/peakLevel here to avoid conflicting state updates\n };\n\n // Start the worklet processor\n workletNode.port.postMessage({\n command: 'start',\n sampleRate: context.sampleRate,\n channelCount,\n });\n\n // Reset state\n recordedChunksRef.current = [];\n totalSamplesRef.current = 0;\n setPeaks(new Int16Array(0));\n setAudioBuffer(null);\n setLevel(0);\n setPeakLevel(0);\n isRecordingRef.current = true;\n isPausedRef.current = false;\n setIsRecording(true);\n setIsPaused(false);\n startTimeRef.current = performance.now();\n\n // Start duration update loop\n const updateDuration = () => {\n if (isRecordingRef.current && !isPausedRef.current) {\n const elapsed = (performance.now() - startTimeRef.current) / 1000;\n setDuration(elapsed);\n animationFrameRef.current = requestAnimationFrame(updateDuration);\n }\n };\n updateDuration();\n } catch (err) {\n console.error('Failed to start recording:', err);\n setError(err instanceof Error ? err : new Error('Failed to start recording'));\n }\n }, [stream, channelCount, samplesPerPixel, loadWorklet, isRecording, isPaused]);\n\n // Stop recording\n const stopRecording = useCallback(async (): Promise<AudioBuffer | null> => {\n if (!isRecording) {\n return null;\n }\n\n try {\n // Stop the worklet\n if (workletNodeRef.current) {\n workletNodeRef.current.port.postMessage({ command: 'stop' });\n\n // Disconnect worklet from source\n if (mediaStreamSourceRef.current) {\n try {\n mediaStreamSourceRef.current.disconnect(workletNodeRef.current);\n } catch (e) {\n // Source may have already been disconnected when stream changed\n // This is fine - just ignore the error\n }\n }\n workletNodeRef.current.disconnect();\n }\n\n // Cancel animation frame\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n // Create final AudioBuffer from accumulated chunks\n const allSamples = concatenateAudioData(recordedChunksRef.current);\n const context = getContext();\n // Use rawContext for createBuffer (native AudioContext method)\n const rawContext = context.rawContext as AudioContext;\n const buffer = createAudioBuffer(\n rawContext,\n allSamples,\n rawContext.sampleRate,\n channelCount\n );\n\n setAudioBuffer(buffer);\n setDuration(buffer.duration);\n isRecordingRef.current = false;\n isPausedRef.current = false;\n setIsRecording(false);\n setIsPaused(false);\n setLevel(0);\n // Keep peakLevel to show the peak reached during recording\n\n return buffer;\n } catch (err) {\n console.error('Failed to stop recording:', err);\n setError(err instanceof Error ? err : new Error('Failed to stop recording'));\n return null;\n }\n }, [isRecording, channelCount]);\n\n // Pause recording\n const pauseRecording = useCallback(() => {\n if (isRecording && !isPaused) {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n isPausedRef.current = true;\n setIsPaused(true);\n }\n }, [isRecording, isPaused]);\n\n // Resume recording\n const resumeRecording = useCallback(() => {\n if (isRecording && isPaused) {\n isPausedRef.current = false;\n setIsPaused(false);\n startTimeRef.current = performance.now() - duration * 1000;\n\n const updateDuration = () => {\n if (isRecordingRef.current && !isPausedRef.current) {\n const elapsed = (performance.now() - startTimeRef.current) / 1000;\n setDuration(elapsed);\n animationFrameRef.current = requestAnimationFrame(updateDuration);\n }\n };\n updateDuration();\n }\n }, [isRecording, isPaused, duration]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (workletNodeRef.current) {\n workletNodeRef.current.port.postMessage({ command: 'stop' });\n\n // Disconnect worklet from source\n if (mediaStreamSourceRef.current) {\n try {\n mediaStreamSourceRef.current.disconnect(workletNodeRef.current);\n } catch (e) {\n // Source may have already been disconnected when stream changed\n // This is fine - just ignore the error\n }\n }\n workletNodeRef.current.disconnect();\n }\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n // Don't close the global AudioContext - it's shared across the app\n };\n }, []);\n\n return {\n isRecording,\n isPaused,\n duration,\n peaks,\n audioBuffer,\n level,\n peakLevel,\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n error,\n };\n}\n","/**\n * Utility functions for working with AudioBuffers during recording\n */\n\n/**\n * Concatenate multiple Float32Arrays into a single array\n */\nexport function concatenateAudioData(chunks: Float32Array[]): Float32Array {\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Float32Array(totalLength);\n\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\n/**\n * Convert Float32Array to AudioBuffer\n */\nexport function createAudioBuffer(\n audioContext: AudioContext,\n samples: Float32Array,\n sampleRate: number,\n channelCount: number = 1\n): AudioBuffer {\n const buffer = audioContext.createBuffer(\n channelCount,\n samples.length,\n sampleRate\n );\n\n // Copy samples to buffer (for now, just mono)\n // Create a new Float32Array to ensure correct type\n const typedSamples = new Float32Array(samples);\n buffer.copyToChannel(typedSamples, 0);\n\n return buffer;\n}\n\n/**\n * Append new samples to an existing AudioBuffer\n */\nexport function appendToAudioBuffer(\n audioContext: AudioContext,\n existingBuffer: AudioBuffer | null,\n newSamples: Float32Array,\n sampleRate: number\n): AudioBuffer {\n if (!existingBuffer) {\n return createAudioBuffer(audioContext, newSamples, sampleRate);\n }\n\n // Get existing samples\n const existingData = existingBuffer.getChannelData(0);\n\n // Concatenate using concatenateAudioData helper\n const combined = concatenateAudioData([existingData, newSamples]);\n\n // Create new buffer\n return createAudioBuffer(audioContext, combined, sampleRate);\n}\n\n/**\n * Calculate duration in seconds from sample count and sample rate\n */\nexport function calculateDuration(sampleCount: number, sampleRate: number): number {\n return sampleCount / sampleRate;\n}\n","/**\n * Peak generation for real-time waveform visualization during recording\n * Matches the format used by webaudio-peaks: min/max pairs with bit depth\n */\n\n/**\n * Generate peaks from audio samples in standard min/max pair format\n *\n * @param samples - Audio samples to process\n * @param samplesPerPixel - Number of samples to represent in each peak\n * @param bits - Bit depth for peak values (8 or 16)\n * @returns Int8Array or Int16Array of peak values (min/max pairs)\n */\nexport function generatePeaks(\n samples: Float32Array,\n samplesPerPixel: number,\n bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n const numPeaks = Math.ceil(samples.length / samplesPerPixel);\n const peakArray = bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);\n const maxValue = 2 ** (bits - 1);\n\n for (let i = 0; i < numPeaks; i++) {\n const start = i * samplesPerPixel;\n const end = Math.min(start + samplesPerPixel, samples.length);\n\n let min = 0;\n let max = 0;\n\n for (let j = start; j < end; j++) {\n const value = samples[j];\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n // Store as min/max pairs scaled to bit depth\n peakArray[i * 2] = Math.floor(min * maxValue);\n peakArray[i * 2 + 1] = Math.floor(max * maxValue);\n }\n\n return peakArray;\n}\n\n/**\n * Append new peaks to existing peaks array\n * This is used for incremental peak updates during recording\n */\nexport function appendPeaks(\n existingPeaks: Int8Array | Int16Array,\n newSamples: Float32Array,\n samplesPerPixel: number,\n totalSamplesProcessed: number,\n bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n const maxValue = 2 ** (bits - 1);\n\n // Check if we have a partial peak from the last update\n const remainder = totalSamplesProcessed % samplesPerPixel;\n let offset = 0;\n\n // If there's a partial peak, we need to update the last peak\n if (remainder > 0 && existingPeaks.length > 0) {\n const samplesToComplete = samplesPerPixel - remainder;\n const endIndex = Math.min(samplesToComplete, newSamples.length);\n\n // Get current min/max from last peak\n let min = existingPeaks[existingPeaks.length - 2] / maxValue;\n let max = existingPeaks[existingPeaks.length - 1] / maxValue;\n\n // Update with new samples\n for (let i = 0; i < endIndex; i++) {\n const value = newSamples[i];\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n // Update last peak\n const updated = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length);\n updated.set(existingPeaks);\n updated[existingPeaks.length - 2] = Math.floor(min * maxValue);\n updated[existingPeaks.length - 1] = Math.floor(max * maxValue);\n\n offset = endIndex;\n\n // Generate peaks for remaining samples and concatenate\n const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n const result = new (bits === 8 ? Int8Array : Int16Array)(updated.length + newPeaks.length);\n result.set(updated);\n result.set(newPeaks, updated.length);\n return result;\n }\n\n // No partial peak, just concatenate\n const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n const result = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length + newPeaks.length);\n result.set(existingPeaks);\n result.set(newPeaks, existingPeaks.length);\n return result;\n}\n","/**\n * Hook for managing microphone access and device enumeration\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { UseMicrophoneAccessReturn, MicrophoneDevice } from '../types';\n\nexport function useMicrophoneAccess(): UseMicrophoneAccessReturn {\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [devices, setDevices] = useState<MicrophoneDevice[]>([]);\n const [hasPermission, setHasPermission] = useState(false);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Enumerate audio input devices\n const enumerateDevices = useCallback(async () => {\n try {\n const allDevices = await navigator.mediaDevices.enumerateDevices();\n const audioInputs = allDevices\n .filter((device) => device.kind === 'audioinput')\n .map((device) => ({\n deviceId: device.deviceId,\n label: device.label || `Microphone ${device.deviceId.slice(0, 8)}`,\n groupId: device.groupId,\n }));\n\n setDevices(audioInputs);\n } catch (err) {\n console.error('Failed to enumerate devices:', err);\n setError(err instanceof Error ? err : new Error('Failed to enumerate devices'));\n }\n }, []);\n\n // Request microphone access\n const requestAccess = useCallback(async (deviceId?: string, audioConstraints?: MediaTrackConstraints) => {\n setIsLoading(true);\n setError(null);\n\n try {\n // Stop existing stream if any\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n\n // Build audio constraints\n const audio: MediaTrackConstraints & { latency?: number } = {\n // Recording-optimized defaults: prioritize raw audio quality and low latency\n echoCancellation: false,\n noiseSuppression: false,\n autoGainControl: false,\n latency: 0, // Low latency mode (not in TS types yet, but supported in modern browsers)\n // User-provided constraints override defaults\n ...audioConstraints,\n // Device ID override (if specified)\n ...(deviceId && { deviceId: { exact: deviceId } }),\n };\n\n const constraints: MediaStreamConstraints = {\n audio,\n video: false,\n };\n\n const newStream = await navigator.mediaDevices.getUserMedia(constraints);\n setStream(newStream);\n setHasPermission(true);\n\n // Enumerate devices after getting permission (labels will be available)\n await enumerateDevices();\n } catch (err) {\n console.error('Failed to access microphone:', err);\n setError(\n err instanceof Error ? err : new Error('Failed to access microphone')\n );\n setHasPermission(false);\n } finally {\n setIsLoading(false);\n }\n }, [stream, enumerateDevices]);\n\n // Stop the stream and revoke access\n const stopStream = useCallback(() => {\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n setStream(null);\n setHasPermission(false);\n }\n }, [stream]);\n\n // Check initial permission state and enumerate devices\n useEffect(() => {\n // Try to enumerate devices (labels won't be available without permission)\n enumerateDevices();\n\n // Cleanup on unmount\n return () => {\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n };\n }, []);\n\n return {\n stream,\n devices,\n hasPermission,\n isLoading,\n requestAccess,\n stopStream,\n error,\n };\n}\n","/**\n * Hook for monitoring microphone input levels\n *\n * Uses Tone.js Meter for real-time audio level monitoring.\n */\n\nimport { useEffect, useState, useRef } from 'react';\nimport { Meter, getContext, connect } from 'tone';\n\nexport interface UseMicrophoneLevelOptions {\n /**\n * How often to update the level (in Hz)\n * Default: 60 (60fps)\n */\n updateRate?: number;\n\n /**\n * FFT size for the analyser\n * Default: 256\n */\n fftSize?: number;\n\n /**\n * Smoothing time constant (0-1)\n * Higher values = smoother but slower response\n * Default: 0.8\n */\n smoothingTimeConstant?: number;\n}\n\nexport interface UseMicrophoneLevelReturn {\n /**\n * Current audio level (0-1)\n * 0 = silence, 1 = maximum level\n */\n level: number;\n\n /**\n * Peak level since last reset (0-1)\n */\n peakLevel: number;\n\n /**\n * Reset the peak level\n */\n resetPeak: () => void;\n}\n\n/**\n * Monitor microphone input levels in real-time\n *\n * @param stream - MediaStream from getUserMedia\n * @param options - Configuration options\n * @returns Object with current level and peak level\n *\n * @example\n * ```typescript\n * const { stream } = useMicrophoneAccess();\n * const { level, peakLevel, resetPeak } = useMicrophoneLevel(stream);\n *\n * return <VUMeter level={level} peakLevel={peakLevel} />;\n * ```\n */\nexport function useMicrophoneLevel(\n stream: MediaStream | null,\n options: UseMicrophoneLevelOptions = {}\n): UseMicrophoneLevelReturn {\n const {\n updateRate = 60,\n smoothingTimeConstant = 0.8,\n } = options;\n\n const [level, setLevel] = useState(0);\n const [peakLevel, setPeakLevel] = useState(0);\n\n const meterRef = useRef<Meter | null>(null);\n const sourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n const resetPeak = () => setPeakLevel(0);\n\n useEffect(() => {\n if (!stream) {\n setLevel(0);\n setPeakLevel(0);\n return;\n }\n\n let isMounted = true;\n\n // Setup audio monitoring\n const setupMonitoring = async () => {\n if (!isMounted) return;\n\n // Get Tone's context and resume if needed\n const context = getContext();\n if (context.state === 'suspended') {\n await context.resume();\n }\n\n if (!isMounted) return;\n\n // Create Tone.js Meter for level monitoring\n // Pass context to ensure it's created in the same context as the source\n const meter = new Meter({ smoothing: smoothingTimeConstant, context });\n meterRef.current = meter;\n\n // Create MediaStreamSource from the SAME context as the meter\n // Note: This creates a separate source from useRecording, but that's OK\n // since we're only using it for level monitoring (not recording)\n const source = context.createMediaStreamSource(stream);\n sourceRef.current = source;\n\n // Connect source to meter using Tone's connect function\n connect(source, meter);\n\n // Start level monitoring\n const updateInterval = 1000 / updateRate;\n let lastUpdateTime = 0;\n\n const updateLevel = (timestamp: number) => {\n if (!isMounted || !meterRef.current) return;\n\n if (timestamp - lastUpdateTime >= updateInterval) {\n lastUpdateTime = timestamp;\n\n // Meter.getValue() returns dB, convert to 0-1 range\n const db = meterRef.current.getValue();\n const dbValue = typeof db === 'number' ? db : db[0];\n // dB is typically -Infinity to 0, map -100dB..0dB to 0..1\n // Using -100dB as floor since Firefox seems to report lower values\n const normalized = Math.max(0, Math.min(1, (dbValue + 100) / 100));\n\n setLevel(normalized);\n setPeakLevel(prev => Math.max(prev, normalized));\n }\n\n animationFrameRef.current = requestAnimationFrame(updateLevel);\n };\n\n animationFrameRef.current = requestAnimationFrame(updateLevel);\n };\n\n setupMonitoring();\n\n // Cleanup\n return () => {\n isMounted = false;\n\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n // Disconnect and clean up\n if (sourceRef.current) {\n try {\n sourceRef.current.disconnect();\n } catch (e) {\n // Ignore disconnect errors\n }\n sourceRef.current = null;\n }\n\n if (meterRef.current) {\n meterRef.current.dispose();\n meterRef.current = null;\n }\n };\n }, [stream, smoothingTimeConstant, updateRate]);\n\n return {\n level,\n peakLevel,\n resetPeak,\n };\n}\n","/**\n * RecordButton - Control button for starting/stopping recording\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface RecordButtonProps {\n isRecording: boolean;\n onClick: () => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst Button = styled.button<{ $isRecording: boolean }>`\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n border: none;\n border-radius: 0.25rem;\n cursor: pointer;\n transition: all 0.2s ease-in-out;\n background: ${(props) => (props.$isRecording ? '#dc3545' : '#e74c3c')};\n color: white;\n\n &:hover:not(:disabled) {\n background: ${(props) => (props.$isRecording ? '#c82333' : '#c0392b')};\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n &:active:not(:disabled) {\n transform: translateY(0);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.3);\n }\n`;\n\nconst RecordingIndicator = styled.span`\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: white;\n margin-right: 0.5rem;\n animation: pulse 1.5s ease-in-out infinite;\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.3;\n }\n }\n`;\n\nexport const RecordButton: React.FC<RecordButtonProps> = ({\n isRecording,\n onClick,\n disabled = false,\n className,\n}) => {\n return (\n <Button\n $isRecording={isRecording}\n onClick={onClick}\n disabled={disabled}\n className={className}\n aria-label={isRecording ? 'Stop recording' : 'Start recording'}\n >\n {isRecording && <RecordingIndicator />}\n {isRecording ? 'Stop Recording' : 'Record'}\n </Button>\n );\n};\n","/**\n * MicrophoneSelector - Dropdown for selecting microphone input device\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport { BaseSelect, BaseLabel } from '@waveform-playlist/ui-components';\nimport { MicrophoneDevice } from '../types';\n\nexport interface MicrophoneSelectorProps {\n devices: MicrophoneDevice[];\n selectedDeviceId?: string;\n onDeviceChange: (deviceId: string) => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst Select = styled(BaseSelect)`\n min-width: 200px;\n`;\n\nconst Label = styled(BaseLabel)`\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n`;\n\nexport const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = ({\n devices,\n selectedDeviceId,\n onDeviceChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {\n onDeviceChange(event.target.value);\n };\n\n // Use first device if no selection provided\n const currentValue = selectedDeviceId || (devices.length > 0 ? devices[0].deviceId : '');\n\n return (\n <Label className={className}>\n Microphone\n <Select\n value={currentValue}\n onChange={handleChange}\n disabled={disabled || devices.length === 0}\n >\n {devices.length === 0 ? (\n <option value=\"\">No microphones found</option>\n ) : (\n devices.map((device) => (\n <option key={device.deviceId} value={device.deviceId}>\n {device.label}\n </option>\n ))\n )}\n </Select>\n </Label>\n );\n};\n","/**\n * RecordingIndicator - Shows recording status, duration, and visual indicator\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface RecordingIndicatorProps {\n isRecording: boolean;\n isPaused?: boolean;\n duration: number; // in seconds\n formatTime?: (seconds: number) => string;\n className?: string;\n}\n\nconst Container = styled.div<{ $isRecording: boolean }>`\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 0.75rem;\n background: ${(props) => (props.$isRecording ? '#fff3cd' : 'transparent')};\n border-radius: 0.25rem;\n transition: background 0.2s ease-in-out;\n`;\n\nconst Dot = styled.div<{ $isRecording: boolean; $isPaused: boolean }>`\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: ${(props) => (props.$isPaused ? '#ffc107' : '#dc3545')};\n opacity: ${(props) => (props.$isRecording ? 1 : 0)};\n transition: opacity 0.2s ease-in-out;\n\n ${(props) =>\n props.$isRecording &&\n !props.$isPaused &&\n `\n animation: blink 1.5s ease-in-out infinite;\n\n @keyframes blink {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.3;\n }\n }\n `}\n`;\n\nconst Duration = styled.span`\n font-family: 'Courier New', Monaco, monospace;\n font-size: 1rem;\n font-weight: 600;\n color: #495057;\n min-width: 70px;\n`;\n\nconst Status = styled.span<{ $isPaused: boolean }>`\n font-size: 0.75rem;\n font-weight: 500;\n color: ${(props) => (props.$isPaused ? '#ffc107' : '#dc3545')};\n text-transform: uppercase;\n`;\n\nconst defaultFormatTime = (seconds: number): string => {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n};\n\nexport const RecordingIndicator: React.FC<RecordingIndicatorProps> = ({\n isRecording,\n isPaused = false,\n duration,\n formatTime = defaultFormatTime,\n className,\n}) => {\n return (\n <Container $isRecording={isRecording} className={className}>\n <Dot $isRecording={isRecording} $isPaused={isPaused} />\n <Duration>{formatTime(duration)}</Duration>\n {isRecording && <Status $isPaused={isPaused}>{isPaused ? 'Paused' : 'Recording'}</Status>}\n </Container>\n );\n};\n","/**\n * VU Meter Component\n *\n * Displays real-time audio input levels with color-coded zones\n * and peak indicator.\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface VUMeterProps {\n /**\n * Current audio level (0-1)\n */\n level: number;\n\n /**\n * Peak level (0-1)\n * Optional - if provided, shows peak indicator\n */\n peakLevel?: number;\n\n /**\n * Width of the meter in pixels\n * Default: 200\n */\n width?: number;\n\n /**\n * Height of the meter in pixels\n * Default: 20\n */\n height?: number;\n\n /**\n * Additional CSS class name\n */\n className?: string;\n}\n\nconst MeterContainer = styled.div<{ $width: number; $height: number }>`\n position: relative;\n width: ${props => props.$width}px;\n height: ${props => props.$height}px;\n background: #2c3e50;\n border-radius: 4px;\n overflow: hidden;\n box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);\n`;\n\n// Helper to get gradient color based on level\nconst getLevelGradient = (level: number): string => {\n if (level < 0.6) return 'linear-gradient(90deg, #27ae60, #2ecc71)';\n if (level < 0.85) return 'linear-gradient(90deg, #f39c12, #f1c40f)';\n return 'linear-gradient(90deg, #c0392b, #e74c3c)';\n};\n\n// Use .attrs() for frequently changing styles to avoid generating new CSS classes\nconst MeterFill = styled.div.attrs<{ $level: number; $height: number }>(props => ({\n style: {\n width: `${props.$level * 100}%`,\n height: `${props.$height}px`,\n background: getLevelGradient(props.$level),\n boxShadow: props.$level > 0.01 ? '0 0 8px rgba(255, 255, 255, 0.3)' : 'none',\n },\n}))<{ $level: number; $height: number }>`\n position: absolute;\n left: 0;\n top: 0;\n transition: width 0.05s ease-out, background 0.1s ease-out;\n`;\n\n// Use .attrs() for frequently changing left position\nconst PeakIndicator = styled.div.attrs<{ $peakLevel: number; $height: number }>(props => ({\n style: {\n left: `${props.$peakLevel * 100}%`,\n height: `${props.$height}px`,\n },\n}))<{ $peakLevel: number; $height: number }>`\n position: absolute;\n top: 0;\n width: 2px;\n background: #ecf0f1;\n box-shadow: 0 0 4px rgba(236, 240, 241, 0.8);\n transition: left 0.1s ease-out;\n`;\n\nconst ScaleMarkers = styled.div<{ $height: number }>`\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: ${props => props.$height}px;\n pointer-events: none;\n`;\n\nconst ScaleMark = styled.div<{ $position: number; $height: number }>`\n position: absolute;\n left: ${props => props.$position}%;\n top: 0;\n width: 1px;\n height: ${props => props.$height}px;\n background: rgba(255, 255, 255, 0.2);\n`;\n\n/**\n * VU Meter component for displaying audio input levels\n *\n * @example\n * ```typescript\n * import { useMicrophoneLevel, VUMeter } from '@waveform-playlist/recording';\n *\n * const { level, peakLevel } = useMicrophoneLevel(stream);\n *\n * return <VUMeter level={level} peakLevel={peakLevel} width={300} height={24} />;\n * ```\n */\nconst VUMeterComponent: React.FC<VUMeterProps> = ({\n level,\n peakLevel,\n width = 200,\n height = 20,\n className,\n}) => {\n // Clamp values to 0-1 range\n const clampedLevel = Math.max(0, Math.min(1, level));\n const clampedPeak = peakLevel !== undefined\n ? Math.max(0, Math.min(1, peakLevel))\n : 0;\n\n return (\n <MeterContainer $width={width} $height={height} className={className}>\n <MeterFill $level={clampedLevel} $height={height} />\n\n {peakLevel !== undefined && clampedPeak > 0 && (\n <PeakIndicator $peakLevel={clampedPeak} $height={height} />\n )}\n\n <ScaleMarkers $height={height}>\n <ScaleMark $position={60} $height={height} />\n <ScaleMark $position={85} $height={height} />\n </ScaleMarkers>\n </MeterContainer>\n );\n};\n\n// Memoize to prevent unnecessary re-renders when parent updates\nexport const VUMeter = React.memo(VUMeterComponent);\n"],"mappings":"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","names":["RecordingIndicator","newPeaks","result","import_react","import_react","import_tone","styled","import_styled_components","import_jsx_runtime","styled","import_styled_components","import_jsx_runtime","styled","RecordingIndicator","import_react","import_styled_components","import_jsx_runtime","styled","React"]}
|