@waveform-playlist/browser 15.1.0 → 15.3.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 +104 -0
- package/dist/index.d.mts +33 -3
- package/dist/index.d.ts +33 -3
- package/dist/index.js +59 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +75 -23
- package/dist/index.mjs.map +1 -1
- package/dist/tone.js +5 -4
- package/dist/tone.js.map +1 -1
- package/dist/tone.mjs +5 -4
- package/dist/tone.mjs.map +1 -1
- package/package.json +8 -9
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @waveform-playlist/browser
|
|
2
|
+
|
|
3
|
+
React components, hooks, and context providers for building a multitrack Web Audio editor and player — canvas waveform rendering, clip-based editing, and playback controls on top of Tone.js or a native `HTMLAudioElement`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multitrack editor** — `<Waveform />` renders canvas waveforms, per-track controls (mute/solo/volume/pan), a time ruler, and a playhead for any number of tracks
|
|
8
|
+
- **Clip-based editing** — drag-to-move, boundary trimming with collision detection, splitting at the playhead, snap-to-grid (beats/bars or timescale), undo/redo
|
|
9
|
+
- **Split context providers** — `WaveformPlaylistProvider` (multitrack, Tone.js-backed) and `MediaElementPlaylistProvider` (single-track, `<audio>`-backed with pitch-preserving playback rate)
|
|
10
|
+
- **Four focused context hooks** — `usePlaybackAnimation`, `usePlaylistState`, `usePlaylistControls`, `usePlaylistData` isolate 60fps playhead updates from low-frequency state so unrelated components don't re-render on every animation frame
|
|
11
|
+
- **Audio effects** — 20 Tone.js effects (reverb, delay, modulation, filter, distortion, dynamics, spatial) with runtime parameter control, master and per-track chains, plus WAM 2.0 plugin hosting
|
|
12
|
+
- **Recording** — pairs with `@waveform-playlist/recording` for mic input, overdubbing, and live waveform preview; punch-in takes replace overlapped content, and `setRecordingActive(active, armedTrackId)` suppresses the end-of-timeline auto-stop and transiently mutes the recorded-over track for the session
|
|
13
|
+
- **Annotations & spectrogram** — optional integration contexts for `@waveform-playlist/annotations` and `@waveform-playlist/spectrogram`
|
|
14
|
+
- **WAV export** — render the full mix (including effects) offline to a downloadable WAV file
|
|
15
|
+
- **MediaElement player mode** — a lightweight single-track player with pitch-preserving speed control, for podcast/audiobook-style use cases that don't need multitrack mixing
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @waveform-playlist/browser tone @dnd-kit/react
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`@waveform-playlist/browser` requires these peer dependencies:
|
|
24
|
+
|
|
25
|
+
| Package | Purpose |
|
|
26
|
+
|---------|---------|
|
|
27
|
+
| `react` / `react-dom` | ^18.0.0 or ^19.0.0 |
|
|
28
|
+
| `styled-components` | ^6.0.0 — CSS-in-JS styling |
|
|
29
|
+
| `tone` | ^15.0.0 — Web Audio engine (optional if you supply your own `PlayoutAdapter`) |
|
|
30
|
+
| `@dnd-kit/react` | ^0.3.0 — drag-and-drop (includes `@dnd-kit/dom` and `@dnd-kit/abstract`) |
|
|
31
|
+
| `@waveform-playlist/playout` | Tone.js playout engine (optional peer, dynamically imported by default) |
|
|
32
|
+
| `@waveform-playlist/media-element-playout` | engine for `MediaElementPlaylistProvider` (optional peer) |
|
|
33
|
+
| `@waveform-playlist/annotations` / `@waveform-playlist/recording` / `@dawcore/wam` | optional, install only if you use those features |
|
|
34
|
+
|
|
35
|
+
**Engine-free core:** the package's main entry has no static dependency on `tone` or `@waveform-playlist/playout` — a `MediaElementPlaylistProvider`-only consumer, or one supplying a custom `createAdapter`, never resolves either. Tone-coupled exports (`useAudioTracks`, `useDynamicTracks`, effects hooks, `useExportWav`, `useOutputMeter`, `useMasterAnalyser`) live at the **`@waveform-playlist/browser/tone`** subpath:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { useAudioTracks, useDynamicEffects, useExportWav } from '@waveform-playlist/browser/tone';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import {
|
|
45
|
+
WaveformPlaylistProvider,
|
|
46
|
+
Waveform,
|
|
47
|
+
PlayButton,
|
|
48
|
+
PauseButton,
|
|
49
|
+
StopButton,
|
|
50
|
+
} from '@waveform-playlist/browser';
|
|
51
|
+
import { useAudioTracks } from '@waveform-playlist/browser/tone';
|
|
52
|
+
|
|
53
|
+
function MyPlaylist() {
|
|
54
|
+
const { tracks, loading, error } = useAudioTracks([
|
|
55
|
+
{ src: '/audio/vocals.mp3', name: 'Vocals' },
|
|
56
|
+
{ src: '/audio/guitar.mp3', name: 'Guitar' },
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
if (loading) return <div>Loading audio...</div>;
|
|
60
|
+
if (error) return <div>Error: {error}</div>;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<WaveformPlaylistProvider
|
|
64
|
+
tracks={tracks}
|
|
65
|
+
samplesPerPixel={1024}
|
|
66
|
+
waveHeight={128}
|
|
67
|
+
timescale
|
|
68
|
+
controls={{ show: true, width: 200 }}
|
|
69
|
+
>
|
|
70
|
+
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
|
|
71
|
+
<PlayButton />
|
|
72
|
+
<PauseButton />
|
|
73
|
+
<StopButton />
|
|
74
|
+
</div>
|
|
75
|
+
<Waveform />
|
|
76
|
+
</WaveformPlaylistProvider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Prefer a lightweight single-track player instead? Use `MediaElementPlaylistProvider` with `@waveform-playlist/media-element-playout` — no Tone.js, no AudioBuffer decoding, and pitch-preserving playback rate out of the box.
|
|
82
|
+
|
|
83
|
+
## Context Hooks
|
|
84
|
+
|
|
85
|
+
`WaveformPlaylistProvider` splits its context into four hooks so components only re-render for the data they actually read:
|
|
86
|
+
|
|
87
|
+
| Hook | Exposes |
|
|
88
|
+
|------|---------|
|
|
89
|
+
| `usePlaybackAnimation` | `isPlaying`, `currentTime`, animation refs, `getPlaybackTime()`, `registerFrameCallback()` for 60fps-driven UI (playhead, progress bars) |
|
|
90
|
+
| `usePlaylistState` | Low-frequency state — selection, loop region, annotations, `selectedTrackId`, `canUndo`/`canRedo` |
|
|
91
|
+
| `usePlaylistControls` | Actions — `play`, `pause`, `stop`, `seekTo`, track mute/solo/volume/pan, zoom, undo/redo, `setRecordingActive` (recording-session suppression + armed-track mute) |
|
|
92
|
+
| `usePlaylistData` | Derived/config data — `tracks`, `duration`, `sampleRate`, `peaksDataArray`, `trackStates`, `isReady` |
|
|
93
|
+
|
|
94
|
+
`usePlaylistDataOptional` is the same as `usePlaylistData` but returns `null` outside a provider (for components that render both inside and outside one). `MediaElementPlaylistProvider` has the equivalent `useMediaElementAnimation`, `useMediaElementState`, `useMediaElementControls`, `useMediaElementData`.
|
|
95
|
+
|
|
96
|
+
## Examples & Documentation
|
|
97
|
+
|
|
98
|
+
- [naomiaro.github.io/waveform-playlist](https://naomiaro.github.io/waveform-playlist/) — guides, API reference, and live examples
|
|
99
|
+
- [Basic Usage guide](https://naomiaro.github.io/waveform-playlist/docs/react/getting-started/basic-usage) — walkthrough of the provider pattern
|
|
100
|
+
- [`examples/media-element-player`](https://github.com/naomiaro/waveform-playlist/tree/main/examples/media-element-player) — `MediaElementPlaylistProvider` starter (`pnpm example:media-element`)
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -94,8 +94,12 @@ interface PlaylistStateContextValue {
|
|
|
94
94
|
selectedTrackId: string | null;
|
|
95
95
|
loopStart: number;
|
|
96
96
|
loopEnd: number;
|
|
97
|
-
/** Whether playback continues past the end of loaded audio
|
|
97
|
+
/** Whether playback continues past the end of loaded audio instead of
|
|
98
|
+
* auto-stopping (implies the fillViewport layout) */
|
|
98
99
|
indefinitePlayback: boolean;
|
|
100
|
+
/** Whether the timeline visually fills the scroll container even when the
|
|
101
|
+
* audio is shorter (layout only — no effect on playback) */
|
|
102
|
+
fillViewport: boolean;
|
|
99
103
|
/** Whether undo is available */
|
|
100
104
|
canUndo: boolean;
|
|
101
105
|
/** Whether redo is available */
|
|
@@ -132,6 +136,16 @@ interface PlaylistControlsContextValue {
|
|
|
132
136
|
clearLoopRegion: () => void;
|
|
133
137
|
undo: () => void;
|
|
134
138
|
redo: () => void;
|
|
139
|
+
/** Mark a recording session active/inactive. While active: (1) the
|
|
140
|
+
* end-of-audio auto-stop is suppressed so overdub playback runs past the
|
|
141
|
+
* end of existing material, and (2) with an `armedTrackId`, that track's
|
|
142
|
+
* existing content is transiently muted — punch-in recording replaces
|
|
143
|
+
* whatever the take overlaps (#579), so the doomed material must not play
|
|
144
|
+
* under the overdub; its previous mute state is restored when the session
|
|
145
|
+
* ends (audio-only, the UI mute control is untouched). Wired automatically
|
|
146
|
+
* from the Waveform / PlaylistVisualization `recordingState` prop; overdub
|
|
147
|
+
* flows should also call it eagerly (before `play()`). */
|
|
148
|
+
setRecordingActive: (active: boolean, armedTrackId?: string | null) => void;
|
|
135
149
|
}
|
|
136
150
|
interface PlaylistDataContextValue {
|
|
137
151
|
duration: number;
|
|
@@ -155,6 +169,8 @@ interface PlaylistDataContextValue {
|
|
|
155
169
|
canZoomOut: boolean;
|
|
156
170
|
barWidth: number;
|
|
157
171
|
barGap: number;
|
|
172
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). */
|
|
173
|
+
roundedBars: boolean;
|
|
158
174
|
/** Width in pixels of progress bars. Defaults to barWidth + barGap (fills gaps). */
|
|
159
175
|
progressBarWidth: number;
|
|
160
176
|
/** Whether the playlist has finished loading all tracks */
|
|
@@ -200,6 +216,8 @@ interface WaveformPlaylistProviderProps {
|
|
|
200
216
|
barWidth?: number;
|
|
201
217
|
/** Spacing in pixels between waveform bars. Default: 0 */
|
|
202
218
|
barGap?: number;
|
|
219
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */
|
|
220
|
+
roundedBars?: boolean;
|
|
203
221
|
/** Width in pixels of progress bars. Default: barWidth + barGap (fills gaps). */
|
|
204
222
|
progressBarWidth?: number;
|
|
205
223
|
/** Callback when engine clip operations (move, trim, split) change tracks.
|
|
@@ -218,9 +236,17 @@ interface WaveformPlaylistProviderProps {
|
|
|
218
236
|
* Use this during progressive loading to avoid rebuilding the engine for
|
|
219
237
|
* each track — flip to false when all tracks are ready for a single build. */
|
|
220
238
|
deferEngineRebuild?: boolean;
|
|
221
|
-
/** Disable automatic stop when the cursor reaches the end of the
|
|
222
|
-
* track
|
|
239
|
+
/** Disable the automatic stop when the cursor reaches the end of the
|
|
240
|
+
* longest track — the transport rolls until an explicit stop/pause
|
|
241
|
+
* (DAW-style). Implies the fillViewport layout. Recording sessions
|
|
242
|
+
* suppress the auto-stop automatically, so most recording UIs don't
|
|
243
|
+
* need this. */
|
|
223
244
|
indefinitePlayback?: boolean;
|
|
245
|
+
/** Extend the timeline (background + timescale) to fill the visible scroll
|
|
246
|
+
* container even when the audio is shorter. Layout only. Recording UIs
|
|
247
|
+
* typically want this so the empty timeline doesn't collapse to the audio
|
|
248
|
+
* width. */
|
|
249
|
+
fillViewport?: boolean;
|
|
224
250
|
/** Desired AudioContext sample rate. Creates a cross-browser AudioContext at
|
|
225
251
|
* this rate via standardized-audio-context. Pre-computed peaks (.dat files)
|
|
226
252
|
* render instantly when they match. On mismatch, falls back to worker. */
|
|
@@ -298,6 +324,8 @@ interface MediaElementDataContextValue {
|
|
|
298
324
|
};
|
|
299
325
|
barWidth: number;
|
|
300
326
|
barGap: number;
|
|
327
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). */
|
|
328
|
+
roundedBars: boolean;
|
|
301
329
|
progressBarWidth: number;
|
|
302
330
|
fadeIn?: FadeConfig;
|
|
303
331
|
fadeOut?: FadeConfig;
|
|
@@ -334,6 +362,8 @@ interface MediaElementPlaylistProviderProps {
|
|
|
334
362
|
barWidth?: number;
|
|
335
363
|
/** Gap between waveform bars */
|
|
336
364
|
barGap?: number;
|
|
365
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */
|
|
366
|
+
roundedBars?: boolean;
|
|
337
367
|
/** Width of progress bars */
|
|
338
368
|
progressBarWidth?: number;
|
|
339
369
|
/** Callback when annotations are changed (drag, edit, etc.) */
|
package/dist/index.d.ts
CHANGED
|
@@ -94,8 +94,12 @@ interface PlaylistStateContextValue {
|
|
|
94
94
|
selectedTrackId: string | null;
|
|
95
95
|
loopStart: number;
|
|
96
96
|
loopEnd: number;
|
|
97
|
-
/** Whether playback continues past the end of loaded audio
|
|
97
|
+
/** Whether playback continues past the end of loaded audio instead of
|
|
98
|
+
* auto-stopping (implies the fillViewport layout) */
|
|
98
99
|
indefinitePlayback: boolean;
|
|
100
|
+
/** Whether the timeline visually fills the scroll container even when the
|
|
101
|
+
* audio is shorter (layout only — no effect on playback) */
|
|
102
|
+
fillViewport: boolean;
|
|
99
103
|
/** Whether undo is available */
|
|
100
104
|
canUndo: boolean;
|
|
101
105
|
/** Whether redo is available */
|
|
@@ -132,6 +136,16 @@ interface PlaylistControlsContextValue {
|
|
|
132
136
|
clearLoopRegion: () => void;
|
|
133
137
|
undo: () => void;
|
|
134
138
|
redo: () => void;
|
|
139
|
+
/** Mark a recording session active/inactive. While active: (1) the
|
|
140
|
+
* end-of-audio auto-stop is suppressed so overdub playback runs past the
|
|
141
|
+
* end of existing material, and (2) with an `armedTrackId`, that track's
|
|
142
|
+
* existing content is transiently muted — punch-in recording replaces
|
|
143
|
+
* whatever the take overlaps (#579), so the doomed material must not play
|
|
144
|
+
* under the overdub; its previous mute state is restored when the session
|
|
145
|
+
* ends (audio-only, the UI mute control is untouched). Wired automatically
|
|
146
|
+
* from the Waveform / PlaylistVisualization `recordingState` prop; overdub
|
|
147
|
+
* flows should also call it eagerly (before `play()`). */
|
|
148
|
+
setRecordingActive: (active: boolean, armedTrackId?: string | null) => void;
|
|
135
149
|
}
|
|
136
150
|
interface PlaylistDataContextValue {
|
|
137
151
|
duration: number;
|
|
@@ -155,6 +169,8 @@ interface PlaylistDataContextValue {
|
|
|
155
169
|
canZoomOut: boolean;
|
|
156
170
|
barWidth: number;
|
|
157
171
|
barGap: number;
|
|
172
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). */
|
|
173
|
+
roundedBars: boolean;
|
|
158
174
|
/** Width in pixels of progress bars. Defaults to barWidth + barGap (fills gaps). */
|
|
159
175
|
progressBarWidth: number;
|
|
160
176
|
/** Whether the playlist has finished loading all tracks */
|
|
@@ -200,6 +216,8 @@ interface WaveformPlaylistProviderProps {
|
|
|
200
216
|
barWidth?: number;
|
|
201
217
|
/** Spacing in pixels between waveform bars. Default: 0 */
|
|
202
218
|
barGap?: number;
|
|
219
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */
|
|
220
|
+
roundedBars?: boolean;
|
|
203
221
|
/** Width in pixels of progress bars. Default: barWidth + barGap (fills gaps). */
|
|
204
222
|
progressBarWidth?: number;
|
|
205
223
|
/** Callback when engine clip operations (move, trim, split) change tracks.
|
|
@@ -218,9 +236,17 @@ interface WaveformPlaylistProviderProps {
|
|
|
218
236
|
* Use this during progressive loading to avoid rebuilding the engine for
|
|
219
237
|
* each track — flip to false when all tracks are ready for a single build. */
|
|
220
238
|
deferEngineRebuild?: boolean;
|
|
221
|
-
/** Disable automatic stop when the cursor reaches the end of the
|
|
222
|
-
* track
|
|
239
|
+
/** Disable the automatic stop when the cursor reaches the end of the
|
|
240
|
+
* longest track — the transport rolls until an explicit stop/pause
|
|
241
|
+
* (DAW-style). Implies the fillViewport layout. Recording sessions
|
|
242
|
+
* suppress the auto-stop automatically, so most recording UIs don't
|
|
243
|
+
* need this. */
|
|
223
244
|
indefinitePlayback?: boolean;
|
|
245
|
+
/** Extend the timeline (background + timescale) to fill the visible scroll
|
|
246
|
+
* container even when the audio is shorter. Layout only. Recording UIs
|
|
247
|
+
* typically want this so the empty timeline doesn't collapse to the audio
|
|
248
|
+
* width. */
|
|
249
|
+
fillViewport?: boolean;
|
|
224
250
|
/** Desired AudioContext sample rate. Creates a cross-browser AudioContext at
|
|
225
251
|
* this rate via standardized-audio-context. Pre-computed peaks (.dat files)
|
|
226
252
|
* render instantly when they match. On mismatch, falls back to worker. */
|
|
@@ -298,6 +324,8 @@ interface MediaElementDataContextValue {
|
|
|
298
324
|
};
|
|
299
325
|
barWidth: number;
|
|
300
326
|
barGap: number;
|
|
327
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). */
|
|
328
|
+
roundedBars: boolean;
|
|
301
329
|
progressBarWidth: number;
|
|
302
330
|
fadeIn?: FadeConfig;
|
|
303
331
|
fadeOut?: FadeConfig;
|
|
@@ -334,6 +362,8 @@ interface MediaElementPlaylistProviderProps {
|
|
|
334
362
|
barWidth?: number;
|
|
335
363
|
/** Gap between waveform bars */
|
|
336
364
|
barGap?: number;
|
|
365
|
+
/** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */
|
|
366
|
+
roundedBars?: boolean;
|
|
337
367
|
/** Width of progress bars */
|
|
338
368
|
progressBarWidth?: number;
|
|
339
369
|
/** Callback when annotations are changed (drag, edit, etc.) */
|
package/dist/index.js
CHANGED
|
@@ -1875,19 +1875,19 @@ var WaveformPlaylistProvider = ({
|
|
|
1875
1875
|
onAnnotationsChange,
|
|
1876
1876
|
barWidth = 1,
|
|
1877
1877
|
barGap = 0,
|
|
1878
|
+
roundedBars = false,
|
|
1878
1879
|
progressBarWidth: progressBarWidthProp,
|
|
1879
1880
|
onTracksChange,
|
|
1880
1881
|
soundFontCache,
|
|
1881
1882
|
deferEngineRebuild = false,
|
|
1882
1883
|
indefinitePlayback = false,
|
|
1884
|
+
fillViewport = false,
|
|
1883
1885
|
sampleRate: sampleRateProp,
|
|
1884
1886
|
createAdapter,
|
|
1885
1887
|
children
|
|
1886
1888
|
}) => {
|
|
1887
1889
|
var _a, _b, _c, _d;
|
|
1888
1890
|
const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
|
|
1889
|
-
const indefinitePlaybackRef = (0, import_react17.useRef)(indefinitePlayback);
|
|
1890
|
-
indefinitePlaybackRef.current = indefinitePlayback;
|
|
1891
1891
|
const stableZoomLevels = (0, import_react17.useMemo)(
|
|
1892
1892
|
() => zoomLevels,
|
|
1893
1893
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -1925,6 +1925,28 @@ var WaveformPlaylistProvider = ({
|
|
|
1925
1925
|
const engineRef = (0, import_react17.useRef)(null);
|
|
1926
1926
|
const adapterRef = (0, import_react17.useRef)(null);
|
|
1927
1927
|
const audioInitializedRef = (0, import_react17.useRef)(false);
|
|
1928
|
+
const indefinitePlaybackRef = (0, import_react17.useRef)(indefinitePlayback);
|
|
1929
|
+
indefinitePlaybackRef.current = indefinitePlayback;
|
|
1930
|
+
const recordingActiveRef = (0, import_react17.useRef)(false);
|
|
1931
|
+
const armedTrackMuteRef = (0, import_react17.useRef)(null);
|
|
1932
|
+
const setRecordingActive = (0, import_react17.useCallback)((active, armedTrackId) => {
|
|
1933
|
+
var _a2, _b2, _c2;
|
|
1934
|
+
recordingActiveRef.current = active;
|
|
1935
|
+
const engine = engineRef.current;
|
|
1936
|
+
if (!engine) return;
|
|
1937
|
+
if (active && armedTrackId) {
|
|
1938
|
+
if (((_a2 = armedTrackMuteRef.current) == null ? void 0 : _a2.trackId) === armedTrackId) return;
|
|
1939
|
+
if (armedTrackMuteRef.current) {
|
|
1940
|
+
engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);
|
|
1941
|
+
}
|
|
1942
|
+
const prevMuted = (_c2 = (_b2 = engine.getState().tracks.find((t) => t.id === armedTrackId)) == null ? void 0 : _b2.muted) != null ? _c2 : false;
|
|
1943
|
+
armedTrackMuteRef.current = { trackId: armedTrackId, prevMuted };
|
|
1944
|
+
engine.setTrackMute(armedTrackId, true);
|
|
1945
|
+
} else if (!active && armedTrackMuteRef.current) {
|
|
1946
|
+
engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);
|
|
1947
|
+
armedTrackMuteRef.current = null;
|
|
1948
|
+
}
|
|
1949
|
+
}, []);
|
|
1928
1950
|
const isPlayingRef = (0, import_react17.useRef)(false);
|
|
1929
1951
|
isPlayingRef.current = isPlaying;
|
|
1930
1952
|
const playStartPositionRef = (0, import_react17.useRef)(0);
|
|
@@ -2516,7 +2538,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2516
2538
|
playbackEndTimeRef.current = null;
|
|
2517
2539
|
return;
|
|
2518
2540
|
}
|
|
2519
|
-
if (time >= duration && !indefinitePlaybackRef.current) {
|
|
2541
|
+
if (time >= duration && !indefinitePlaybackRef.current && !recordingActiveRef.current) {
|
|
2520
2542
|
if (engineRef.current) {
|
|
2521
2543
|
engineRef.current.stop();
|
|
2522
2544
|
}
|
|
@@ -2787,6 +2809,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2787
2809
|
loopStart,
|
|
2788
2810
|
loopEnd,
|
|
2789
2811
|
indefinitePlayback,
|
|
2812
|
+
fillViewport,
|
|
2790
2813
|
canUndo,
|
|
2791
2814
|
canRedo
|
|
2792
2815
|
}),
|
|
@@ -2804,6 +2827,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2804
2827
|
loopStart,
|
|
2805
2828
|
loopEnd,
|
|
2806
2829
|
indefinitePlayback,
|
|
2830
|
+
fillViewport,
|
|
2807
2831
|
canUndo,
|
|
2808
2832
|
canRedo
|
|
2809
2833
|
]
|
|
@@ -2859,7 +2883,9 @@ var WaveformPlaylistProvider = ({
|
|
|
2859
2883
|
clearLoopRegion,
|
|
2860
2884
|
// Undo/redo
|
|
2861
2885
|
undo,
|
|
2862
|
-
redo
|
|
2886
|
+
redo,
|
|
2887
|
+
// Recording
|
|
2888
|
+
setRecordingActive
|
|
2863
2889
|
}),
|
|
2864
2890
|
[
|
|
2865
2891
|
play,
|
|
@@ -2891,7 +2917,8 @@ var WaveformPlaylistProvider = ({
|
|
|
2891
2917
|
setLoopRegionFromSelection,
|
|
2892
2918
|
clearLoopRegion,
|
|
2893
2919
|
undo,
|
|
2894
|
-
redo
|
|
2920
|
+
redo,
|
|
2921
|
+
setRecordingActive
|
|
2895
2922
|
]
|
|
2896
2923
|
);
|
|
2897
2924
|
const dataValue = (0, import_react17.useMemo)(
|
|
@@ -2914,6 +2941,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2914
2941
|
canZoomOut: zoom.canZoomOut,
|
|
2915
2942
|
barWidth,
|
|
2916
2943
|
barGap,
|
|
2944
|
+
roundedBars,
|
|
2917
2945
|
progressBarWidth,
|
|
2918
2946
|
isReady,
|
|
2919
2947
|
mono,
|
|
@@ -2939,6 +2967,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2939
2967
|
zoom.canZoomOut,
|
|
2940
2968
|
barWidth,
|
|
2941
2969
|
barGap,
|
|
2970
|
+
roundedBars,
|
|
2942
2971
|
progressBarWidth,
|
|
2943
2972
|
isReady,
|
|
2944
2973
|
mono,
|
|
@@ -3026,6 +3055,7 @@ var MediaElementPlaylistProvider = ({
|
|
|
3026
3055
|
annotationList,
|
|
3027
3056
|
barWidth = 1,
|
|
3028
3057
|
barGap = 0,
|
|
3058
|
+
roundedBars = false,
|
|
3029
3059
|
progressBarWidth: progressBarWidthProp,
|
|
3030
3060
|
audioContext,
|
|
3031
3061
|
onAnnotationsChange,
|
|
@@ -3348,6 +3378,7 @@ var MediaElementPlaylistProvider = ({
|
|
|
3348
3378
|
controls,
|
|
3349
3379
|
barWidth,
|
|
3350
3380
|
barGap,
|
|
3381
|
+
roundedBars,
|
|
3351
3382
|
progressBarWidth,
|
|
3352
3383
|
fadeIn: track.fadeIn,
|
|
3353
3384
|
fadeOut: track.fadeOut
|
|
@@ -3362,6 +3393,7 @@ var MediaElementPlaylistProvider = ({
|
|
|
3362
3393
|
controls,
|
|
3363
3394
|
barWidth,
|
|
3364
3395
|
barGap,
|
|
3396
|
+
roundedBars,
|
|
3365
3397
|
progressBarWidth,
|
|
3366
3398
|
track.fadeIn,
|
|
3367
3399
|
track.fadeOut
|
|
@@ -4015,7 +4047,7 @@ var PlaylistVisualization = ({
|
|
|
4015
4047
|
onRemoveTrack,
|
|
4016
4048
|
recordingState
|
|
4017
4049
|
}) => {
|
|
4018
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
4050
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
4019
4051
|
const theme = (0, import_ui_components8.useTheme)();
|
|
4020
4052
|
const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();
|
|
4021
4053
|
const {
|
|
@@ -4030,7 +4062,8 @@ var PlaylistVisualization = ({
|
|
|
4030
4062
|
loopStart,
|
|
4031
4063
|
loopEnd,
|
|
4032
4064
|
isLoopEnabled,
|
|
4033
|
-
indefinitePlayback
|
|
4065
|
+
indefinitePlayback,
|
|
4066
|
+
fillViewport
|
|
4034
4067
|
} = usePlaylistState();
|
|
4035
4068
|
const annotationIntegration = (0, import_react26.useContext)(AnnotationIntegrationContext);
|
|
4036
4069
|
const {
|
|
@@ -4045,7 +4078,8 @@ var PlaylistVisualization = ({
|
|
|
4045
4078
|
setScrollContainer,
|
|
4046
4079
|
setSelectedTrackId,
|
|
4047
4080
|
setCurrentTime,
|
|
4048
|
-
setLoopRegion
|
|
4081
|
+
setLoopRegion,
|
|
4082
|
+
setRecordingActive
|
|
4049
4083
|
} = usePlaylistControls();
|
|
4050
4084
|
const {
|
|
4051
4085
|
peaksDataArray,
|
|
@@ -4059,10 +4093,18 @@ var PlaylistVisualization = ({
|
|
|
4059
4093
|
controls,
|
|
4060
4094
|
barWidth,
|
|
4061
4095
|
barGap,
|
|
4096
|
+
roundedBars,
|
|
4062
4097
|
isReady,
|
|
4063
4098
|
mono
|
|
4064
4099
|
} = usePlaylistData();
|
|
4065
4100
|
const spectrogram = (0, import_react26.useContext)(SpectrogramIntegrationContext);
|
|
4101
|
+
const recordingActive = !!(recordingState == null ? void 0 : recordingState.isRecording);
|
|
4102
|
+
const armedTrackId = (_a = recordingState == null ? void 0 : recordingState.trackId) != null ? _a : null;
|
|
4103
|
+
(0, import_react26.useEffect)(() => {
|
|
4104
|
+
if (!recordingActive) return void 0;
|
|
4105
|
+
setRecordingActive(true, armedTrackId);
|
|
4106
|
+
return () => setRecordingActive(false);
|
|
4107
|
+
}, [recordingActive, armedTrackId, setRecordingActive]);
|
|
4066
4108
|
const perTrackSpectrogramHelpers = (0, import_react26.useMemo)(() => {
|
|
4067
4109
|
if (!spectrogram)
|
|
4068
4110
|
return /* @__PURE__ */ new Map();
|
|
@@ -4100,8 +4142,8 @@ var PlaylistVisualization = ({
|
|
|
4100
4142
|
}, max);
|
|
4101
4143
|
}, 0);
|
|
4102
4144
|
let displayDuration = tracksMaxDuration > 0 ? tracksMaxDuration : duration > 0 ? duration : DEFAULT_EMPTY_TRACK_DURATION;
|
|
4103
|
-
if (indefinitePlayback) {
|
|
4104
|
-
const containerWidth = (
|
|
4145
|
+
if (indefinitePlayback || fillViewport) {
|
|
4146
|
+
const containerWidth = (_c = (_b = scrollContainerRef.current) == null ? void 0 : _b.clientWidth) != null ? _c : 0;
|
|
4105
4147
|
const minContainerDuration = containerWidth * samplesPerPixel / sampleRate;
|
|
4106
4148
|
displayDuration = Math.max(displayDuration, minContainerDuration);
|
|
4107
4149
|
}
|
|
@@ -4317,7 +4359,8 @@ var PlaylistVisualization = ({
|
|
|
4317
4359
|
duration: displayDuration * 1e3,
|
|
4318
4360
|
controls,
|
|
4319
4361
|
barWidth,
|
|
4320
|
-
barGap
|
|
4362
|
+
barGap,
|
|
4363
|
+
roundedBars
|
|
4321
4364
|
},
|
|
4322
4365
|
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4323
4366
|
import_ui_components8.Playlist,
|
|
@@ -4558,8 +4601,8 @@ var PlaylistVisualization = ({
|
|
|
4558
4601
|
{
|
|
4559
4602
|
open: settingsModalTrackId !== null,
|
|
4560
4603
|
onClose: () => setSettingsModalTrackId(null),
|
|
4561
|
-
config: settingsModalTrackId !== null ? (
|
|
4562
|
-
colorMap: settingsModalTrackId !== null ? (
|
|
4604
|
+
config: settingsModalTrackId !== null ? (_h = (_g = (_f = (_d = spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)) == null ? void 0 : _d.config) != null ? _f : (_e = tracks.find((t) => t.id === settingsModalTrackId)) == null ? void 0 : _e.spectrogramConfig) != null ? _g : spectrogram.spectrogramConfig) != null ? _h : {} : {},
|
|
4605
|
+
colorMap: settingsModalTrackId !== null ? (_m = (_l = (_k = (_i = spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)) == null ? void 0 : _i.colorMap) != null ? _k : (_j = tracks.find((t) => t.id === settingsModalTrackId)) == null ? void 0 : _j.spectrogramColorMap) != null ? _l : spectrogram.spectrogramColorMap) != null ? _m : "viridis" : "viridis",
|
|
4563
4606
|
onApply: (newConfig, newColorMap) => {
|
|
4564
4607
|
if (settingsModalTrackId !== null) {
|
|
4565
4608
|
spectrogram.setTrackSpectrogramConfig(settingsModalTrackId, newConfig, newColorMap);
|
|
@@ -4935,6 +4978,7 @@ var MediaElementPlaylist = ({
|
|
|
4935
4978
|
playoutRef,
|
|
4936
4979
|
barWidth,
|
|
4937
4980
|
barGap,
|
|
4981
|
+
roundedBars,
|
|
4938
4982
|
fadeIn,
|
|
4939
4983
|
fadeOut
|
|
4940
4984
|
} = useMediaElementData();
|
|
@@ -5046,7 +5090,8 @@ var MediaElementPlaylist = ({
|
|
|
5046
5090
|
duration: duration * 1e3,
|
|
5047
5091
|
controls,
|
|
5048
5092
|
barWidth,
|
|
5049
|
-
barGap
|
|
5093
|
+
barGap,
|
|
5094
|
+
roundedBars
|
|
5050
5095
|
},
|
|
5051
5096
|
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
5052
5097
|
import_ui_components10.Playlist,
|