waveframe 0.3.0 → 0.4.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/README.md +4 -3
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +120 -35
- package/dist/index.d.ts +120 -35
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React$1 from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Defines the core color palette for the Waveframe component.
|
|
@@ -82,6 +82,8 @@ interface WaveformConfig {
|
|
|
82
82
|
type PlayerState = {
|
|
83
83
|
/** Whether the audio is currently playing */
|
|
84
84
|
isPlaying: boolean;
|
|
85
|
+
/** Whether the audio is stalled (waiting for data) */
|
|
86
|
+
isStalled: boolean;
|
|
85
87
|
/** The current playback time in seconds */
|
|
86
88
|
currentTime: number;
|
|
87
89
|
/** The total duration of the track in seconds */
|
|
@@ -90,6 +92,8 @@ type PlayerState = {
|
|
|
90
92
|
volume: number;
|
|
91
93
|
/** Whether the audio is currently muted */
|
|
92
94
|
muted: boolean;
|
|
95
|
+
/** Any error reported by the audio element */
|
|
96
|
+
error: string | null;
|
|
93
97
|
};
|
|
94
98
|
/**
|
|
95
99
|
* A callback function that receives the latest PlayerState.
|
|
@@ -147,7 +151,7 @@ declare class PlayerCore {
|
|
|
147
151
|
/**
|
|
148
152
|
* Toggles between play and pause states.
|
|
149
153
|
*/
|
|
150
|
-
togglePlay(): void
|
|
154
|
+
togglePlay(): Promise<void>;
|
|
151
155
|
/**
|
|
152
156
|
* Seeks to a specific time.
|
|
153
157
|
* @param time Time in seconds.
|
|
@@ -170,7 +174,7 @@ declare class PlayerCore {
|
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
/**
|
|
173
|
-
* Represents the complete state of the Waveframe
|
|
177
|
+
* Represents the complete state of the Waveframe controller, combining playback and analysis.
|
|
174
178
|
*/
|
|
175
179
|
type EngineState = PlayerState & {
|
|
176
180
|
/** The current set of generated or provided waveform peaks (0-1 range) */
|
|
@@ -192,21 +196,21 @@ type EngineListener = (state: EngineState) => void;
|
|
|
192
196
|
*
|
|
193
197
|
* @example
|
|
194
198
|
* ```typescript
|
|
195
|
-
* const
|
|
199
|
+
* const controller = new WaveframeController();
|
|
196
200
|
*
|
|
197
201
|
* // Load from URL (automatic analysis if peaks omitted)
|
|
198
|
-
*
|
|
202
|
+
* controller.load('https://example.com/audio.mp3');
|
|
199
203
|
*
|
|
200
204
|
* // Load from Blob with pre-computed peaks
|
|
201
|
-
*
|
|
205
|
+
* controller.load(myBlob, [0.1, 0.5, 0.8]);
|
|
202
206
|
*
|
|
203
207
|
* // Subscription
|
|
204
|
-
* const unsubscribe =
|
|
208
|
+
* const unsubscribe = controller.subscribe((state) => {
|
|
205
209
|
* console.log('Current time:', state.currentTime);
|
|
206
210
|
* });
|
|
207
211
|
* ```
|
|
208
212
|
*/
|
|
209
|
-
declare class
|
|
213
|
+
declare class WaveframeController {
|
|
210
214
|
private player;
|
|
211
215
|
private analyzer;
|
|
212
216
|
private listeners;
|
|
@@ -214,7 +218,7 @@ declare class WaveframeEngine {
|
|
|
214
218
|
private _media;
|
|
215
219
|
private _objectUrl;
|
|
216
220
|
/**
|
|
217
|
-
* Creates a new instance of the
|
|
221
|
+
* Creates a new instance of the WaveframeController.
|
|
218
222
|
* Initializes internal PlayerCore and PeakAnalyzer.
|
|
219
223
|
*/
|
|
220
224
|
constructor();
|
|
@@ -237,6 +241,14 @@ declare class WaveframeEngine {
|
|
|
237
241
|
* Useful for `useSyncExternalStore`.
|
|
238
242
|
*/
|
|
239
243
|
getSnapshot(): EngineState;
|
|
244
|
+
/**
|
|
245
|
+
* Returns the current engine state.
|
|
246
|
+
*/
|
|
247
|
+
get state(): EngineState;
|
|
248
|
+
/**
|
|
249
|
+
* Resets the audio player and analyzer, clearing state and current media.
|
|
250
|
+
*/
|
|
251
|
+
reset(): void;
|
|
240
252
|
/**
|
|
241
253
|
* Revokes any existing Object URLs to prevent memory leaks.
|
|
242
254
|
*/
|
|
@@ -261,11 +273,11 @@ declare class WaveframeEngine {
|
|
|
261
273
|
/**
|
|
262
274
|
* Toggles playback between playing and paused.
|
|
263
275
|
*/
|
|
264
|
-
togglePlay(): void
|
|
276
|
+
togglePlay(): Promise<void>;
|
|
265
277
|
/**
|
|
266
278
|
* Starts audio playback.
|
|
267
279
|
*/
|
|
268
|
-
play(): void
|
|
280
|
+
play(): Promise<void>;
|
|
269
281
|
/**
|
|
270
282
|
* Pauses audio playback.
|
|
271
283
|
*/
|
|
@@ -286,7 +298,7 @@ declare class WaveframeEngine {
|
|
|
286
298
|
*/
|
|
287
299
|
setMuted(muted: boolean): void;
|
|
288
300
|
/**
|
|
289
|
-
* Disposes of the
|
|
301
|
+
* Disposes of the controller, pausing playback and clearing all listeners and resources.
|
|
290
302
|
*/
|
|
291
303
|
dispose(): void;
|
|
292
304
|
}
|
|
@@ -338,7 +350,7 @@ interface WaveframePlayerProps {
|
|
|
338
350
|
/**
|
|
339
351
|
* Inline styles for the container
|
|
340
352
|
*/
|
|
341
|
-
style?: React.CSSProperties;
|
|
353
|
+
style?: React$1.CSSProperties;
|
|
342
354
|
/**
|
|
343
355
|
* The number of bars to render. Use 'auto' to fit the container width.
|
|
344
356
|
* @default "auto"
|
|
@@ -359,10 +371,10 @@ interface WaveframePlayerProps {
|
|
|
359
371
|
*/
|
|
360
372
|
theme?: WaveframeTheme;
|
|
361
373
|
/**
|
|
362
|
-
* Optional
|
|
363
|
-
* If provided, the player will sync with this
|
|
374
|
+
* Optional WaveframeController instance for external control.
|
|
375
|
+
* If provided, the player will sync with this controller instead of creating its own.
|
|
364
376
|
*/
|
|
365
|
-
|
|
377
|
+
controller?: WaveframeController;
|
|
366
378
|
}
|
|
367
379
|
/**
|
|
368
380
|
* The standard "all-in-one" Waveframe player component.
|
|
@@ -370,7 +382,41 @@ interface WaveframePlayerProps {
|
|
|
370
382
|
* This component features a SoundCloud-inspired layout with a prominent
|
|
371
383
|
* play/pause button positioned next to the track metadata.
|
|
372
384
|
*/
|
|
373
|
-
declare const WaveframePlayer: React.FC<WaveframePlayerProps>;
|
|
385
|
+
declare const WaveframePlayer: React$1.FC<WaveframePlayerProps>;
|
|
386
|
+
|
|
387
|
+
interface WaveformProps {
|
|
388
|
+
/** The WaveframeController instance managing audio state */
|
|
389
|
+
controller: WaveframeController;
|
|
390
|
+
/** Optional pre-computed peaks. If omitted, peaks from the controller are used. */
|
|
391
|
+
peaks?: number[];
|
|
392
|
+
/** Color of the background waveform bars */
|
|
393
|
+
waveColor?: string;
|
|
394
|
+
/** Color of the progress (played) waveform bars */
|
|
395
|
+
progressColor?: string;
|
|
396
|
+
/** Total height of the waveform in pixels */
|
|
397
|
+
height?: number;
|
|
398
|
+
/**
|
|
399
|
+
* Resolution of the waveform.
|
|
400
|
+
* 'auto' matches the container width (1 bar per pixel).
|
|
401
|
+
* A number sets a fixed number of bars.
|
|
402
|
+
*/
|
|
403
|
+
resolution?: number | 'auto';
|
|
404
|
+
/** Width of each bar in pixels (if resolution is 'auto') */
|
|
405
|
+
barWidth?: number;
|
|
406
|
+
/** Gap between bars in pixels (if resolution is 'auto') */
|
|
407
|
+
barGap?: number;
|
|
408
|
+
/** Overall amplitude multiplier (default 1.0) */
|
|
409
|
+
amplitude?: number;
|
|
410
|
+
/** Non-linear power scale to increase detail (e.g. 0.7-0.9 reduces 'sausage' look). Default 1.0 (linear). */
|
|
411
|
+
powerScale?: number;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* A "smart" waveform component that visualizes audio progress and allows seeking.
|
|
415
|
+
*
|
|
416
|
+
* It subscribes directly to the provided WaveframeController for high-frequency
|
|
417
|
+
* progress updates, ensuring the parent component remains immune to re-renders.
|
|
418
|
+
*/
|
|
419
|
+
declare const Waveform: React$1.FC<WaveformProps>;
|
|
374
420
|
|
|
375
421
|
/**
|
|
376
422
|
* A specialized class for decoding audio data and generating waveform peaks.
|
|
@@ -415,31 +461,70 @@ declare class PeakAnalyzer {
|
|
|
415
461
|
}
|
|
416
462
|
|
|
417
463
|
/**
|
|
418
|
-
*
|
|
464
|
+
* Configuration options for the `useWaveframe` hook.
|
|
465
|
+
*/
|
|
466
|
+
interface UseWaveframeOptions {
|
|
467
|
+
/** Optional pre-computed peaks to skip automatic analysis */
|
|
468
|
+
peaks?: number[];
|
|
469
|
+
/** Optional external controller instance for shared playback across components */
|
|
470
|
+
controller?: WaveframeController;
|
|
471
|
+
/** Whether to automatically start playback when media is loaded */
|
|
472
|
+
autoPlay?: boolean;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* A headless hook that manages the lifecycle of a WaveframeController.
|
|
419
476
|
*
|
|
420
|
-
* It
|
|
421
|
-
*
|
|
477
|
+
* It returns a stable controller instance that can be passed to components
|
|
478
|
+
* like <Waveform /> or used for custom playback logic.
|
|
422
479
|
*
|
|
423
|
-
* @param
|
|
424
|
-
* @
|
|
480
|
+
* @param media The audio source (URL string or Blob/File object).
|
|
481
|
+
* @param options Additional configuration and an optional external controller.
|
|
425
482
|
*
|
|
426
483
|
* @example
|
|
427
484
|
* ```tsx
|
|
428
|
-
* const
|
|
429
|
-
* const { isPlaying, currentTime, duration } = useWaveframeStore(engine);
|
|
485
|
+
* const { controller, state } = useWaveframe('https://example.com/audio.mp3');
|
|
430
486
|
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
* };
|
|
487
|
+
* return (
|
|
488
|
+
* <div>
|
|
489
|
+
* <Waveform controller={controller} />
|
|
490
|
+
* <button onClick={() => controller.togglePlay()}>
|
|
491
|
+
* {state.isPlaying ? 'Pause' : 'Play'}
|
|
492
|
+
* </button>
|
|
493
|
+
* </div>
|
|
494
|
+
* );
|
|
440
495
|
* ```
|
|
441
496
|
*/
|
|
442
|
-
declare const
|
|
497
|
+
declare const useWaveframe: (media: string | Blob | undefined, options?: UseWaveframeOptions) => {
|
|
498
|
+
/** The stable WaveframeController instance */
|
|
499
|
+
controller: WaveframeController;
|
|
500
|
+
/** The reactive engine state */
|
|
501
|
+
state: EngineState;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* A React hook that synchronizes a WaveframeController's state with a React component.
|
|
506
|
+
*
|
|
507
|
+
* It supports an optional selector function to subscribe to specific parts of the state,
|
|
508
|
+
* preventing unnecessary re-renders when unrelated state changes.
|
|
509
|
+
*
|
|
510
|
+
* @param controller The WaveframeController instance to subscribe to.
|
|
511
|
+
* @param selector An optional function to select a specific slice of the state.
|
|
512
|
+
* @returns The selected state or the full EngineState if no selector is provided.
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```tsx
|
|
516
|
+
* // Subscribe only to isPlaying
|
|
517
|
+
* const isPlaying = useWaveframeStore(controller, state => state.isPlaying);
|
|
518
|
+
*
|
|
519
|
+
* // Subscribe to the full state
|
|
520
|
+
* const state = useWaveframeStore(controller);
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
declare function useWaveframeStore<T = EngineState>(controller: WaveframeController, selector?: (state: EngineState) => T): T;
|
|
524
|
+
|
|
525
|
+
declare const useResampledPeaks: (peaks: number[], targetCount: number) => number[];
|
|
526
|
+
|
|
527
|
+
declare const useResizeObserver: (ref: React.RefObject<HTMLElement | null>) => number;
|
|
443
528
|
|
|
444
529
|
/**
|
|
445
530
|
* Loads audio from a URL, decodes it, and generates a specific number of peaks (samples).
|
|
@@ -488,4 +573,4 @@ declare const resamplePeaks: (peaks: number[], targetCount: number) => number[];
|
|
|
488
573
|
*/
|
|
489
574
|
declare const highlightCode: (code: string) => string[];
|
|
490
575
|
|
|
491
|
-
export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type WaveformConfig,
|
|
576
|
+
export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type UseWaveframeOptions, Waveform, type WaveformConfig, WaveframeController, WaveframePlayer, type WaveframePlayerProps, type WaveframeTheme, formatTime, generatePeaks, highlightCode, loadAudioToMemory, resamplePeaks, revokeAudioMemory, useResampledPeaks, useResizeObserver, useWaveframe, useWaveframeStore };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {memo,useRef,useEffect,useState,
|
|
3
|
-
`).map(t=>{let
|
|
2
|
+
import {memo,useRef,useMemo,useEffect,useState,useSyncExternalStore}from'react';import {jsxs,jsx}from'react/jsx-runtime';var M=class{audio;listeners=new Set;_state;constructor(){this.audio=new Audio,this._state={isPlaying:false,isStalled:false,currentTime:0,duration:0,volume:1,muted:false,error:null},this.initListeners();}initListeners(){this.audio.addEventListener("play",()=>this.updateState({isPlaying:true,error:null})),this.audio.addEventListener("pause",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("waiting",()=>this.updateState({isStalled:true})),this.audio.addEventListener("playing",()=>this.updateState({isStalled:false})),this.audio.addEventListener("canplay",()=>this.updateState({isStalled:false})),this.audio.addEventListener("timeupdate",()=>this.updateState({currentTime:this.audio.currentTime})),this.audio.addEventListener("durationchange",()=>this.updateState({duration:this.audio.duration})),this.audio.addEventListener("volumechange",()=>this.updateState({volume:this.audio.volume,muted:this.audio.muted})),this.audio.addEventListener("ended",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("error",()=>{let t=this.audio.error,r="Unknown audio error";if(t)switch(t.code){case t.MEDIA_ERR_ABORTED:r="Playback aborted";break;case t.MEDIA_ERR_NETWORK:r="Network error";break;case t.MEDIA_ERR_DECODE:r="Audio decoding failed";break;case t.MEDIA_ERR_SRC_NOT_SUPPORTED:r="Audio format not supported";break}this.updateState({isPlaying:false,error:r});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}get state(){return this._state}setSource(t){this.audio.src=t,this.audio.load(),this.updateState({error:null,currentTime:0,duration:0});}async play(){try{await this.audio.play();}catch(t){let r=t instanceof Error?t.message:"Playback failed";throw this.updateState({isPlaying:false,error:r}),t}}pause(){this.audio.pause();}async togglePlay(){if(this._state.isPlaying)this.pause();else try{await this.play();}catch{}}seek(t){this.audio.currentTime=t;}setVolume(t){this.audio.volume=t;}setMuted(t){this.audio.muted=t;}dispose(){this.pause(),this.audio.src="",this.listeners.clear();}};var z=class{audioCtx=null;constructor(){}getContext(){return this.audioCtx||(this.audioCtx=new(window.AudioContext||window.webkitAudioContext)),this.audioCtx}async generatePeaks(t,r=512){try{let a;if(typeof t=="string"){let g=await fetch(t);if(!g.ok)throw new Error(`Failed to fetch audio: ${g.statusText}`);a=await g.arrayBuffer();}else a=await t.arrayBuffer();let o=(await this.getContext().decodeAudioData(a)).getChannelData(0),s=Math.floor(o.length/r),l=[];for(let g=0;g<r;g++){let y=0,m=g*s,c=m+s;for(let S=m;S<c;S++){let u=Math.abs(o[S]);u>y&&(y=u);}l.push(y);}let x=Math.max(...l);return l.map(g=>g/(x||1))}catch(a){throw console.error("PeakAnalyzer Error:",a),a}}dispose(){this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null);}};var T=class{player;analyzer;listeners=new Set;_state;_media=null;_objectUrl=null;constructor(){this.player=new M,this.analyzer=new z,this._state={...this.player.state,peaks:[],isAnalyzing:false,error:null},this.player.subscribe(t=>{this.updateState({...t});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(){return this._state}get state(){return this._state}reset(){this.revokeOldSource(),this._media=null,this.player.dispose(),this.analyzer.dispose(),this.player=new M,this.analyzer=new z,this.player.subscribe(t=>{this.updateState({...t});}),this.updateState({...this.player.state,peaks:[],isAnalyzing:false,error:null});}revokeOldSource(){this._objectUrl&&(URL.revokeObjectURL(this._objectUrl),this._objectUrl=null);}load(t,r){if(this._media===t){r&&r.length!==this._state.peaks.length&&this.updateState({peaks:r});return}this.revokeOldSource(),this._media=t;let a;typeof t=="string"?a=t:(this._objectUrl=URL.createObjectURL(t),a=this._objectUrl),this.player.setSource(a);let n=r&&r.length>0;this.updateState({peaks:r||[],isAnalyzing:false,error:null}),n||this.analyze();}async analyze(t=512){if(!this._media){this.updateState({error:"No media loaded to analyze"});return}this.updateState({isAnalyzing:true,error:null});try{let r=await this.analyzer.generatePeaks(this._media,t);this.updateState({peaks:r,isAnalyzing:!1});}catch(r){this.updateState({isAnalyzing:false,error:r instanceof Error?r.message:"Analysis failed"});}}async togglePlay(){return await this.player.togglePlay()}async play(){return await this.player.play()}pause(){this.player.pause();}seek(t){let{duration:r}=this._state;r&&this.player.seek(t*r);}setVolume(t){this.player.setVolume(t);}setMuted(t){this.player.setMuted(t);}dispose(){this.revokeOldSource(),this.player.dispose(),this.analyzer.dispose(),this.listeners.clear();}};function f(e,t){return useSyncExternalStore(r=>e.subscribe(r),()=>{let r=e.getSnapshot();return t?t(r):r})}var J=(e,t={})=>{let{peaks:r,controller:a,autoPlay:n}=t,i=useRef(false),o=useMemo(()=>a||new T,[a]),s=a||o,l=f(s);return useEffect(()=>(i.current=true,e&&(s.load(e,r),n&&s.play().catch(()=>{})),()=>{i.current=false;}),[s,e,r,n]),useEffect(()=>()=>{a||o.dispose();},[o,a]),{controller:s,state:l}};var O=memo(({artworkUrl:e,title:t,isLoading:r})=>jsxs("div",{className:"relative flex-shrink-0 w-full md:w-auto md:h-full aspect-square overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork",children:[jsx("div",{className:`w-full h-full transition-all duration-700 ${r?"blur-md scale-110":""}`,children:e?jsx("img",{src:e,alt:t,className:"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110"}):jsx("div",{className:"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center",children:jsx("svg",{className:"w-16 h-16 text-white opacity-50",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"})})})}),r&&jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]",children:jsx("div",{className:"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin"})})]}));O.displayName="ArtworkOverlay";var Q=e=>{let[t,r]=useState(0);return useEffect(()=>{if(!e.current)return;let a=new ResizeObserver(n=>{for(let i of n)r(i.contentRect.width);});return a.observe(e.current),()=>a.disconnect()},[e]),t};var It=async(e,t=512)=>{let r=new z;try{return await r.generatePeaks(e,t)}finally{r.dispose();}},Ht=async e=>{let r=await(await fetch(e)).blob();return URL.createObjectURL(r)},Vt=e=>{e&&e.startsWith("blob:")&&URL.revokeObjectURL(e);};var B=e=>{if(isNaN(e))return "0:00";let t=Math.floor(e/60),r=Math.floor(e%60);return `${t}:${r.toString().padStart(2,"0")}`},j=(e,t)=>{if(e.length===0)return [];if(e.length===t)return e;let r=new Array(t),a=e.length/t;if(a>1)for(let n=0;n<t;n++){let i=0,o=Math.floor(n*a),s=Math.floor((n+1)*a);for(let l=o;l<s;l++)e[l]>i&&(i=e[l]);r[n]=i;}else for(let n=0;n<t;n++){let i=n*a,o=Math.floor(i),s=Math.min(o+1,e.length-1),l=i-o;r[n]=e[o]+(e[s]-e[o])*l;}return r},qt=e=>e.split(`
|
|
3
|
+
`).map(t=>{let r=t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),a={},n=0,i=(o,s)=>{let l=`__TOKEN_${n++}__`;return a[l]=`<span class="${s}">${o}</span>`,l};return r=r.replace(/("(?:[^"\\]|\\.)*")/g,o=>i(o,"text-[#ce9178]")),r=r.replace(/\b(\d+(\.\d+)?)\b/g,o=>i(o,"text-[#b5cea8]")),r=r.replace(/\b(WaveframePlayer)\b/g,o=>i(o,"text-[#4ec9b0]")),r=r.replace(/\b([a-z][a-zA-Z0-9]+)(?==)/g,o=>i(o,"text-[#9cdcfe]")),r=r.replace(/(<|>|\{|\}|\/|:|,)/g,'<span class="text-gray-500">$1</span>'),Object.entries(a).forEach(([o,s])=>{r=r.replace(o,s);}),r});var I=memo(({controller:e,peaks:t,waveColor:r="#e5e7eb",progressColor:a="#3b82f6",height:n=80,resolution:i="auto",barWidth:o=2,barGap:s=1,amplitude:l=1,powerScale:x=1})=>{let g=useRef(null),y=useRef(null),m=useRef(null),c=Q(m),S=f(e,d=>d.currentTime),u=f(e,d=>d.duration),A=f(e,d=>d.peaks),E=useMemo(()=>{let d=t||A;if(d.length===0)return [];let v=i==="auto"?Math.floor(c/(o+s)):i,p=j(d,Math.max(1,v));return (x!==1||l!==1)&&(p=p.map(h=>Math.pow(h,x)*l)),p},[t,A,c,i,o,s,l,x]);useEffect(()=>{let d=g.current,v=y.current;if(!d||!v)return;let p=d.getContext("2d"),h=v.getContext("2d");if(!p||!h)return;let C=window.devicePixelRatio||1,w=d.getBoundingClientRect(),H=w.width*C,V=w.height*C;[d,v].forEach(k=>{(k.width!==H||k.height!==V)&&(k.width=H,k.height=V);}),(()=>{if(E.length===0)return;let{width:k,height:L}=d;p.clearRect(0,0,k,L),h.clearRect(0,0,k,L);let rt=E.length,F=k/rt,R=typeof i=="number"?F*.7:o*C,et=typeof i=="number"?F*.3:s*C;p.lineCap="round",p.lineWidth=R,h.lineCap="round",h.lineWidth=R;let q=L/2;E.forEach((K,at)=>{if(K<=0)return;let N=at*(R+et)+R/2,G=Math.max(R,K*L*.8),X=q-G/2,Y=q+G/2;p.beginPath(),p.strokeStyle=r,p.moveTo(N,X),p.lineTo(N,Y),p.stroke(),h.beginPath(),h.strokeStyle=a,h.moveTo(N,X),h.lineTo(N,Y),h.stroke();});})();},[E,r,a,i,o,s,n]);let W=d=>{if(m.current&&u){let v=m.current.getBoundingClientRect(),p=d.clientX-v.left,h=Math.max(0,Math.min(1,p/v.width));e.seek(h);}},U=u?S/u*100:0;return jsxs("div",{ref:m,className:"relative w-full cursor-pointer overflow-hidden",style:{height:`${n}px`,"--wf-wave-color":r,"--wf-progress-color":a},onClick:W,children:[jsx("canvas",{ref:g,className:"absolute inset-0 w-full h-full"}),jsx("div",{className:"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none",style:{width:`${U}%`},children:jsx("canvas",{ref:y,className:"absolute h-full",style:{width:`${c}px`}})})]})});I.displayName="Waveform";var bt=memo(({media:e,peaks:t,artwork:r,title:a,artist:n,waveColor:i,progressColor:o,height:s=80,className:l="",style:x,resolution:g="auto",barWidth:y=2,barGap:m=1,theme:c,controller:S})=>{let{controller:u}=J(e,{peaks:t,controller:S}),A=f(u,w=>w.isPlaying),E=f(u,w=>w.isAnalyzing),W=f(u,w=>w.currentTime),U=f(u,w=>w.duration),[d,v]=useState(typeof r=="string"?r:void 0);useEffect(()=>{if(r instanceof Blob){let w=URL.createObjectURL(r);return v(w),()=>URL.revokeObjectURL(w)}else v(r);},[r]);let p=useMemo(()=>i||(c?c.bg==="#ffffff"?"#e5e7eb":"#374151":"#e5e7eb"),[i,c]),h=o||c?.primary||"#3b82f6",C=useMemo(()=>({...{"--wf-bg-color":c?.bg||"white","--wf-border-color":c?.border||"#f3f4f6","--wf-title-color":c?.text||"#111827","--wf-artist-color":c?.text||"#6b7280","--wf-time-color":c?.text||"#9ca3af","--wf-play-btn-bg":c?.primary||"#3b82f6","--wf-placeholder-from":c?.primary||"#fb923c","--wf-placeholder-to":c?.bg||"#ec4899"},...x}),[c,x]);return jsxs("div",{className:`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${l}`,style:C,children:[jsx("div",{className:"flex-shrink-0",children:jsx(O,{artworkUrl:d,title:a,isLoading:E})}),jsxs("div",{className:"flex-1 w-full flex flex-col min-w-0",children:[jsxs("div",{className:"flex items-center gap-4 mb-6",children:[jsx("button",{onClick:()=>u.togglePlay(),className:"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play",children:A?jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"})}):jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7 ml-1",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M8 5v14l11-7z"})})}),jsxs("div",{className:"flex-1 flex flex-col min-w-0",children:[jsxs("div",{className:"flex items-center justify-between gap-4",children:[n&&jsx("p",{className:"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1",children:n}),jsxs("div",{className:"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0",children:[B(W)," / ",B(U)]})]}),a&&jsx("h3",{className:"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight",children:a})]})]}),jsx("div",{className:"mt-auto",children:jsx(I,{controller:u,peaks:t,waveColor:p,progressColor:h,height:s,resolution:g,barWidth:y,barGap:m})})]})]})});bt.displayName="WaveframePlayer";var gr=(e,t)=>useMemo(()=>j(e,t),[e,t]);export{z as PeakAnalyzer,M as PlayerCore,I as Waveform,T as WaveframeController,bt as WaveframePlayer,B as formatTime,It as generatePeaks,qt as highlightCode,Ht as loadAudioToMemory,j as resamplePeaks,Vt as revokeAudioMemory,gr as useResampledPeaks,Q as useResizeObserver,J as useWaveframe,f as useWaveframeStore};//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|