waveframe 0.3.1 → 0.4.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 +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 +83 -64
- package/dist/index.d.ts +83 -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,32 +196,37 @@ 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;
|
|
215
217
|
private _state;
|
|
216
218
|
private _media;
|
|
217
219
|
private _objectUrl;
|
|
220
|
+
private _isDisposed;
|
|
218
221
|
/**
|
|
219
|
-
* Creates a new instance of the
|
|
222
|
+
* Creates a new instance of the WaveframeController.
|
|
220
223
|
* Initializes internal PlayerCore and PeakAnalyzer.
|
|
221
224
|
*/
|
|
222
225
|
constructor();
|
|
226
|
+
/**
|
|
227
|
+
* Returns whether the controller has been disposed and is no longer usable.
|
|
228
|
+
*/
|
|
229
|
+
get isDisposed(): boolean;
|
|
223
230
|
/**
|
|
224
231
|
* Internal method to update the state and notify all subscribers.
|
|
225
232
|
*/
|
|
@@ -239,6 +246,14 @@ declare class WaveframeEngine {
|
|
|
239
246
|
* Useful for `useSyncExternalStore`.
|
|
240
247
|
*/
|
|
241
248
|
getSnapshot(): EngineState;
|
|
249
|
+
/**
|
|
250
|
+
* Returns the current engine state.
|
|
251
|
+
*/
|
|
252
|
+
get state(): EngineState;
|
|
253
|
+
/**
|
|
254
|
+
* Resets the audio player and analyzer, clearing state and current media.
|
|
255
|
+
*/
|
|
256
|
+
reset(): void;
|
|
242
257
|
/**
|
|
243
258
|
* Revokes any existing Object URLs to prevent memory leaks.
|
|
244
259
|
*/
|
|
@@ -263,11 +278,11 @@ declare class WaveframeEngine {
|
|
|
263
278
|
/**
|
|
264
279
|
* Toggles playback between playing and paused.
|
|
265
280
|
*/
|
|
266
|
-
togglePlay(): void
|
|
281
|
+
togglePlay(): Promise<void>;
|
|
267
282
|
/**
|
|
268
283
|
* Starts audio playback.
|
|
269
284
|
*/
|
|
270
|
-
play(): void
|
|
285
|
+
play(): Promise<void>;
|
|
271
286
|
/**
|
|
272
287
|
* Pauses audio playback.
|
|
273
288
|
*/
|
|
@@ -288,7 +303,7 @@ declare class WaveframeEngine {
|
|
|
288
303
|
*/
|
|
289
304
|
setMuted(muted: boolean): void;
|
|
290
305
|
/**
|
|
291
|
-
* Disposes of the
|
|
306
|
+
* Disposes of the controller, pausing playback and clearing all listeners and resources.
|
|
292
307
|
*/
|
|
293
308
|
dispose(): void;
|
|
294
309
|
}
|
|
@@ -361,10 +376,10 @@ interface WaveframePlayerProps {
|
|
|
361
376
|
*/
|
|
362
377
|
theme?: WaveframeTheme;
|
|
363
378
|
/**
|
|
364
|
-
* Optional
|
|
365
|
-
* If provided, the player will sync with this
|
|
379
|
+
* Optional WaveframeController instance for external control.
|
|
380
|
+
* If provided, the player will sync with this controller instead of creating its own.
|
|
366
381
|
*/
|
|
367
|
-
|
|
382
|
+
controller?: WaveframeController;
|
|
368
383
|
}
|
|
369
384
|
/**
|
|
370
385
|
* The standard "all-in-one" Waveframe player component.
|
|
@@ -375,17 +390,37 @@ interface WaveframePlayerProps {
|
|
|
375
390
|
declare const WaveframePlayer: React$1.FC<WaveframePlayerProps>;
|
|
376
391
|
|
|
377
392
|
interface WaveformProps {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
393
|
+
/** The WaveframeController instance managing audio state */
|
|
394
|
+
controller: WaveframeController;
|
|
395
|
+
/** Optional pre-computed peaks. If omitted, peaks from the controller are used. */
|
|
396
|
+
peaks?: number[];
|
|
397
|
+
/** Color of the background waveform bars */
|
|
398
|
+
waveColor?: string;
|
|
399
|
+
/** Color of the progress (played) waveform bars */
|
|
400
|
+
progressColor?: string;
|
|
401
|
+
/** Total height of the waveform in pixels */
|
|
402
|
+
height?: number;
|
|
403
|
+
/**
|
|
404
|
+
* Resolution of the waveform.
|
|
405
|
+
* 'auto' matches the container width (1 bar per pixel).
|
|
406
|
+
* A number sets a fixed number of bars.
|
|
407
|
+
*/
|
|
385
408
|
resolution?: number | 'auto';
|
|
409
|
+
/** Width of each bar in pixels (if resolution is 'auto') */
|
|
386
410
|
barWidth?: number;
|
|
411
|
+
/** Gap between bars in pixels (if resolution is 'auto') */
|
|
387
412
|
barGap?: number;
|
|
413
|
+
/** Overall amplitude multiplier (default 1.0) */
|
|
414
|
+
amplitude?: number;
|
|
415
|
+
/** Non-linear power scale to increase detail (e.g. 0.7-0.9 reduces 'sausage' look). Default 1.0 (linear). */
|
|
416
|
+
powerScale?: number;
|
|
388
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* A "smart" waveform component that visualizes audio progress and allows seeking.
|
|
420
|
+
*
|
|
421
|
+
* It subscribes directly to the provided WaveframeController for high-frequency
|
|
422
|
+
* progress updates, ensuring the parent component remains immune to re-renders.
|
|
423
|
+
*/
|
|
389
424
|
declare const Waveform: React$1.FC<WaveformProps>;
|
|
390
425
|
|
|
391
426
|
/**
|
|
@@ -436,77 +471,61 @@ declare class PeakAnalyzer {
|
|
|
436
471
|
interface UseWaveframeOptions {
|
|
437
472
|
/** Optional pre-computed peaks to skip automatic analysis */
|
|
438
473
|
peaks?: number[];
|
|
439
|
-
/** Optional external
|
|
440
|
-
|
|
474
|
+
/** Optional external controller instance for shared playback across components */
|
|
475
|
+
controller?: WaveframeController;
|
|
476
|
+
/** Whether to automatically start playback when media is loaded */
|
|
477
|
+
autoPlay?: boolean;
|
|
441
478
|
}
|
|
442
479
|
/**
|
|
443
|
-
* A headless hook that
|
|
480
|
+
* A headless hook that manages the lifecycle of a WaveframeController.
|
|
444
481
|
*
|
|
445
|
-
* It
|
|
446
|
-
*
|
|
482
|
+
* It returns a stable controller instance that can be passed to components
|
|
483
|
+
* like <Waveform /> or used for custom playback logic.
|
|
447
484
|
*
|
|
448
485
|
* @param media The audio source (URL string or Blob/File object).
|
|
449
|
-
* @param options Additional configuration and an optional external
|
|
486
|
+
* @param options Additional configuration and an optional external controller.
|
|
450
487
|
*
|
|
451
488
|
* @example
|
|
452
489
|
* ```tsx
|
|
453
|
-
* const {
|
|
490
|
+
* const { controller, state } = useWaveframe('https://example.com/audio.mp3');
|
|
454
491
|
*
|
|
455
492
|
* return (
|
|
456
493
|
* <div>
|
|
457
|
-
* <
|
|
458
|
-
* <
|
|
494
|
+
* <Waveform controller={controller} />
|
|
495
|
+
* <button onClick={() => controller.togglePlay()}>
|
|
496
|
+
* {state.isPlaying ? 'Pause' : 'Play'}
|
|
497
|
+
* </button>
|
|
459
498
|
* </div>
|
|
460
499
|
* );
|
|
461
500
|
* ```
|
|
462
501
|
*/
|
|
463
502
|
declare const useWaveframe: (media: string | Blob | undefined, options?: UseWaveframeOptions) => {
|
|
464
|
-
/** The
|
|
503
|
+
/** The stable WaveframeController instance */
|
|
504
|
+
controller: WaveframeController;
|
|
505
|
+
/** The reactive engine state */
|
|
465
506
|
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
507
|
};
|
|
483
508
|
|
|
484
509
|
/**
|
|
485
|
-
* A React hook that synchronizes a
|
|
510
|
+
* A React hook that synchronizes a WaveframeController's state with a React component.
|
|
486
511
|
*
|
|
487
|
-
* It
|
|
488
|
-
*
|
|
512
|
+
* It supports an optional selector function to subscribe to specific parts of the state,
|
|
513
|
+
* preventing unnecessary re-renders when unrelated state changes.
|
|
489
514
|
*
|
|
490
|
-
* @param
|
|
491
|
-
* @
|
|
515
|
+
* @param controller The WaveframeController instance to subscribe to.
|
|
516
|
+
* @param selector An optional function to select a specific slice of the state.
|
|
517
|
+
* @returns The selected state or the full EngineState if no selector is provided.
|
|
492
518
|
*
|
|
493
519
|
* @example
|
|
494
520
|
* ```tsx
|
|
495
|
-
*
|
|
496
|
-
*
|
|
521
|
+
* // Subscribe only to isPlaying
|
|
522
|
+
* const isPlaying = useWaveframeStore(controller, state => state.isPlaying);
|
|
497
523
|
*
|
|
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
|
-
* };
|
|
524
|
+
* // Subscribe to the full state
|
|
525
|
+
* const state = useWaveframeStore(controller);
|
|
507
526
|
* ```
|
|
508
527
|
*/
|
|
509
|
-
declare
|
|
528
|
+
declare function useWaveframeStore<T = EngineState>(controller: WaveframeController, selector?: (state: EngineState) => T): T;
|
|
510
529
|
|
|
511
530
|
declare const useResampledPeaks: (peaks: number[], targetCount: number) => number[];
|
|
512
531
|
|
|
@@ -559,4 +578,4 @@ declare const resamplePeaks: (peaks: number[], targetCount: number) => number[];
|
|
|
559
578
|
*/
|
|
560
579
|
declare const highlightCode: (code: string) => string[];
|
|
561
580
|
|
|
562
|
-
export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type UseWaveframeOptions, Waveform, type WaveformConfig,
|
|
581
|
+
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 e=t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),
|
|
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,e="Unknown audio error";if(t)switch(t.code){case t.MEDIA_ERR_ABORTED:e="Playback aborted";break;case t.MEDIA_ERR_NETWORK:e="Network error";break;case t.MEDIA_ERR_DECODE:e="Audio decoding failed";break;case t.MEDIA_ERR_SRC_NOT_SUPPORTED:e="Audio format not supported";break}this.updateState({isPlaying:false,error:e});});}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 e=t instanceof Error?t.message:"Playback failed";throw this.updateState({isPlaying:false,error:e}),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,e=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/e),l=[];for(let g=0;g<e;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;_isDisposed=false;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});});}get isDisposed(){return this._isDisposed}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._isDisposed=false,this.updateState({...this.player.state,peaks:[],isAnalyzing:false,error:null});}revokeOldSource(){this._objectUrl&&(URL.revokeObjectURL(this._objectUrl),this._objectUrl=null);}load(t,e){if(this._media===t){e&&e.length!==this._state.peaks.length&&this.updateState({peaks:e});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=e&&e.length>0;this.updateState({peaks:e||[],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 e=await this.analyzer.generatePeaks(this._media,t);this.updateState({peaks:e,isAnalyzing:!1});}catch(e){this.updateState({isAnalyzing:false,error:e instanceof Error?e.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:e}=this._state;e&&this.player.seek(t*e);}setVolume(t){this.player.setVolume(t);}setMuted(t){this.player.setMuted(t);}dispose(){this._isDisposed=true,this.revokeOldSource(),this.player.dispose(),this.analyzer.dispose(),this.listeners.clear();}};function f(r,t){return useSyncExternalStore(e=>r.subscribe(e),()=>{let e=r.getSnapshot();return t?t(e):e})}var J=(r,t={})=>{let{peaks:e,controller:a,autoPlay:n}=t,i=useRef(false),o=useMemo(()=>a||new T,[a]),s=a||o;useEffect(()=>{!a&&o.isDisposed&&o.reset();},[o,a]);let l=f(s);return useEffect(()=>(i.current=true,r&&(s.load(r,e),n&&s.play().catch(()=>{})),()=>{i.current=false;}),[s,r,e,n]),useEffect(()=>()=>{a||o.dispose();},[o,a]),{controller:s,state:l}};var B=memo(({artworkUrl:r,title:t,isLoading:e})=>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 ${e?"blur-md scale-110":""}`,children:r?jsx("img",{src:r,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"})})})}),e&&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"})})]}));B.displayName="ArtworkOverlay";var Q=r=>{let[t,e]=useState(0);return useEffect(()=>{if(!r.current)return;let a=new ResizeObserver(n=>{for(let i of n)e(i.contentRect.width);});return a.observe(r.current),()=>a.disconnect()},[r]),t};var It=async(r,t=512)=>{let e=new z;try{return await e.generatePeaks(r,t)}finally{e.dispose();}},Ht=async r=>{let e=await(await fetch(r)).blob();return URL.createObjectURL(e)},Vt=r=>{r&&r.startsWith("blob:")&&URL.revokeObjectURL(r);};var D=r=>{if(isNaN(r))return "0:00";let t=Math.floor(r/60),e=Math.floor(r%60);return `${t}:${e.toString().padStart(2,"0")}`},j=(r,t)=>{if(r.length===0)return [];if(r.length===t)return r;let e=new Array(t),a=r.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++)r[l]>i&&(i=r[l]);e[n]=i;}else for(let n=0;n<t;n++){let i=n*a,o=Math.floor(i),s=Math.min(o+1,r.length-1),l=i-o;e[n]=r[o]+(r[s]-r[o])*l;}return e},qt=r=>r.split(`
|
|
3
|
+
`).map(t=>{let e=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 e=e.replace(/("(?:[^"\\]|\\.)*")/g,o=>i(o,"text-[#ce9178]")),e=e.replace(/\b(\d+(\.\d+)?)\b/g,o=>i(o,"text-[#b5cea8]")),e=e.replace(/\b(WaveframePlayer)\b/g,o=>i(o,"text-[#4ec9b0]")),e=e.replace(/\b([a-z][a-zA-Z0-9]+)(?==)/g,o=>i(o,"text-[#9cdcfe]")),e=e.replace(/(<|>|\{|\}|\/|:|,)/g,'<span class="text-gray-500">$1</span>'),Object.entries(a).forEach(([o,s])=>{e=e.replace(o,s);}),e});var H=memo(({controller:r,peaks:t,waveColor:e="#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(r,d=>d.currentTime),u=f(r,d=>d.duration),A=f(r,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(),V=w.width*C,F=w.height*C;[d,v].forEach(k=>{(k.width!==V||k.height!==F)&&(k.width=V,k.height=F);}),(()=>{if(E.length===0)return;let{width:k,height:L}=d;p.clearRect(0,0,k,L),h.clearRect(0,0,k,L);let et=E.length,q=k/et,R=typeof i=="number"?q*.7:o*C,rt=typeof i=="number"?q*.3:s*C;p.lineCap="round",p.lineWidth=R,h.lineCap="round",h.lineWidth=R;let K=L/2;E.forEach((G,at)=>{if(G<=0)return;let N=at*(R+rt)+R/2,X=Math.max(R,G*L*.8),Y=K-X/2,Z=K+X/2;p.beginPath(),p.strokeStyle=e,p.moveTo(N,Y),p.lineTo(N,Z),p.stroke(),h.beginPath(),h.strokeStyle=a,h.moveTo(N,Y),h.lineTo(N,Z),h.stroke();});})();},[E,e,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));r.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":e,"--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`}})})]})});H.displayName="Waveform";var bt=memo(({media:r,peaks:t,artwork:e,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(r,{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 e=="string"?e:void 0);useEffect(()=>{if(e instanceof Blob){let w=URL.createObjectURL(e);return v(w),()=>URL.revokeObjectURL(w)}else v(e);},[e]);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(B,{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:[D(W)," / ",D(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(H,{controller:u,peaks:t,waveColor:p,progressColor:h,height:s,resolution:g,barWidth:y,barGap:m})})]})]})});bt.displayName="WaveframePlayer";var ge=(r,t)=>useMemo(()=>j(r,t),[r,t]);export{z as PeakAnalyzer,M as PlayerCore,H as Waveform,T as WaveframeController,bt as WaveframePlayer,D as formatTime,It as generatePeaks,qt as highlightCode,Ht as loadAudioToMemory,j as resamplePeaks,Vt as revokeAudioMemory,ge as useResampledPeaks,Q as useResizeObserver,J as useWaveframe,f as useWaveframeStore};//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|