waveframe 0.1.3 → 0.2.1

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/README.md CHANGED
@@ -1,78 +1,75 @@
1
1
  # Waveframe
2
2
 
3
- A high-performance, professional React audio player featuring SoundCloud-style waveforms, built-in audio analysis, and deep customization.
4
-
5
- [![NPM Version](https://img.shields.io/npm/v/waveframe?color=blue&style=flat-square)](https://www.npmjs.com/package/waveframe)
6
- [![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat-square)](LICENSE)
7
- [![GitHub](https://img.shields.io/badge/github-gradippp%2Fwaveframe-black?style=flat-square&logo=github)](https://github.com/gradippp/waveframe)
3
+ Waveframe is a soundcloud-embed inspired web audio player for React. It handles audio playback and waveform visualization with a modular engine.
8
4
 
9
5
  ## Features
10
6
 
11
- - **Ultra-Efficient Rendering**: Uses a dual-layer CSS-clipped canvas engine. No 60fps re-draws during playback, saving CPU and battery.
12
- - **Auto-Analysis**: Don't have peak data? Just provide a URL. Waveframe uses the Web Audio API to analyze and generate waveforms on-the-fly.
13
- - **Modern Theming**: Fully customizable with a single theme object. Supports deep navy dark modes and crisp light themes.
14
- - **Responsive & Fluid**: Proportional scaling ensures your waveform looks perfect on any screen size, from mobile to ultra-wide.
15
- - **Developer First**: Built with TypeScript, fully memoized, and includes a live [Configuration Playground](https://gradippp.github.io/waveframe).
7
+ - Canvas-based waveform rendering.
8
+ - Automatic peak analysis for URLs and Blobs.
9
+ - Customizable colors via a theme object or CSS variables.
10
+ - A hook-based API for building custom layouts.
11
+ - Support for local audio and artwork file uploads.
12
+ - Responsive scaling to fit different container sizes.
16
13
 
17
14
  ## Installation
18
15
 
19
16
  ```bash
20
17
  pnpm add waveframe
21
- # or
22
- npm install waveframe
23
18
  ```
24
19
 
25
- > **Note**: Waveframe requires **React 19+**.
20
+ Note: Waveframe requires React 19 or newer.
26
21
 
27
22
  ## Quick Start
28
23
 
24
+ The simplest way to use Waveframe is to provide an audio source via the `media` prop.
25
+
29
26
  ```tsx
30
27
  import { WaveframePlayer } from 'waveframe';
31
- import 'waveframe/style.css'; // Essential styles
28
+ import 'waveframe/style.css';
32
29
 
33
30
  const App = () => {
34
31
  return (
35
32
  <WaveframePlayer
36
33
  title="Electronic Sunset"
37
34
  artist="Digital Nomad"
38
- audioUrl="https://example.com/audio.mp3"
39
- artworkUrl="https://example.com/cover.jpg"
35
+ media="https://example.com/audio.mp3"
36
+ artwork="https://example.com/cover.jpg"
40
37
  />
41
38
  );
42
39
  };
43
40
  ```
44
41
 
45
- ## API Reference
42
+ ## Component Reference
46
43
 
47
- ### Props
44
+ ### Primary Props
48
45
 
49
- | Prop | Type | Default | Description |
50
- | :--- | :--- | :--- | :--- |
51
- | `audioUrl` | `string` | **Required** | Direct link to your audio file. |
52
- | `peaks` | `number[]` | `undefined` | Optional array of normalized peaks (0-1). Triggers **Auto-Analysis** if omitted. |
53
- | `title` | `string` | `undefined` | Track title. |
54
- | `artist` | `string` | `undefined` | Artist name. |
55
- | `artworkUrl` | `string` | `undefined` | Cover art image URL. |
56
- | `theme` | `WaveframeTheme` | `Light` | Object to customize colors (see below). |
57
- | `resolution` | `number \| 'auto'`| `'auto'` | Target number of waveform bars. |
58
- | `barWidth` | `number` | `2` | Width of waveform bars in pixels. |
59
- | `barGap` | `number` | `1` | Space between bars in pixels. |
60
- | `height` | `number` | `80` | Height of the waveform in pixels. |
61
- | `autoAnalyze`| `boolean` | `true` | Automatically generate peaks if missing. |
46
+ | Prop | Type | Description |
47
+ | :--- | :--- | :--- |
48
+ | `media` | `string \| Blob` | Audio source (URL or local File/Blob). |
49
+ | `peaks` | `number[]` | Optional pre-computed peaks to skip analysis. |
50
+ | `artwork`| `string \| Blob` | Artwork image source (URL or local File/Blob). |
51
+ | `title` | `string` | Track title. |
52
+ | `artist` | `string` | Artist name. |
53
+ | `theme` | `WaveframeTheme` | Object for color customization. |
62
54
 
63
- ### Theme Object
55
+ ## Architecture
64
56
 
65
- Customizing the player's palette is straightforward:
57
+ Waveframe separates playback logic from the UI to allow for custom implementations.
66
58
 
67
- ```tsx
68
- theme={{
69
- bg: "#111827", // Main card background
70
- primary: "#3b82f6", // Accent (progress & play button)
71
- text: "#f9fafb", // Text color
72
- border: "#1f2937" // Border and divider lines
73
- }}
74
- ```
59
+ - **useWaveframe**: A hook for controlling the player state and analysis.
60
+ - **WaveframeEngine**: The core logic class.
61
+ - **Waveform**: A component for rendering peaks.
62
+
63
+ For technical guides on building custom layouts or handling file workflows, refer to the full documentation.
64
+
65
+ ## Documentation and Playground
66
+
67
+ ### Interactive Playground
68
+ `pnpm run dev`
69
+
70
+ ### API Reference
71
+ `pnpm run docs`
75
72
 
76
73
  ## License
77
74
 
78
- MIT © [Agradip](mailto:me@agradip.fyi)
75
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './src/index'
1
+ export * from './src/index.js'
2
2
  export {}
@@ -1,20 +1,83 @@
1
1
  import { default as React } from 'react';
2
- import { WaveframeTheme } from '../types';
2
+ import { WaveframeTheme } from '../types.js';
3
+ import { WaveframeEngine } from '../core/WaveframeEngine.js';
4
+ /**
5
+ * Props for the WaveframePlayer component
6
+ */
3
7
  export interface WaveframePlayerProps {
4
- audioUrl: string;
8
+ /**
9
+ * The audio source to play. Can be a URL string or a Blob/File object.
10
+ */
11
+ media?: string | Blob;
12
+ /**
13
+ * Optional pre-generated peaks for the waveform (0-1 range).
14
+ * If omitted or empty, the player will automatically analyze the media.
15
+ */
5
16
  peaks?: number[];
6
- artworkUrl?: string;
17
+ /**
18
+ * The artwork source to display. Can be a URL string or a Blob/File object.
19
+ */
20
+ artwork?: string | Blob;
21
+ /**
22
+ * The title of the track
23
+ */
7
24
  title?: string;
25
+ /**
26
+ * The artist of the track
27
+ */
8
28
  artist?: string;
29
+ /**
30
+ * The base color of the waveform bars
31
+ * @default "#e5e7eb" (light) or "#374151" (dark)
32
+ */
9
33
  waveColor?: string;
34
+ /**
35
+ * The color of the played progress part of the waveform
36
+ * @default theme.primary or "#3b82f6"
37
+ */
10
38
  progressColor?: string;
39
+ /**
40
+ * The height of the waveform in pixels
41
+ * @default 80
42
+ */
11
43
  height?: number;
44
+ /**
45
+ * Additional CSS classes for the container
46
+ */
12
47
  className?: string;
48
+ /**
49
+ * Inline styles for the container
50
+ */
13
51
  style?: React.CSSProperties;
52
+ /**
53
+ * The number of bars to render. Use 'auto' to fit the container width.
54
+ * @default "auto"
55
+ */
14
56
  resolution?: number | 'auto';
57
+ /**
58
+ * The width of each bar in pixels (if resolution is 'auto')
59
+ * @default 2
60
+ */
15
61
  barWidth?: number;
62
+ /**
63
+ * The gap between bars in pixels (if resolution is 'auto')
64
+ * @default 1
65
+ */
16
66
  barGap?: number;
67
+ /**
68
+ * Custom theme configuration
69
+ */
17
70
  theme?: WaveframeTheme;
18
- autoAnalyze?: boolean;
71
+ /**
72
+ * Optional WaveframeEngine instance for external control.
73
+ * If provided, the player will sync with this engine instead of creating its own.
74
+ */
75
+ engine?: WaveframeEngine;
19
76
  }
77
+ /**
78
+ * The standard "all-in-one" Waveframe player component.
79
+ *
80
+ * This component features a SoundCloud-inspired layout with a prominent
81
+ * play/pause button positioned next to the track metadata.
82
+ */
20
83
  export declare const WaveframePlayer: React.FC<WaveframePlayerProps>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * A specialized class for decoding audio data and generating waveform peaks.
3
+ *
4
+ * It leverages the Web Audio API (`AudioContext`) to process audio buffers
5
+ * and extract amplitude data for visualization.
6
+ */
7
+ export declare class PeakAnalyzer {
8
+ private audioCtx;
9
+ /**
10
+ * Initializes the analyzer. AudioContext creation is deferred to the first use
11
+ * to comply with browser autoplay and resource management policies.
12
+ */
13
+ constructor();
14
+ /**
15
+ * Lazily creates or returns the existing AudioContext.
16
+ */
17
+ private getContext;
18
+ /**
19
+ * Processes media (URL or Blob) and generates a set of normalized peaks.
20
+ *
21
+ * @param media The URL string or Blob object to analyze.
22
+ * @param samples The number of peaks (bars) to generate. Defaults to 512.
23
+ * @returns A promise resolving to an array of normalized peak values (0 to 1).
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const analyzer = new PeakAnalyzer();
28
+ *
29
+ * // Analyze from URL
30
+ * const peaksFromUrl = await analyzer.generatePeaks('https://example.com/audio.mp3');
31
+ *
32
+ * // Analyze from Blob
33
+ * const peaksFromBlob = await analyzer.generatePeaks(myAudioBlob);
34
+ * ```
35
+ */
36
+ generatePeaks(media: string | Blob, samples?: number): Promise<number[]>;
37
+ /**
38
+ * Closes the AudioContext and releases system audio resources.
39
+ */
40
+ dispose(): void;
41
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Represents the low-level playback state of the audio element.
3
+ */
4
+ export type PlayerState = {
5
+ /** Whether the audio is currently playing */
6
+ isPlaying: boolean;
7
+ /** The current playback time in seconds */
8
+ currentTime: number;
9
+ /** The total duration of the track in seconds */
10
+ duration: number;
11
+ /** The current volume level (0 to 1) */
12
+ volume: number;
13
+ /** Whether the audio is currently muted */
14
+ muted: boolean;
15
+ };
16
+ /**
17
+ * A callback function that receives the latest PlayerState.
18
+ */
19
+ export type PlayerListener = (state: PlayerState) => void;
20
+ /**
21
+ * The internal core class responsible for managing the HTMLAudioElement.
22
+ *
23
+ * It handles raw playback logic, volume control, and synchronizes the
24
+ * internal `PlayerState` with DOM events from the underlying `Audio` instance.
25
+ */
26
+ export declare class PlayerCore {
27
+ private audio;
28
+ private listeners;
29
+ private _state;
30
+ /**
31
+ * Initializes a new PlayerCore instance and sets up event listeners on a new Audio object.
32
+ */
33
+ constructor();
34
+ /**
35
+ * Subscribes to various HTMLMediaElement events to keep the internal state in sync.
36
+ */
37
+ private initListeners;
38
+ /**
39
+ * Updates the internal state and notifies subscribers.
40
+ */
41
+ private updateState;
42
+ /**
43
+ * Triggers all registered listener callbacks.
44
+ */
45
+ private notify;
46
+ /**
47
+ * Registers a listener for state updates.
48
+ * @param listener The callback function.
49
+ * @returns An unsubscribe function.
50
+ */
51
+ subscribe(listener: PlayerListener): () => boolean;
52
+ /**
53
+ * Returns the current playback state.
54
+ */
55
+ get state(): PlayerState;
56
+ /**
57
+ * Updates the source URL of the underlying audio element.
58
+ * @param url The audio source URL.
59
+ */
60
+ setSource(url: string): void;
61
+ /**
62
+ * Starts playback. Returns a promise that resolves when playback begins.
63
+ */
64
+ play(): Promise<void>;
65
+ /**
66
+ * Pauses playback.
67
+ */
68
+ pause(): void;
69
+ /**
70
+ * Toggles between play and pause states.
71
+ */
72
+ togglePlay(): void;
73
+ /**
74
+ * Seeks to a specific time.
75
+ * @param time Time in seconds.
76
+ */
77
+ seek(time: number): void;
78
+ /**
79
+ * Sets the volume level.
80
+ * @param volume Level from 0 to 1.
81
+ */
82
+ setVolume(volume: number): void;
83
+ /**
84
+ * Mutes or unmutes the audio element.
85
+ * @param muted Mute status.
86
+ */
87
+ setMuted(muted: boolean): void;
88
+ /**
89
+ * Cleans up the audio element and removes all listeners.
90
+ */
91
+ dispose(): void;
92
+ }
@@ -0,0 +1,122 @@
1
+ import { PlayerState } from './PlayerCore.js';
2
+ /**
3
+ * Represents the complete state of the Waveframe engine, combining playback and analysis.
4
+ */
5
+ export type EngineState = PlayerState & {
6
+ /** The current set of generated or provided waveform peaks (0-1 range) */
7
+ peaks: number[];
8
+ /** Whether an audio analysis process is currently in progress */
9
+ isAnalyzing: boolean;
10
+ /** Any error message encountered during playback or analysis */
11
+ error: string | null;
12
+ };
13
+ /**
14
+ * A callback function that receives the latest EngineState.
15
+ */
16
+ export type EngineListener = (state: EngineState) => void;
17
+ /**
18
+ * The orchestrator class for Waveframe.
19
+ *
20
+ * It manages the lifecycle of audio playback and waveform analysis, providing a unified
21
+ * store-like interface that can be easily consumed by React or other frameworks.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const engine = new WaveframeEngine();
26
+ *
27
+ * // Load from URL (automatic analysis if peaks omitted)
28
+ * engine.load('https://example.com/audio.mp3');
29
+ *
30
+ * // Load from Blob with pre-computed peaks
31
+ * engine.load(myBlob, [0.1, 0.5, 0.8]);
32
+ *
33
+ * // Subscription
34
+ * const unsubscribe = engine.subscribe((state) => {
35
+ * console.log('Current time:', state.currentTime);
36
+ * });
37
+ * ```
38
+ */
39
+ export declare class WaveframeEngine {
40
+ private player;
41
+ private analyzer;
42
+ private listeners;
43
+ private _state;
44
+ private _media;
45
+ private _objectUrl;
46
+ /**
47
+ * Creates a new instance of the WaveframeEngine.
48
+ * Initializes internal PlayerCore and PeakAnalyzer.
49
+ */
50
+ constructor();
51
+ /**
52
+ * Internal method to update the state and notify all subscribers.
53
+ */
54
+ private updateState;
55
+ /**
56
+ * Notifies all registered listeners of a state change.
57
+ */
58
+ private notify;
59
+ /**
60
+ * Registers a listener to be called whenever the engine state changes.
61
+ * @param listener The callback function.
62
+ * @returns An unsubscribe function.
63
+ */
64
+ subscribe(listener: EngineListener): () => boolean;
65
+ /**
66
+ * Returns a snapshot of the current engine state.
67
+ * Useful for `useSyncExternalStore`.
68
+ */
69
+ getSnapshot(): EngineState;
70
+ /**
71
+ * Revokes any existing Object URLs to prevent memory leaks.
72
+ */
73
+ private revokeOldSource;
74
+ /**
75
+ * Loads media (URL or Blob) into the player.
76
+ *
77
+ * If a string is passed, it's treated as a URL and used directly for playback.
78
+ * If a Blob is passed, an Object URL is created for playback.
79
+ *
80
+ * If `peaks` are not provided, it automatically triggers an analysis.
81
+ *
82
+ * @param media The audio source (URL string or Blob/File object).
83
+ * @param peaks Optional pre-generated peaks for the waveform.
84
+ */
85
+ load(media: string | Blob, peaks?: number[]): void;
86
+ /**
87
+ * Analyzes the current media to generate waveform peaks.
88
+ * @param samples The number of peaks to generate. Defaults to 512.
89
+ */
90
+ analyze(samples?: number): Promise<void>;
91
+ /**
92
+ * Toggles playback between playing and paused.
93
+ */
94
+ togglePlay(): void;
95
+ /**
96
+ * Starts audio playback.
97
+ */
98
+ play(): void;
99
+ /**
100
+ * Pauses audio playback.
101
+ */
102
+ pause(): void;
103
+ /**
104
+ * Seeks to a specific position in the track.
105
+ * @param percentage The seek position as a decimal (0 to 1).
106
+ */
107
+ seek(percentage: number): void;
108
+ /**
109
+ * Sets the playback volume.
110
+ * @param volume The volume level (0 to 1).
111
+ */
112
+ setVolume(volume: number): void;
113
+ /**
114
+ * Mutes or unmutes the audio.
115
+ * @param muted Whether the audio should be muted.
116
+ */
117
+ setMuted(muted: boolean): void;
118
+ /**
119
+ * Disposes of the engine, pausing playback and clearing all listeners and resources.
120
+ */
121
+ dispose(): void;
122
+ }
@@ -0,0 +1,51 @@
1
+ import { WaveframeEngine, EngineState } from '../core/WaveframeEngine.js';
2
+ /**
3
+ * Configuration options for the `useWaveframe` hook.
4
+ */
5
+ export interface UseWaveframeOptions {
6
+ /** Optional pre-computed peaks to skip automatic analysis */
7
+ peaks?: number[];
8
+ /** Optional external engine instance for shared playback across components */
9
+ engine?: WaveframeEngine;
10
+ }
11
+ /**
12
+ * A headless hook that provides full control over the Waveframe engine.
13
+ *
14
+ * It manages the engine's lifecycle, loads the provided media, and returns
15
+ * the current state along with playback controls.
16
+ *
17
+ * @param media The audio source (URL string or Blob/File object).
18
+ * @param options Additional configuration and an optional external engine.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const { state, togglePlay, seek } = useWaveframe('https://example.com/audio.mp3');
23
+ *
24
+ * return (
25
+ * <div>
26
+ * <button onClick={togglePlay}>{state.isPlaying ? 'Pause' : 'Play'}</button>
27
+ * <div onClick={(e) => seek(0.5)}>Seek to Middle</div>
28
+ * </div>
29
+ * );
30
+ * ```
31
+ */
32
+ export declare const useWaveframe: (media: string | Blob | undefined, options?: UseWaveframeOptions) => {
33
+ /** The current reactive state of the engine */
34
+ state: EngineState;
35
+ /** The raw WaveframeEngine instance for advanced usage */
36
+ engine: WaveframeEngine;
37
+ /** Toggles playback between playing and paused */
38
+ togglePlay: () => void;
39
+ /** Starts audio playback */
40
+ play: () => void;
41
+ /** Pauses audio playback */
42
+ pause: () => void;
43
+ /** Seeks to a specific percentage (0-1) */
44
+ seek: (percentage: number) => void;
45
+ /** Sets the playback volume (0-1) */
46
+ setVolume: (v: number) => void;
47
+ /** Mutes or unmutes the audio */
48
+ setMuted: (m: boolean) => void;
49
+ /** Manually triggers a re-analysis of the current media */
50
+ analyze: (samples?: number) => Promise<void>;
51
+ };
@@ -0,0 +1,27 @@
1
+ import { WaveframeEngine, EngineState } from '../core/WaveframeEngine.js';
2
+ /**
3
+ * A React hook that synchronizes a WaveframeEngine's state with a React component.
4
+ *
5
+ * It uses `useSyncExternalStore` for high-performance updates, ensuring that
6
+ * the component only re-renders when the engine's state snapshot actually changes.
7
+ *
8
+ * @param engine The WaveframeEngine instance to subscribe to.
9
+ * @returns The current EngineState (isPlaying, currentTime, peaks, etc.).
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const MyPlayer = ({ engine }: { engine: WaveframeEngine }) => {
14
+ * const { isPlaying, currentTime, duration } = useWaveframeStore(engine);
15
+ *
16
+ * return (
17
+ * <div>
18
+ * <button onClick={() => engine.togglePlay()}>
19
+ * {isPlaying ? 'Pause' : 'Play'}
20
+ * </button>
21
+ * <p>{currentTime.toFixed(2)} / {duration.toFixed(2)}</p>
22
+ * </div>
23
+ * );
24
+ * };
25
+ * ```
26
+ */
27
+ export declare const useWaveframeStore: (engine: WaveframeEngine) => EngineState;
@@ -1 +1,7 @@
1
- export * from './components/WaveframePlayer';
1
+ export * from './components/WaveframePlayer.js';
2
+ export * from './core/WaveframeEngine.js';
3
+ export * from './core/PlayerCore.js';
4
+ export * from './core/PeakAnalyzer.js';
5
+ export * from './hooks/useWaveframeStore.js';
6
+ export * from './types.js';
7
+ export * from './utils.js';
@@ -1,10 +1,20 @@
1
1
  import { default as React } from 'react';
2
+ /**
3
+ * Props for the ArtworkOverlay component.
4
+ */
2
5
  interface ArtworkOverlayProps {
6
+ /** The URL or Object URL of the artwork image */
3
7
  artworkUrl?: string;
8
+ /** The title of the track (used for alt text) */
4
9
  title?: string;
5
- isPlaying: boolean;
6
- onToggle: (e: React.MouseEvent) => void;
10
+ /** Whether the artwork is currently being processed or the audio is analyzing */
7
11
  isLoading?: boolean;
8
12
  }
13
+ /**
14
+ * A purely visual component for displaying track artwork.
15
+ *
16
+ * It handles loading states with a blur effect and provides a consistent
17
+ * container for the track image.
18
+ */
9
19
  export declare const ArtworkOverlay: React.FC<ArtworkOverlayProps>;
10
20
  export {};
@@ -1,11 +1,16 @@
1
1
  import { default as React } from 'react';
2
- import { WaveframeTheme, TrackInfo, WaveformConfig } from '../types';
2
+ import { WaveframeTheme, TrackInfo, WaveformConfig } from '../types.js';
3
3
  interface SettingsPanelProps {
4
4
  theme: WaveframeTheme;
5
5
  trackInfo: TrackInfo;
6
6
  config: WaveformConfig;
7
7
  scale: number;
8
- isAnalyzing: boolean;
8
+ engineState: {
9
+ isPlaying: boolean;
10
+ volume: number;
11
+ muted: boolean;
12
+ isAnalyzing: boolean;
13
+ };
9
14
  onAnalyze: () => void;
10
15
  onThemeChange: (theme: Partial<WaveframeTheme>) => void;
11
16
  onTrackChange: (track: Partial<TrackInfo>) => void;
@@ -13,6 +18,12 @@ interface SettingsPanelProps {
13
18
  onScaleChange: (scale: number) => void;
14
19
  onTogglePreset: (type: 'light' | 'dark') => void;
15
20
  onClearPeaks: () => void;
21
+ onFileUpload: (e: React.ChangeEvent<HTMLInputElement>) => void;
22
+ onArtworkUpload: (e: React.ChangeEvent<HTMLInputElement>) => void;
23
+ onReset: () => void;
24
+ onTogglePlay: () => void;
25
+ onSetVolume: (v: number) => void;
26
+ onSetMuted: (m: boolean) => void;
16
27
  }
17
28
  export declare const SettingsPanel: React.FC<SettingsPanelProps>;
18
29
  export {};