waveframe 0.3.1 → 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 +78 -64
- package/dist/index.d.ts +78 -64
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -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 */
|
|
@@ -172,7 +174,7 @@ declare class PlayerCore {
|
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
/**
|
|
175
|
-
* Represents the complete state of the Waveframe
|
|
177
|
+
* Represents the complete state of the Waveframe controller, combining playback and analysis.
|
|
176
178
|
*/
|
|
177
179
|
type EngineState = PlayerState & {
|
|
178
180
|
/** The current set of generated or provided waveform peaks (0-1 range) */
|
|
@@ -194,21 +196,21 @@ type EngineListener = (state: EngineState) => void;
|
|
|
194
196
|
*
|
|
195
197
|
* @example
|
|
196
198
|
* ```typescript
|
|
197
|
-
* const
|
|
199
|
+
* const controller = new WaveframeController();
|
|
198
200
|
*
|
|
199
201
|
* // Load from URL (automatic analysis if peaks omitted)
|
|
200
|
-
*
|
|
202
|
+
* controller.load('https://example.com/audio.mp3');
|
|
201
203
|
*
|
|
202
204
|
* // Load from Blob with pre-computed peaks
|
|
203
|
-
*
|
|
205
|
+
* controller.load(myBlob, [0.1, 0.5, 0.8]);
|
|
204
206
|
*
|
|
205
207
|
* // Subscription
|
|
206
|
-
* const unsubscribe =
|
|
208
|
+
* const unsubscribe = controller.subscribe((state) => {
|
|
207
209
|
* console.log('Current time:', state.currentTime);
|
|
208
210
|
* });
|
|
209
211
|
* ```
|
|
210
212
|
*/
|
|
211
|
-
declare class
|
|
213
|
+
declare class WaveframeController {
|
|
212
214
|
private player;
|
|
213
215
|
private analyzer;
|
|
214
216
|
private listeners;
|
|
@@ -216,7 +218,7 @@ declare class WaveframeEngine {
|
|
|
216
218
|
private _media;
|
|
217
219
|
private _objectUrl;
|
|
218
220
|
/**
|
|
219
|
-
* Creates a new instance of the
|
|
221
|
+
* Creates a new instance of the WaveframeController.
|
|
220
222
|
* Initializes internal PlayerCore and PeakAnalyzer.
|
|
221
223
|
*/
|
|
222
224
|
constructor();
|
|
@@ -239,6 +241,14 @@ declare class WaveframeEngine {
|
|
|
239
241
|
* Useful for `useSyncExternalStore`.
|
|
240
242
|
*/
|
|
241
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;
|
|
242
252
|
/**
|
|
243
253
|
* Revokes any existing Object URLs to prevent memory leaks.
|
|
244
254
|
*/
|
|
@@ -263,11 +273,11 @@ declare class WaveframeEngine {
|
|
|
263
273
|
/**
|
|
264
274
|
* Toggles playback between playing and paused.
|
|
265
275
|
*/
|
|
266
|
-
togglePlay(): void
|
|
276
|
+
togglePlay(): Promise<void>;
|
|
267
277
|
/**
|
|
268
278
|
* Starts audio playback.
|
|
269
279
|
*/
|
|
270
|
-
play(): void
|
|
280
|
+
play(): Promise<void>;
|
|
271
281
|
/**
|
|
272
282
|
* Pauses audio playback.
|
|
273
283
|
*/
|
|
@@ -288,7 +298,7 @@ declare class WaveframeEngine {
|
|
|
288
298
|
*/
|
|
289
299
|
setMuted(muted: boolean): void;
|
|
290
300
|
/**
|
|
291
|
-
* Disposes of the
|
|
301
|
+
* Disposes of the controller, pausing playback and clearing all listeners and resources.
|
|
292
302
|
*/
|
|
293
303
|
dispose(): void;
|
|
294
304
|
}
|
|
@@ -361,10 +371,10 @@ interface WaveframePlayerProps {
|
|
|
361
371
|
*/
|
|
362
372
|
theme?: WaveframeTheme;
|
|
363
373
|
/**
|
|
364
|
-
* Optional
|
|
365
|
-
* 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.
|
|
366
376
|
*/
|
|
367
|
-
|
|
377
|
+
controller?: WaveframeController;
|
|
368
378
|
}
|
|
369
379
|
/**
|
|
370
380
|
* The standard "all-in-one" Waveframe player component.
|
|
@@ -375,17 +385,37 @@ interface WaveframePlayerProps {
|
|
|
375
385
|
declare const WaveframePlayer: React$1.FC<WaveframePlayerProps>;
|
|
376
386
|
|
|
377
387
|
interface WaveformProps {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
+
*/
|
|
385
403
|
resolution?: number | 'auto';
|
|
404
|
+
/** Width of each bar in pixels (if resolution is 'auto') */
|
|
386
405
|
barWidth?: number;
|
|
406
|
+
/** Gap between bars in pixels (if resolution is 'auto') */
|
|
387
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;
|
|
388
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
|
+
*/
|
|
389
419
|
declare const Waveform: React$1.FC<WaveformProps>;
|
|
390
420
|
|
|
391
421
|
/**
|
|
@@ -436,77 +466,61 @@ declare class PeakAnalyzer {
|
|
|
436
466
|
interface UseWaveframeOptions {
|
|
437
467
|
/** Optional pre-computed peaks to skip automatic analysis */
|
|
438
468
|
peaks?: number[];
|
|
439
|
-
/** Optional external
|
|
440
|
-
|
|
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;
|
|
441
473
|
}
|
|
442
474
|
/**
|
|
443
|
-
* A headless hook that
|
|
475
|
+
* A headless hook that manages the lifecycle of a WaveframeController.
|
|
444
476
|
*
|
|
445
|
-
* It
|
|
446
|
-
*
|
|
477
|
+
* It returns a stable controller instance that can be passed to components
|
|
478
|
+
* like <Waveform /> or used for custom playback logic.
|
|
447
479
|
*
|
|
448
480
|
* @param media The audio source (URL string or Blob/File object).
|
|
449
|
-
* @param options Additional configuration and an optional external
|
|
481
|
+
* @param options Additional configuration and an optional external controller.
|
|
450
482
|
*
|
|
451
483
|
* @example
|
|
452
484
|
* ```tsx
|
|
453
|
-
* const {
|
|
485
|
+
* const { controller, state } = useWaveframe('https://example.com/audio.mp3');
|
|
454
486
|
*
|
|
455
487
|
* return (
|
|
456
488
|
* <div>
|
|
457
|
-
* <
|
|
458
|
-
* <
|
|
489
|
+
* <Waveform controller={controller} />
|
|
490
|
+
* <button onClick={() => controller.togglePlay()}>
|
|
491
|
+
* {state.isPlaying ? 'Pause' : 'Play'}
|
|
492
|
+
* </button>
|
|
459
493
|
* </div>
|
|
460
494
|
* );
|
|
461
495
|
* ```
|
|
462
496
|
*/
|
|
463
497
|
declare const useWaveframe: (media: string | Blob | undefined, options?: UseWaveframeOptions) => {
|
|
464
|
-
/** The
|
|
498
|
+
/** The stable WaveframeController instance */
|
|
499
|
+
controller: WaveframeController;
|
|
500
|
+
/** The reactive engine state */
|
|
465
501
|
state: EngineState;
|
|
466
|
-
/** The raw WaveframeEngine instance for advanced usage */
|
|
467
|
-
engine: WaveframeEngine;
|
|
468
|
-
/** Toggles playback between playing and paused */
|
|
469
|
-
togglePlay: () => void;
|
|
470
|
-
/** Starts audio playback */
|
|
471
|
-
play: () => void;
|
|
472
|
-
/** Pauses audio playback */
|
|
473
|
-
pause: () => void;
|
|
474
|
-
/** Seeks to a specific percentage (0-1) */
|
|
475
|
-
seek: (percentage: number) => void;
|
|
476
|
-
/** Sets the playback volume (0-1) */
|
|
477
|
-
setVolume: (v: number) => void;
|
|
478
|
-
/** Mutes or unmutes the audio */
|
|
479
|
-
setMuted: (m: boolean) => void;
|
|
480
|
-
/** Manually triggers a re-analysis of the current media */
|
|
481
|
-
analyze: (samples?: number) => Promise<void>;
|
|
482
502
|
};
|
|
483
503
|
|
|
484
504
|
/**
|
|
485
|
-
* A React hook that synchronizes a
|
|
505
|
+
* A React hook that synchronizes a WaveframeController's state with a React component.
|
|
486
506
|
*
|
|
487
|
-
* It
|
|
488
|
-
*
|
|
507
|
+
* It supports an optional selector function to subscribe to specific parts of the state,
|
|
508
|
+
* preventing unnecessary re-renders when unrelated state changes.
|
|
489
509
|
*
|
|
490
|
-
* @param
|
|
491
|
-
* @
|
|
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.
|
|
492
513
|
*
|
|
493
514
|
* @example
|
|
494
515
|
* ```tsx
|
|
495
|
-
*
|
|
496
|
-
*
|
|
516
|
+
* // Subscribe only to isPlaying
|
|
517
|
+
* const isPlaying = useWaveframeStore(controller, state => state.isPlaying);
|
|
497
518
|
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* <button onClick={() => engine.togglePlay()}>
|
|
501
|
-
* {isPlaying ? 'Pause' : 'Play'}
|
|
502
|
-
* </button>
|
|
503
|
-
* <p>{currentTime.toFixed(2)} / {duration.toFixed(2)}</p>
|
|
504
|
-
* </div>
|
|
505
|
-
* );
|
|
506
|
-
* };
|
|
519
|
+
* // Subscribe to the full state
|
|
520
|
+
* const state = useWaveframeStore(controller);
|
|
507
521
|
* ```
|
|
508
522
|
*/
|
|
509
|
-
declare
|
|
523
|
+
declare function useWaveframeStore<T = EngineState>(controller: WaveframeController, selector?: (state: EngineState) => T): T;
|
|
510
524
|
|
|
511
525
|
declare const useResampledPeaks: (peaks: number[], targetCount: number) => number[];
|
|
512
526
|
|
|
@@ -559,4 +573,4 @@ declare const resamplePeaks: (peaks: number[], targetCount: number) => number[];
|
|
|
559
573
|
*/
|
|
560
574
|
declare const highlightCode: (code: string) => string[];
|
|
561
575
|
|
|
562
|
-
export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type UseWaveframeOptions, Waveform, 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
|