@lucaismyname/ginger 0.0.60 → 0.0.61

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.
Files changed (2) hide show
  1. package/README.md +272 -65
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -69,6 +69,8 @@ For docs beyond this README, use the repository links below:
69
69
 
70
70
  ## Subpath Exports
71
71
 
72
+ Optional entrypoints keep the core bundle small. **Copy-paste starters** (full `Ginger.Provider` + `Ginger.Player` + subpath wiring) are in [Subpath copy-paste starters](#subpath-copy-paste-starters) below.
73
+
72
74
  - `@lucaismyname/ginger/client`
73
75
  - `@lucaismyname/ginger/testing`
74
76
  - `@lucaismyname/ginger/waveform`
@@ -81,12 +83,101 @@ For docs beyond this README, use the repository links below:
81
83
  - `@lucaismyname/ginger/experimental-gapless`
82
84
  - `@lucaismyname/ginger/devtools`
83
85
 
84
- ### Equalizer
86
+ ### Subpath Examples
87
+
88
+ Each snippet is a **single-file starting point**: replace `/your-audio.mp3` (or any `fileUrl`) with a real URL, then layer your UI. Imports use the published package names.
89
+
90
+ #### <a id="subpath-starter-client"></a> `@lucaismyname/ginger/client`
91
+
92
+ Same API as the root package, with a `"use client"` directive for React Server Components (for example Next.js App Router).
93
+
94
+ ```tsx
95
+ "use client";
96
+
97
+ import { Ginger } from "@lucaismyname/ginger/client";
98
+
99
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
100
+
101
+ export function App() {
102
+ return (
103
+ <Ginger.Provider initialTracks={tracks}>
104
+ <Ginger.Player />
105
+ <Ginger.Control.PlayPause />
106
+ </Ginger.Provider>
107
+ );
108
+ }
109
+ ```
110
+
111
+ #### <a id="subpath-starter-testing"></a> `@lucaismyname/ginger/testing`
112
+
113
+ Vitest (or Jest) example: `renderGinger` wraps **`Ginger.Provider`** and optionally **`Ginger.Player`**.
85
114
 
86
115
  ```tsx
116
+ import type { Track } from "@lucaismyname/ginger";
117
+ import { queryAudio, renderGinger } from "@lucaismyname/ginger/testing";
118
+ import { describe, expect, it } from "vitest";
119
+
120
+ const tracks: Track[] = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
121
+
122
+ describe("Ginger", () => {
123
+ it("mounts audio", () => {
124
+ const { container } = renderGinger(<p>ok</p>, { tracks });
125
+ expect(queryAudio(container)).toBeTruthy();
126
+ });
127
+ });
128
+ ```
129
+
130
+ #### <a id="subpath-starter-waveform"></a> `@lucaismyname/ginger/waveform`
131
+
132
+ `useAudioPeaks` reads the **same** URL as the current track (offline scan; keep files reasonably small).
133
+
134
+ ```tsx
135
+ import { Ginger, useGinger } from "@lucaismyname/ginger";
136
+ import { useAudioPeaks } from "@lucaismyname/ginger/waveform";
137
+
138
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
139
+
140
+ function PeaksRow() {
141
+ const { currentTrack } = useGinger();
142
+ const { peaks, isLoading, error } = useAudioPeaks(currentTrack?.fileUrl, 32);
143
+ if (error) return <p>{error}</p>;
144
+ if (isLoading) return <p>Scanning…</p>;
145
+ return (
146
+ <div style={{ display: "flex", gap: 1, height: 24, alignItems: "flex-end" }}>
147
+ {peaks.map((p, i) => (
148
+ <span
149
+ key={i}
150
+ style={{
151
+ width: 3,
152
+ height: `${Math.max(2, p * 24)}px`,
153
+ background: "#ea580c",
154
+ }}
155
+ />
156
+ ))}
157
+ </div>
158
+ );
159
+ }
160
+
161
+ export function App() {
162
+ return (
163
+ <Ginger.Provider initialTracks={tracks}>
164
+ <Ginger.Player />
165
+ <Ginger.Control.PlayPause />
166
+ <PeaksRow />
167
+ </Ginger.Provider>
168
+ );
169
+ }
170
+ ```
171
+
172
+ #### <a id="subpath-starter-equalizer"></a> `@lucaismyname/ginger/equalizer`
173
+
174
+ ```tsx
175
+ import { Ginger } from "@lucaismyname/ginger";
87
176
  import { useGingerEqualizer } from "@lucaismyname/ginger/equalizer";
88
177
 
89
- function MyPlayer() {
178
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
179
+
180
+ function EqSliders() {
90
181
  const { setBandGain, bands, error } = useGingerEqualizer({
91
182
  bands: [
92
183
  { frequency: 60 },
@@ -96,7 +187,6 @@ function MyPlayer() {
96
187
  { frequency: 16000 },
97
188
  ],
98
189
  });
99
-
100
190
  return (
101
191
  <div>
102
192
  {bands.map((band, i) => (
@@ -115,24 +205,32 @@ function MyPlayer() {
115
205
  </div>
116
206
  );
117
207
  }
118
- ```
119
-
120
- The EQ and `useGingerLiveAnalyzer` share the same `AudioContext` and can be used together. EQ filters are inserted before the analyser in the Web Audio graph.
121
208
 
122
- ### Spatial audio (`@lucaismyname/ginger/spatial`)
209
+ export function App() {
210
+ return (
211
+ <Ginger.Provider initialTracks={tracks}>
212
+ <Ginger.Player />
213
+ <Ginger.Control.PlayPause />
214
+ <EqSliders />
215
+ </Ginger.Provider>
216
+ );
217
+ }
218
+ ```
123
219
 
124
- Inserts an HRTF **`PannerNode`** into the same Web Audio graph as the EQ and live analyser (one `MediaElementAudioSourceNode` per `<audio>`).
220
+ #### <a id="subpath-starter-spatial"></a> `@lucaismyname/ginger/spatial`
125
221
 
126
222
  ```tsx
223
+ import { Ginger } from "@lucaismyname/ginger";
127
224
  import { useGingerSpatialAudio } from "@lucaismyname/ginger/spatial";
128
225
 
129
- function Spatialized() {
226
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
227
+
228
+ function SpatialControls() {
130
229
  const { setSourcePosition, error } = useGingerSpatialAudio({
131
230
  panningModel: "HRTF",
132
231
  position: [2, 0, 0],
133
232
  listenerPosition: [0, 0, 0],
134
233
  });
135
-
136
234
  return (
137
235
  <div>
138
236
  <button type="button" onClick={() => setSourcePosition(0, 0, -2)}>
@@ -142,20 +240,25 @@ function Spatialized() {
142
240
  </div>
143
241
  );
144
242
  }
145
- ```
146
-
147
- Use **`setListenerPosition`** and **`setPanningModel`** for runtime updates without rebuilding the graph.
148
243
 
149
- ### Transcript (`@lucaismyname/ginger/transcript`)
244
+ export function App() {
245
+ return (
246
+ <Ginger.Provider initialTracks={tracks}>
247
+ <Ginger.Player />
248
+ <Ginger.Control.PlayPause />
249
+ <SpatialControls />
250
+ </Ginger.Provider>
251
+ );
252
+ }
253
+ ```
150
254
 
151
- Parse **SRT** and **WebVTT** captions and sync cues to playback time (podcasts, video-style transcripts). HTML tags in cue text are stripped.
255
+ #### <a id="subpath-starter-transcript"></a> `@lucaismyname/ginger/transcript`
152
256
 
153
257
  ```tsx
154
- import {
155
- parseSrt,
156
- parseVtt,
157
- useGingerTranscriptSync,
158
- } from "@lucaismyname/ginger/transcript";
258
+ import { Ginger } from "@lucaismyname/ginger";
259
+ import { useGingerTranscriptSync } from "@lucaismyname/ginger/transcript";
260
+
261
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
159
262
 
160
263
  const vtt = `WEBVTT
161
264
 
@@ -164,11 +267,10 @@ Hello from VTT
164
267
  `;
165
268
 
166
269
  function TranscriptPanel() {
167
- const { cues, activeCue, activeCues } = useGingerTranscriptSync({
270
+ const { activeCue, activeCues } = useGingerTranscriptSync({
168
271
  transcript: vtt,
169
272
  format: "auto",
170
273
  });
171
-
172
274
  return (
173
275
  <div>
174
276
  <p>Now: {activeCue?.text ?? "—"}</p>
@@ -177,26 +279,29 @@ function TranscriptPanel() {
177
279
  );
178
280
  }
179
281
 
180
- // Or parse ahead of time:
181
- const cuesFromSrt = parseSrt(srtString);
182
- const cuesFromVtt = parseVtt(vttString);
282
+ export function App() {
283
+ return (
284
+ <Ginger.Provider initialTracks={tracks}>
285
+ <Ginger.Player />
286
+ <Ginger.Control.PlayPause />
287
+ <TranscriptPanel />
288
+ </Ginger.Provider>
289
+ );
290
+ }
183
291
  ```
184
292
 
185
- **`useGingerTranscriptSync`** mirrors **`useGingerLyricsSync`** but uses cue **start/end** ranges and exposes **`activeCues`** for overlapping captions. **`parseTranscriptAuto`** chooses VTT when the string starts with `WEBVTT`, otherwise SRT.
186
-
187
- ### Multi-tab sync (`@lucaismyname/ginger/remote`)
188
-
189
- Elects a **leader** tab via **`BroadcastChannel`** and pushes **`INIT`** snapshots to followers so queue and transport settings stay aligned. Mount **`Ginger.Player`** only on the leader so a single `<audio>` element plays.
293
+ #### <a id="subpath-starter-remote"></a> `@lucaismyname/ginger/remote`
190
294
 
191
295
  ```tsx
192
296
  import { Ginger } from "@lucaismyname/ginger";
193
297
  import { useGingerRemote } from "@lucaismyname/ginger/remote";
194
298
 
195
- function RemoteAwarePlayer() {
299
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
300
+
301
+ function RemoteShell() {
196
302
  const { isLeader, isPending, error } = useGingerRemote({
197
303
  channelName: "my-app-ginger",
198
304
  });
199
-
200
305
  return (
201
306
  <>
202
307
  {error && <p role="alert">{error}</p>}
@@ -205,86 +310,188 @@ function RemoteAwarePlayer() {
205
310
  </>
206
311
  );
207
312
  }
208
- ```
209
-
210
- Snapshots send the current queue order with **`isShuffled: false`** so followers do not re-randomize; the visible order matches the leader. **`claimLeadership()`** requests leadership (lexicographically smaller tab IDs win conflicts).
211
313
 
212
- `@lucaismyname/ginger/remote` also exports **`DEFAULT_REMOTE_CHANNEL_NAME`** and the **`RemoteMessage`** type if you need to share protocol constants or type your own channel helpers.
213
-
214
- ### Chromecast (`@lucaismyname/ginger/cast`)
215
-
216
- Loads the **Google Cast Web Sender** (CAF), exposes **`useGingerCast`** for session + **`loadMedia`** sync, and helpers **`loadCastFramework`**, **`trackToMediaInfo`**, **`guessContentTypeFromUrl`**. The default receiver is the **Default Media Receiver**; override with **`receiverApplicationId`**.
314
+ export function App() {
315
+ return (
316
+ <Ginger.Provider initialTracks={tracks}>
317
+ <RemoteShell />
318
+ {/* Transport controls still work in every tab */}
319
+ <Ginger.Control.PlayPause />
320
+ </Ginger.Provider>
321
+ );
322
+ }
323
+ ```
217
324
 
218
- **Platform:** Cast requires **HTTPS** in production (localhost is allowed for development). **`Track.fileUrl`** must be fetchable by the **Cast device** with correct **CORS**; avoid **mixed content**.
325
+ #### <a id="subpath-starter-cast"></a> `@lucaismyname/ginger/cast`
219
326
 
220
- **Avoid double playback:** render **`{!isCasting && <Ginger.Player />}`** so the browser does not decode the same URLs as the TV. Optional **`syncLocalAudio: "pause-mute"`** mutes the local `<audio>` while connected.
327
+ Cast needs **HTTPS** in production; use a real HTTPS `fileUrl` the receiver can fetch.
221
328
 
222
329
  ```tsx
223
330
  import { Ginger } from "@lucaismyname/ginger";
224
331
  import { useGingerCast } from "@lucaismyname/ginger/cast";
225
332
 
226
- function WithCast() {
333
+ const tracks = [{ title: "Demo", fileUrl: "https://your.cdn/your-audio.mp3" }];
334
+
335
+ function CastShell() {
227
336
  const { isCasting, requestSession, endSession, error } = useGingerCast();
228
337
  return (
229
338
  <>
230
339
  {error && <p role="alert">{error}</p>}
231
- <button type="button" onClick={() => void requestSession()}>Cast</button>
232
- <button type="button" onClick={endSession}>Stop</button>
340
+ <button type="button" onClick={() => void requestSession()}>
341
+ Cast
342
+ </button>
343
+ <button type="button" onClick={endSession}>
344
+ Stop casting
345
+ </button>
233
346
  {!isCasting && <Ginger.Player />}
234
347
  </>
235
348
  );
236
349
  }
237
- ```
238
350
 
239
- ### Crossfade (`@lucaismyname/ginger/crossfade`)
351
+ export function App() {
352
+ return (
353
+ <Ginger.Provider initialTracks={tracks}>
354
+ <CastShell />
355
+ <Ginger.Control.PlayPause />
356
+ </Ginger.Provider>
357
+ );
358
+ }
359
+ ```
240
360
 
241
- Adds a Web Audio crossfade graph for **overlap-based** transitions between outgoing and incoming media. This is distinct from the longer-term **gapless** work: crossfade overlaps two sources on purpose, while gapless aims for seamless adjacent track boundaries on a single playback path.
361
+ #### <a id="subpath-starter-crossfade"></a> `@lucaismyname/ginger/crossfade`
242
362
 
243
363
  ```tsx
364
+ import { Ginger } from "@lucaismyname/ginger";
244
365
  import { useGingerCrossfade } from "@lucaismyname/ginger/crossfade";
245
366
 
246
- function CrossfadeSetup() {
367
+ const tracks = [
368
+ { title: "A", fileUrl: "/your-audio-a.mp3" },
369
+ { title: "B", fileUrl: "/your-audio-b.mp3" },
370
+ ];
371
+
372
+ function CrossfadeReadout() {
247
373
  const { status, error } = useGingerCrossfade({
248
374
  enabled: true,
249
375
  durationMs: 1200,
250
376
  });
251
-
252
377
  return (
253
378
  <div>
254
- <p>Status: {status}</p>
379
+ <p>Crossfade: {status}</p>
255
380
  {error && <p role="alert">{error}</p>}
256
381
  </div>
257
382
  );
258
383
  }
384
+
385
+ export function App() {
386
+ return (
387
+ <Ginger.Provider initialTracks={tracks}>
388
+ <Ginger.Player />
389
+ <Ginger.Control.PlayPause />
390
+ <Ginger.Control.Next />
391
+ <CrossfadeReadout />
392
+ </Ginger.Provider>
393
+ );
394
+ }
259
395
  ```
260
396
 
261
- For lower-level integrations, the subpath also exports **`attachCrossfadeGraph`**, **`scheduleCrossfade`**, and **`teardownCrossfadeGraph`** plus the related graph/curve types. Like EQ and spatial audio, crossfade attaches to the active Ginger media graph and should be torn down when you unmount or switch playback strategies.
397
+ #### <a id="subpath-starter-experimental-gapless"></a> `@lucaismyname/ginger/experimental-gapless`
262
398
 
263
- ### Devtools (`@lucaismyname/ginger/devtools`)
399
+ Probe only; does **not** change playback.
400
+
401
+ ```tsx
402
+ import { Ginger } from "@lucaismyname/ginger";
403
+ import { useExperimentalGapless } from "@lucaismyname/ginger/experimental-gapless";
404
+
405
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
264
406
 
265
- A debugging overlay for inspecting and controlling Ginger audio players at runtime. Supports **multiple providers** on the same page via a global registry — place a single `<GingerDevtools />` anywhere in your app and it auto-discovers every active `<Ginger.Provider>`.
407
+ function GaplessProbe() {
408
+ const { supported, reason, gingerGaplessPlayback, preloadedTrackIds } =
409
+ useExperimentalGapless();
410
+ return (
411
+ <pre style={{ fontSize: 12 }}>
412
+ {JSON.stringify(
413
+ { supported, reason, gingerGaplessPlayback, preloadedTrackIds },
414
+ null,
415
+ 2,
416
+ )}
417
+ </pre>
418
+ );
419
+ }
420
+
421
+ export function App() {
422
+ return (
423
+ <Ginger.Provider initialTracks={tracks}>
424
+ <Ginger.Player />
425
+ <Ginger.Control.PlayPause />
426
+ <GaplessProbe />
427
+ </Ginger.Provider>
428
+ );
429
+ }
430
+ ```
431
+
432
+ #### <a id="subpath-starter-devtools"></a> `@lucaismyname/ginger/devtools`
433
+
434
+ `GingerDevtools` may sit **outside** `Ginger.Provider`; it discovers all registered players.
266
435
 
267
436
  ```tsx
437
+ import { Ginger } from "@lucaismyname/ginger";
268
438
  import { GingerDevtools } from "@lucaismyname/ginger/devtools";
269
439
 
270
- function App() {
440
+ const tracks = [{ title: "Demo", fileUrl: "/your-audio.mp3" }];
441
+
442
+ export function App() {
271
443
  return (
272
444
  <>
273
- <Ginger.Provider debugLabel="Main Player" initialTracks={tracks}>
274
- {/* ... */}
275
- </Ginger.Provider>
276
-
277
- <Ginger.Provider debugLabel="Ambient" initialTracks={ambientTracks}>
278
- {/* ... */}
445
+ <Ginger.Provider debugLabel="Main" initialTracks={tracks}>
446
+ <Ginger.Player />
447
+ <Ginger.Control.PlayPause />
279
448
  </Ginger.Provider>
280
-
281
- {/* Single devtools instance — discovers both providers */}
282
449
  <GingerDevtools />
283
450
  </>
284
451
  );
285
452
  }
286
453
  ```
287
454
 
455
+ ### Equalizer
456
+
457
+ **Full shell:** [Equalizer starter](#subpath-starter-equalizer) (above). The EQ and `useGingerLiveAnalyzer` share the same `AudioContext` and can be used together. EQ filters are inserted before the analyser in the Web Audio graph.
458
+
459
+ ### Spatial audio (`@lucaismyname/ginger/spatial`)
460
+
461
+ Inserts an HRTF **`PannerNode`** into the same Web Audio graph as the EQ and live analyser (one `MediaElementAudioSourceNode` per `<audio>`). **Full shell:** [Spatial starter](#subpath-starter-spatial). Use **`setListenerPosition`** and **`setPanningModel`** for runtime updates without rebuilding the graph.
462
+
463
+ ### Transcript (`@lucaismyname/ginger/transcript`)
464
+
465
+ Parse **SRT** and **WebVTT** captions and sync cues to playback time (podcasts, video-style transcripts). HTML tags in cue text are stripped. **Full shell:** [Transcript starter](#subpath-starter-transcript).
466
+
467
+ **`useGingerTranscriptSync`** mirrors **`useGingerLyricsSync`** but uses cue **start/end** ranges and exposes **`activeCues`** for overlapping captions. **`parseTranscriptAuto`** chooses VTT when the string starts with `WEBVTT`, otherwise SRT. Parse ahead of time with **`parseSrt`** / **`parseVtt`** when you do not need the hook.
468
+
469
+ ### Multi-tab sync (`@lucaismyname/ginger/remote`)
470
+
471
+ Elects a **leader** tab via **`BroadcastChannel`** and pushes **`INIT`** snapshots to followers so queue and transport settings stay aligned. Mount **`Ginger.Player`** only on the leader so a single `<audio>` element plays. **Full shell:** [Remote starter](#subpath-starter-remote).
472
+
473
+ Snapshots send the current queue order with **`isShuffled: false`** so followers do not re-randomize; the visible order matches the leader. **`claimLeadership()`** requests leadership (lexicographically smaller tab IDs win conflicts).
474
+
475
+ `@lucaismyname/ginger/remote` also exports **`DEFAULT_REMOTE_CHANNEL_NAME`** and the **`RemoteMessage`** type if you need to share protocol constants or type your own channel helpers.
476
+
477
+ ### Chromecast (`@lucaismyname/ginger/cast`)
478
+
479
+ Loads the **Google Cast Web Sender** (CAF), exposes **`useGingerCast`** for session + **`loadMedia`** sync, and helpers **`loadCastFramework`**, **`trackToMediaInfo`**, **`guessContentTypeFromUrl`**. The default receiver is the **Default Media Receiver**; override with **`receiverApplicationId`**.
480
+
481
+ **Platform:** Cast requires **HTTPS** in production (localhost is allowed for development). **`Track.fileUrl`** must be fetchable by the **Cast device** with correct **CORS**; avoid **mixed content**.
482
+
483
+ **Avoid double playback:** render **`{!isCasting && <Ginger.Player />}`** so the browser does not decode the same URLs as the TV. Optional **`syncLocalAudio: "pause-mute"`** mutes the local `<audio>` while connected. **Full shell:** [Cast starter](#subpath-starter-cast).
484
+
485
+ ### Crossfade (`@lucaismyname/ginger/crossfade`)
486
+
487
+ Adds a Web Audio crossfade graph for **overlap-based** transitions between outgoing and incoming media. This is distinct from the longer-term **gapless** work: crossfade overlaps two sources on purpose, while gapless aims for seamless adjacent track boundaries on a single playback path. **Full shell:** [Crossfade starter](#subpath-starter-crossfade).
488
+
489
+ For lower-level integrations, the subpath also exports **`attachCrossfadeGraph`**, **`scheduleCrossfade`**, and **`teardownCrossfadeGraph`** plus the related graph/curve types. Like EQ and spatial audio, crossfade attaches to the active Ginger media graph and should be torn down when you unmount or switch playback strategies.
490
+
491
+ ### Devtools (`@lucaismyname/ginger/devtools`)
492
+
493
+ A debugging overlay for inspecting and controlling Ginger audio players at runtime. Supports **multiple providers** on the same page via a global registry — place a single `<GingerDevtools />` anywhere in your app and it auto-discovers every active `<Ginger.Provider>`. **Full shell:** [Devtools starter](#subpath-starter-devtools).
494
+
288
495
  The overlay provides **bidirectional controls**: you can play/pause, seek, change volume, adjust playback rate, toggle repeat/shuffle, and click tracks in the queue — all changes apply to the live player instantly. State changes from the player are reflected in the devtools panel in real-time.
289
496
 
290
497
  The panel uses Tailwind CSS via CDN (injected on mount, removed on unmount) and renders in a portal so it does not interfere with your app's layout or styles. Use the `debugLabel` prop on `<Ginger.Provider>` to give each player a human-readable tab name.
@@ -292,7 +499,7 @@ The panel uses Tailwind CSS via CDN (injected on mount, removed on unmount) and
292
499
  ### Experimental Notice
293
500
 
294
501
  `@lucaismyname/ginger/experimental-gapless` is intentionally non-production.
295
- It currently provides capability metadata only and does not alter playback behavior.
502
+ It currently provides capability metadata only and does not alter playback behavior. **Full shell:** [Experimental gapless starter](#subpath-starter-experimental-gapless).
296
503
 
297
504
  ## Release Process
298
505
 
@@ -1344,7 +1551,7 @@ Additional entrypoints:
1344
1551
  - `@lucaismyname/ginger/experimental-gapless`
1345
1552
  - `@lucaismyname/ginger/devtools`
1346
1553
 
1347
- See [Subpath Exports](#subpath-exports) for **`spatial`**, **`transcript`**, **`remote`**, **`cast`**, and **`devtools`** usage. `experimental-gapless` is explicitly non-production and does not alter core playback.
1554
+ See [Subpath Exports](#subpath-exports) for the import list, per-feature notes, and **[copy-paste starters](#subpath-copy-paste-starters)** for each subpath. `experimental-gapless` is explicitly non-production and does not alter core playback.
1348
1555
 
1349
1556
  ## Notes
1350
1557
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucaismyname/ginger",
3
- "version": "0.0.60",
3
+ "version": "0.0.61",
4
4
  "description": "A headless react audio-player component primitive",
5
5
  "type": "module",
6
6
  "sideEffects": false,