apm-react-audio-player 1.0.27 → 1.1.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 CHANGED
@@ -21,7 +21,21 @@ The library was designed to add a audio player to a body of a story which will n
21
21
  - [Props](#props)
22
22
  - [Example](#example)
23
23
 
24
- [License] (#License)
24
+ [Breaking Changes](#breaking-changes)
25
+
26
+ [HLS Support](#hls-support)
27
+ - [Browser Compatibility](#browser-compatibility)
28
+ - [HLS Usage Examples](#hls-usage-examples)
29
+ - [Live Stream Detection](#live-stream-detection)
30
+ - [Supported Audio Formats](#supported-audio-formats)
31
+
32
+ [Examples](#examples)
33
+ - [Running the Examples](#running-the-examples)
34
+ - [Building the Examples](#building-the-examples)
35
+
36
+ [Publishing](#publishing)
37
+
38
+ [License](#license)
25
39
 
26
40
 
27
41
  ## Dependencies
@@ -67,7 +81,7 @@ See the [audio tag documentation](https://developer.mozilla.org/en-US/docs/Web/H
67
81
  Prop | Type | Default | Notes
68
82
  --- | --- | --- | ---
69
83
  `title` | String | *empty string* | The title of the audio track
70
- `audioSrc` | String | *empty string* | The source URL of the audio file
84
+ `audioSrc` | String or Array | *empty string* | **String:** Single audio source URL<br>**Array:** Multiple source URLs for progressive enhancement (e.g., `['stream.m3u8', 'audio.aac', 'audio.mp3']`)
71
85
  `description` | String | *empty string* | The description of the audio track
72
86
  `audioPlayerRef` | Object | *empty object* | A ref object for the audio player
73
87
  `progressBarRef` | Object | *empty object* | A ref object for the progress bar
@@ -76,7 +90,8 @@ Prop | Type | Default | Notes
76
90
  `isPlaying` | Boolean | false | Whether the audio is currently playing
77
91
  `isMuted` | Boolean | false | Whether the audio is currently muted
78
92
  `toggleMute` | Function | --- | A function to toggle the mute state
79
- `volumeCtrl` | Function | --- | A function to control the volume
93
+ `volumeCtrl` | Boolean | false | Whether to show volume controls
94
+ `volumeControl` | Function | --- | A function to handle volume changes
80
95
  `currentTime` | Number | null | The current time of the audio track
81
96
  `duration` | Number | null | The duration of the audio track
82
97
  `rewindControl` | Function | --- | A function to rewind the audio track
@@ -133,7 +148,8 @@ const Example = () => {
133
148
  duration={duration}
134
149
  isAudioFinished={isFinishedPlaying}
135
150
  toggleMute={toggleMute}
136
- volumeCtrl={volumeControl}
151
+ volumeCtrl={true}
152
+ volumeControl={volumeControl}
137
153
  changePlayerCurrentTime={changePlayerCurrentTime}
138
154
  rewindControl={rewindControl}
139
155
  forwardControl={forwardControl}
@@ -145,6 +161,169 @@ const Example = () => {
145
161
  }
146
162
  ```
147
163
 
164
+ ## Breaking Changes
165
+
166
+ ### Version 1.0.29+
167
+
168
+ **Removed `isLive` prop:** The `isLive` prop has been removed from `ReactAudioPlayerInner`. Live stream detection is now **automatic** based on the HTML5 audio `duration` property.
169
+
170
+ - **Old behavior:** You had to manually pass `isLive={true}` for live streams
171
+ - **New behavior:** The player automatically detects live streams when `duration === Infinity`
172
+
173
+ **Migration:**
174
+ ```javascript
175
+ // ❌ Old - Remove the isLive prop
176
+ <ReactAudioPlayerInner
177
+ audioSrc="https://example.com/live.m3u8"
178
+ isLive={true} // <-- Remove this
179
+ // ... other props
180
+ />
181
+
182
+ // ✅ New - Detection is automatic
183
+ <ReactAudioPlayerInner
184
+ audioSrc="https://example.com/live.m3u8"
185
+ // ... other props
186
+ />
187
+ ```
188
+
189
+ The UI will automatically adapt based on the audio metadata - no manual configuration needed.
190
+
191
+ ## HLS Support
192
+
193
+ The audio player now supports HLS (HTTP Live Streaming) for both live streams and pre-recorded content using native browser support. The player uses HTML5 `<source>` tags for progressive enhancement, allowing browsers to fall back to alternate formats if HLS is not supported.
194
+
195
+ ### Browser Compatibility
196
+
197
+ | Browser | HLS Support | Fallback Behavior |
198
+ |---------|------------|-------------------|
199
+ | Safari 11+ | Native ✓ | N/A |
200
+ | iOS Safari | Native ✓ | N/A |
201
+ | Chrome 142+ | Native ✓ | N/A |
202
+ | Edge 142+ | Native ✓ | N/A |
203
+ | Mobile Chrome/Android | Native ✓ | N/A |
204
+ | Chrome <142 | None | Automatically uses AAC/MP3 |
205
+ | Firefox | None | Automatically uses AAC/MP3 |
206
+ | Edge <142 | None | Automatically uses AAC/MP3 |
207
+ | Safari <11 | None | Automatically uses AAC/MP3 |
208
+
209
+ **Note:** Chrome and Edge added native HLS support in version 142 (December 2024). Older versions will automatically skip HLS sources and use alternative formats.
210
+
211
+ ### HLS Usage Examples
212
+
213
+ **See `examples/hls-example.jsx` for a complete working example with multiple HLS scenarios.**
214
+
215
+ #### Basic HLS with Progressive Enhancement
216
+
217
+ Provide multiple source formats to ensure compatibility across all browsers:
218
+
219
+ ```javascript
220
+ <ReactAudioPlayerInner
221
+ title="Podcast Episode"
222
+ audioSrc={[
223
+ 'https://example.com/episode.m3u8',
224
+ 'https://example.com/episode.aac',
225
+ 'https://example.com/episode.mp3'
226
+ ]}
227
+ // ... other props
228
+ />
229
+ ```
230
+
231
+ Browsers will try sources in order:
232
+ 1. Modern browsers (Safari, Chrome 142+, Edge 142+) will play the HLS stream
233
+ 2. Older browsers will skip the .m3u8 and fall back to AAC or MP3
234
+
235
+ #### Live Radio Stream
236
+
237
+ ```javascript
238
+ <ReactAudioPlayerInner
239
+ title="Live Radio"
240
+ audioSrc={['https://stream.example.com/live.m3u8']}
241
+ // ... other props
242
+ />
243
+ ```
244
+
245
+ #### Single Source (Backward Compatible)
246
+
247
+ The player maintains backward compatibility with the original single-source API:
248
+
249
+ ```javascript
250
+ <ReactAudioPlayerInner
251
+ title="Audio Track"
252
+ audioSrc="https://example.com/audio.mp3"
253
+ // ... other props
254
+ />
255
+ ```
256
+
257
+ ### Live Stream Detection
258
+
259
+ The player automatically detects live streams using the HTML5 audio `duration` property:
260
+
261
+ - **Live streams:** `duration === Infinity`
262
+ - Timeline/seek controls are hidden
263
+ - Rewind/forward buttons are hidden
264
+ - "On Air" label is displayed
265
+
266
+ - **Pre-recorded audio:** `duration` is a finite number
267
+ - Timeline/seek controls are shown
268
+ - Rewind/forward buttons are shown
269
+ - Duration is displayed
270
+
271
+ No additional props are needed - the player automatically adapts its UI based on whether the content is live or pre-recorded.
272
+
273
+ ### Supported Audio Formats
274
+
275
+ The player automatically detects MIME types from file extensions:
276
+
277
+ | Extension | MIME Type |
278
+ |-----------|-----------|
279
+ | `.m3u8` | `application/x-mpegURL` |
280
+ | `.mp3` | `audio/mpeg` |
281
+ | `.aac` | `audio/aac` |
282
+ | `.ogg` | `audio/ogg` |
283
+ | `.wav` | `audio/wav` |
284
+
285
+ ## Examples
286
+
287
+ The `examples/` directory contains working demonstrations of the audio player with HLS support.
288
+
289
+ ### Running the Examples
290
+
291
+ **Option 1: Using the serve script (recommended)**
292
+ ```bash
293
+ # Start local server and open examples in browser
294
+ yarn serve:examples
295
+
296
+ # Or using npm
297
+ npm run serve:examples
298
+ ```
299
+
300
+ This will start a local server on port 8000 and automatically open the examples page in your browser.
301
+
302
+ **Option 2: Open HTML file directly**
303
+ 1. Open `examples/index.html` in a web browser
304
+ 2. The page includes three examples:
305
+ - HLS with progressive enhancement (fallback sources)
306
+ - Live HLS stream (MPR Current)
307
+ - Regular MP3 (backward compatible)
308
+
309
+ ### Building the Examples
310
+
311
+ The examples use a pre-built bundle (`examples/bundle.js`). If you modify the source code and want to rebuild the examples:
312
+
313
+ ```bash
314
+ # Rebuild the examples bundle
315
+ yarn build:examples
316
+
317
+ # Or using npm
318
+ npm run build:examples
319
+ ```
320
+
321
+ **Files:**
322
+ - `examples/index.html` - Main example page with CSS and layout
323
+ - `examples/hls-example.jsx` - React components demonstrating HLS features
324
+ - `examples/bundle.js` - Compiled bundle (auto-generated, don't edit directly)
325
+ - `examples/hls-test.html` - Additional test page for development
326
+
148
327
  ## Publishing
149
328
 
150
329
  1. Ensure every merge request and/or change to `apm-react-audio-player` should always come with an updated version (ex. 1.0.17 to 1.0.18) in the package.json.
package/dist/index.js CHANGED
@@ -27,15 +27,15 @@ function _iterableToArrayLimit(r, l) {
27
27
  i,
28
28
  u,
29
29
  a = [],
30
- f = !0,
31
- o = !1;
30
+ f = true,
31
+ o = false;
32
32
  try {
33
33
  if (i = (t = t.call(r)).next, 0 === l) {
34
34
  if (Object(t) !== t) return;
35
35
  f = !1;
36
36
  } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
37
37
  } catch (r) {
38
- o = !0, n = r;
38
+ o = true, n = r;
39
39
  } finally {
40
40
  try {
41
41
  if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
@@ -83,32 +83,41 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
83
83
  _useState10 = _slicedToArray(_useState9, 2),
84
84
  isMuted = _useState10[0],
85
85
  setIsMuted = _useState10[1];
86
- var isStream = audioRef.current && audioRef.current.currentSrc.includes('stream');
86
+ var isStream = audioRef.current && audioRef.current.duration === Infinity;
87
87
  React.useEffect(function () {
88
88
  if (currentTime === Number(duration)) {
89
89
  // restart()
90
90
  setIsFinishedPlaying(true);
91
91
  }
92
92
  }, [currentTime]);
93
+ React.useEffect(function () {
94
+ // Cancel RAF loop if duration changes to Infinity (live stream metadata loaded)
95
+ if (duration === Infinity && animationRef.current) {
96
+ window.cancelAnimationFrame(animationRef.current);
97
+ }
98
+ }, [duration]);
93
99
  var onLoadedMetadata = function onLoadedMetadata() {
94
- var _audioRef$current;
100
+ if (!audioRef.current) return;
95
101
  var seconds = Math.floor(audioRef.current.duration);
96
102
  setDuration(seconds);
97
- if (!(audioRef !== null && audioRef !== void 0 && (_audioRef$current = audioRef.current) !== null && _audioRef$current !== void 0 && _audioRef$current.currentSrc.includes('stream'))) {
103
+ if (audioRef.current.duration !== Infinity && progressBarRef.current) {
98
104
  progressBarRef.current.max = seconds;
99
105
  }
100
106
  };
101
107
  var updateCurrentTime = function updateCurrentTime() {
102
- setCurrentTime(progressBarRef.current.value);
108
+ if (progressBarRef.current) {
109
+ setCurrentTime(progressBarRef.current.value);
110
+ }
103
111
  };
104
112
  var _whilePlaying = function whilePlaying() {
105
- var _audioRef$current2;
106
- if (!(audioRef !== null && audioRef !== void 0 && (_audioRef$current2 = audioRef.current) !== null && _audioRef$current2 !== void 0 && _audioRef$current2.currentSrc.includes('stream'))) {
107
- // isStream isn't correct here
108
-
113
+ // Guard against null refs (can happen when timeline unmounts for live streams)
114
+ if (!progressBarRef.current || !audioRef.current) {
115
+ return;
116
+ }
117
+ if (audioRef.current.duration !== Infinity) {
109
118
  progressBarRef.current.value = Math.floor(audioRef.current.currentTime);
119
+ progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
110
120
  }
111
- progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
112
121
  updateCurrentTime();
113
122
 
114
123
  // when you reach the end of the song
@@ -135,8 +144,10 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
135
144
  setIsPlaying(true);
136
145
  setIsFinishedPlaying(false);
137
146
  audioRef.current.play();
138
- if (!audioRef.current.currentSrc.includes('stream')) {
139
- // isStream isn't correctly set here
147
+
148
+ // Only start RAF loop for non-live streams with valid duration
149
+ var dur = audioRef.current.duration;
150
+ if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
140
151
  animationRef.current = window.requestAnimationFrame(_whilePlaying);
141
152
  }
142
153
  };
@@ -158,24 +169,29 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
158
169
  }
159
170
  };
160
171
  var changePlayerCurrentTime = function changePlayerCurrentTime() {
172
+ if (!progressBarRef.current || !audioRef.current) return;
161
173
  audioRef.current.currentTime = progressBarRef.current.value;
162
174
  setCurrentTime(progressBarRef.current.value);
163
175
  progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
164
176
  };
165
177
  var changeRange = function changeRange() {
178
+ if (!progressBarRef.current || !audioRef.current) return;
166
179
  audioRef.current.currentTime = progressBarRef.current.value;
167
180
  updateCurrentTime();
168
181
  changePlayerCurrentTime();
169
182
  };
170
183
  var rewindControl = function rewindControl() {
184
+ if (!progressBarRef.current) return;
171
185
  progressBarRef.current.value = Number(progressBarRef.current.value) - 15;
172
186
  changeRange();
173
187
  };
174
188
  var forwardControl = function forwardControl() {
189
+ if (!progressBarRef.current) return;
175
190
  progressBarRef.current.value = Number(progressBarRef.current.value) + 15;
176
191
  changeRange();
177
192
  };
178
193
  var volumeControl = function volumeControl(e) {
194
+ if (!audioRef.current) return;
179
195
  var value = e.target.value;
180
196
  var volume = value / 100;
181
197
  audioRef.current.volume = volume;
@@ -259,6 +275,24 @@ var Pause = function Pause() {
259
275
  }));
260
276
  };
261
277
 
278
+ var getTypeFromExtension = function getTypeFromExtension(url) {
279
+ var extension = url.split('.').pop().split('?')[0];
280
+ switch (extension) {
281
+ case 'm3u8':
282
+ return 'application/x-mpegURL';
283
+ case 'aac':
284
+ return 'audio/aac';
285
+ case 'mp3':
286
+ return 'audio/mpeg';
287
+ case 'ogg':
288
+ return 'audio/ogg';
289
+ case 'wav':
290
+ return 'audio/wav';
291
+ default:
292
+ return undefined;
293
+ // Let browser auto-detect
294
+ }
295
+ };
262
296
  var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
263
297
  var _props$audioPlayerRef, _props$progressBarRef;
264
298
  // references
@@ -271,7 +305,6 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
271
305
  volumeCtrl = props.volumeCtrl,
272
306
  playBtnClass = props.playBtnClass,
273
307
  customHtml = props.customHtml,
274
- isLive = props.isLive,
275
308
  onLoadedMetadata = props.onLoadedMetadata,
276
309
  calculateTime = props.calculateTime,
277
310
  togglePlaying = props.togglePlaying,
@@ -289,16 +322,46 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
289
322
  prefix = props.prefix;
290
323
  var audioDuration = duration && !isNaN(duration) && calculateTime(duration);
291
324
  var formatDuration = duration && !isNaN(duration) && audioDuration && formatCalculateTime(audioDuration);
325
+
326
+ // Reload audio when audioSrc changes
327
+ // Use JSON.stringify to handle array comparisons by value instead of reference
328
+ React.useEffect(function () {
329
+ if (audioPlayerRef.current && audioSrc) {
330
+ try {
331
+ audioPlayerRef.current.load();
332
+ } catch (err) {
333
+ console.warn('Failed to reload audio source:', err);
334
+ }
335
+ }
336
+ }, [JSON.stringify(audioSrc)]);
337
+
338
+ // Set initial volume to 100%
339
+ React.useEffect(function () {
340
+ if (audioPlayerRef.current) {
341
+ audioPlayerRef.current.volume = 1.0;
342
+ }
343
+ }, []);
344
+
345
+ // Helper to determine if controls should show
346
+ var showControls = duration !== Infinity && duration !== undefined && !isNaN(duration) && isFinite(duration);
292
347
  return audioSrc && /*#__PURE__*/React.createElement("div", {
293
348
  className: "audioPlayer",
294
349
  style: customStyles && customStyles.audioPlayer
295
350
  }, /*#__PURE__*/React.createElement("audio", {
296
351
  ref: audioPlayerRef,
297
- src: audioSrc,
298
352
  preload: "none",
299
353
  onLoadedMetadata: onLoadedMetadata,
300
354
  muted: isMuted
301
- }), /*#__PURE__*/React.createElement("div", {
355
+ }, Array.isArray(audioSrc) ? audioSrc.map(function (src, index) {
356
+ return /*#__PURE__*/React.createElement("source", {
357
+ key: index,
358
+ src: src,
359
+ type: getTypeFromExtension(src)
360
+ });
361
+ }) : audioSrc ? /*#__PURE__*/React.createElement("source", {
362
+ src: audioSrc,
363
+ type: getTypeFromExtension(audioSrc)
364
+ }) : null, "Your browser does not support the audio element."), /*#__PURE__*/React.createElement("div", {
302
365
  className: "player-layout"
303
366
  }, volumeCtrl && /*#__PURE__*/React.createElement("div", {
304
367
  className: "player-controls-secondary-outer"
@@ -324,6 +387,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
324
387
  className: "player-volume-progress",
325
388
  min: 0,
326
389
  max: 100,
390
+ defaultValue: 100,
327
391
  "aria-hidden": "true",
328
392
  "aria-valuetext": "100%",
329
393
  onChange: function onChange(e) {
@@ -336,7 +400,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
336
400
  className: "player-volume-label"
337
401
  }, "Volume")), /*#__PURE__*/React.createElement("div", {
338
402
  className: "player-controls"
339
- }, !isLive && /*#__PURE__*/React.createElement("div", {
403
+ }, showControls && /*#__PURE__*/React.createElement("div", {
340
404
  className: "player-backward-forward-controls"
341
405
  }, /*#__PURE__*/React.createElement("button", {
342
406
  onClick: rewindControl
@@ -350,14 +414,14 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
350
414
  className: playBtnClass,
351
415
  style: customStyles && customStyles.playPause,
352
416
  id: "playbutton"
353
- }, isPlaying ? /*#__PURE__*/React.createElement(Pause, null) : /*#__PURE__*/React.createElement(Play, null))), !isLive && /*#__PURE__*/React.createElement("div", {
417
+ }, isPlaying ? /*#__PURE__*/React.createElement(Pause, null) : /*#__PURE__*/React.createElement(Play, null))), showControls && /*#__PURE__*/React.createElement("div", {
354
418
  className: "player-backward-forward-controls"
355
419
  }, /*#__PURE__*/React.createElement("button", {
356
420
  onClick: forwardControl
357
421
  }, /*#__PURE__*/React.createElement("img", {
358
422
  src: "/img/icon-forward-15.svg",
359
423
  alt: "Forward 15 seconds"
360
- })))), !isLive && /*#__PURE__*/React.createElement("div", {
424
+ })))), showControls && /*#__PURE__*/React.createElement("div", {
361
425
  className: "player-timeline"
362
426
  }, /*#__PURE__*/React.createElement("div", {
363
427
  className: "player-currentTime"
@@ -378,7 +442,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
378
442
  className: "player-content"
379
443
  }, customHtml && customHtml, /*#__PURE__*/React.createElement("div", {
380
444
  className: "player-audio-type type-sm"
381
- }, isLive ? /*#__PURE__*/React.createElement("div", {
445
+ }, duration === Infinity ? /*#__PURE__*/React.createElement("div", {
382
446
  className: "player-live-label"
383
447
  }, prefix ? prefix : 'On Air') : /*#__PURE__*/React.createElement("div", {
384
448
  className: "player-label"
@@ -0,0 +1,497 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+
3
+ function _arrayLikeToArray(r, a) {
4
+ (null == a || a > r.length) && (a = r.length);
5
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
6
+ return n;
7
+ }
8
+ function _arrayWithHoles(r) {
9
+ if (Array.isArray(r)) return r;
10
+ }
11
+ function _extends() {
12
+ return _extends = Object.assign ? Object.assign.bind() : function (n) {
13
+ for (var e = 1; e < arguments.length; e++) {
14
+ var t = arguments[e];
15
+ for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
16
+ }
17
+ return n;
18
+ }, _extends.apply(null, arguments);
19
+ }
20
+ function _iterableToArrayLimit(r, l) {
21
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
22
+ if (null != t) {
23
+ var e,
24
+ n,
25
+ i,
26
+ u,
27
+ a = [],
28
+ f = true,
29
+ o = false;
30
+ try {
31
+ if (i = (t = t.call(r)).next, 0 === l) {
32
+ if (Object(t) !== t) return;
33
+ f = !1;
34
+ } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
35
+ } catch (r) {
36
+ o = true, n = r;
37
+ } finally {
38
+ try {
39
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
40
+ } finally {
41
+ if (o) throw n;
42
+ }
43
+ }
44
+ return a;
45
+ }
46
+ }
47
+ function _nonIterableRest() {
48
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
49
+ }
50
+ function _slicedToArray(r, e) {
51
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
52
+ }
53
+ function _unsupportedIterableToArray(r, a) {
54
+ if (r) {
55
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
56
+ var t = {}.toString.call(r).slice(8, -1);
57
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
58
+ }
59
+ }
60
+
61
+ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtrl) {
62
+ var initialDuration = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined;
63
+ var _useState = useState(false),
64
+ _useState2 = _slicedToArray(_useState, 2),
65
+ isPlaying = _useState2[0],
66
+ setIsPlaying = _useState2[1];
67
+ var _useState3 = useState(initialDuration),
68
+ _useState4 = _slicedToArray(_useState3, 2),
69
+ duration = _useState4[0],
70
+ setDuration = _useState4[1];
71
+ var _useState5 = useState(0),
72
+ _useState6 = _slicedToArray(_useState5, 2),
73
+ currentTime = _useState6[0],
74
+ setCurrentTime = _useState6[1];
75
+ var _useState7 = useState(false),
76
+ _useState8 = _slicedToArray(_useState7, 2),
77
+ isFinishedPlaying = _useState8[0],
78
+ setIsFinishedPlaying = _useState8[1];
79
+ var animationRef = useRef(); // reference the animation
80
+ var _useState9 = useState(false),
81
+ _useState10 = _slicedToArray(_useState9, 2),
82
+ isMuted = _useState10[0],
83
+ setIsMuted = _useState10[1];
84
+ var isStream = audioRef.current && audioRef.current.duration === Infinity;
85
+ useEffect(function () {
86
+ if (currentTime === Number(duration)) {
87
+ // restart()
88
+ setIsFinishedPlaying(true);
89
+ }
90
+ }, [currentTime]);
91
+ useEffect(function () {
92
+ // Cancel RAF loop if duration changes to Infinity (live stream metadata loaded)
93
+ if (duration === Infinity && animationRef.current) {
94
+ window.cancelAnimationFrame(animationRef.current);
95
+ }
96
+ }, [duration]);
97
+ var onLoadedMetadata = function onLoadedMetadata() {
98
+ if (!audioRef.current) return;
99
+ var seconds = Math.floor(audioRef.current.duration);
100
+ setDuration(seconds);
101
+ if (audioRef.current.duration !== Infinity && progressBarRef.current) {
102
+ progressBarRef.current.max = seconds;
103
+ }
104
+ };
105
+ var updateCurrentTime = function updateCurrentTime() {
106
+ if (progressBarRef.current) {
107
+ setCurrentTime(progressBarRef.current.value);
108
+ }
109
+ };
110
+ var _whilePlaying = function whilePlaying() {
111
+ // Guard against null refs (can happen when timeline unmounts for live streams)
112
+ if (!progressBarRef.current || !audioRef.current) {
113
+ return;
114
+ }
115
+ if (audioRef.current.duration !== Infinity) {
116
+ progressBarRef.current.value = Math.floor(audioRef.current.currentTime);
117
+ progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
118
+ }
119
+ updateCurrentTime();
120
+
121
+ // when you reach the end of the song
122
+ if (!isStream && progressBarRef.current.value === duration) {
123
+ // restart()
124
+ setIsFinishedPlaying(true);
125
+ return;
126
+ }
127
+ animationRef.current = window.requestAnimationFrame(_whilePlaying);
128
+ };
129
+ var pause = function pause() {
130
+ setIsPlaying(false);
131
+ audioRef.current.pause();
132
+ window.cancelAnimationFrame(animationRef.current);
133
+ };
134
+
135
+ // const restart = () => {
136
+ // progressBarRef.current.value = 0
137
+ // updateCurrentTime()
138
+ // pause()
139
+ // }
140
+
141
+ var play = function play() {
142
+ setIsPlaying(true);
143
+ setIsFinishedPlaying(false);
144
+ audioRef.current.play();
145
+
146
+ // Only start RAF loop for non-live streams with valid duration
147
+ var dur = audioRef.current.duration;
148
+ if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
149
+ animationRef.current = window.requestAnimationFrame(_whilePlaying);
150
+ }
151
+ };
152
+ var toggleMute = function toggleMute() {
153
+ setIsMuted(!isMuted);
154
+ };
155
+ var calculateTime = function calculateTime(secs) {
156
+ var minutes = Math.floor(secs / 60);
157
+ var returnedMinutes = minutes < 10 ? "0".concat(minutes) : "".concat(minutes);
158
+ var seconds = Math.floor(secs % 60);
159
+ var returnedSeconds = seconds < 10 ? "0".concat(seconds) : "".concat(seconds);
160
+ return "".concat(returnedMinutes, ":").concat(returnedSeconds);
161
+ };
162
+ var togglePlaying = function togglePlaying() {
163
+ if (!isPlaying) {
164
+ play();
165
+ } else {
166
+ pause();
167
+ }
168
+ };
169
+ var changePlayerCurrentTime = function changePlayerCurrentTime() {
170
+ if (!progressBarRef.current || !audioRef.current) return;
171
+ audioRef.current.currentTime = progressBarRef.current.value;
172
+ setCurrentTime(progressBarRef.current.value);
173
+ progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
174
+ };
175
+ var changeRange = function changeRange() {
176
+ if (!progressBarRef.current || !audioRef.current) return;
177
+ audioRef.current.currentTime = progressBarRef.current.value;
178
+ updateCurrentTime();
179
+ changePlayerCurrentTime();
180
+ };
181
+ var rewindControl = function rewindControl() {
182
+ if (!progressBarRef.current) return;
183
+ progressBarRef.current.value = Number(progressBarRef.current.value) - 15;
184
+ changeRange();
185
+ };
186
+ var forwardControl = function forwardControl() {
187
+ if (!progressBarRef.current) return;
188
+ progressBarRef.current.value = Number(progressBarRef.current.value) + 15;
189
+ changeRange();
190
+ };
191
+ var volumeControl = function volumeControl(e) {
192
+ if (!audioRef.current) return;
193
+ var value = e.target.value;
194
+ var volume = value / 100;
195
+ audioRef.current.volume = volume;
196
+ };
197
+ var formatCalculateTime = function formatCalculateTime(timeString) {
198
+ var toString = String(timeString);
199
+ if (toString.split(':').length === 3) {
200
+ var _toString$split = toString.split(':'),
201
+ _toString$split2 = _slicedToArray(_toString$split, 3),
202
+ hours = _toString$split2[0],
203
+ minutes = _toString$split2[1],
204
+ seconds = _toString$split2[2];
205
+ return "".concat(parseInt(hours), "hr ").concat(parseInt(minutes), "min ").concat(parseInt(seconds), "sec");
206
+ } else if (toString.split(':').length === 2) {
207
+ var _toString$split3 = toString.split(':'),
208
+ _toString$split4 = _slicedToArray(_toString$split3, 2),
209
+ _minutes = _toString$split4[0],
210
+ _seconds = _toString$split4[1];
211
+ return "".concat(parseInt(_minutes), "min ").concat(parseInt(_seconds), "sec");
212
+ }
213
+ };
214
+ return {
215
+ onLoadedMetadata: onLoadedMetadata,
216
+ calculateTime: calculateTime,
217
+ togglePlaying: togglePlaying,
218
+ changePlayerCurrentTime: changePlayerCurrentTime,
219
+ rewindControl: rewindControl,
220
+ forwardControl: forwardControl,
221
+ play: play,
222
+ pause: pause,
223
+ isPlaying: isPlaying,
224
+ isFinishedPlaying: isFinishedPlaying,
225
+ currentTime: currentTime,
226
+ duration: duration,
227
+ volumeCtrl: volumeCtrl,
228
+ isMuted: isMuted,
229
+ volumeControl: volumeControl,
230
+ toggleMute: toggleMute,
231
+ formatCalculateTime: formatCalculateTime
232
+ };
233
+ };
234
+
235
+ var Play = function Play() {
236
+ return /*#__PURE__*/React.createElement("svg", {
237
+ stroke: "currentColor",
238
+ fill: "currentColor",
239
+ strokeWidth: "0",
240
+ viewBox: "0 0 448 512",
241
+ className: "play",
242
+ height: "1em",
243
+ width: "1em",
244
+ xmlns: "http://www.w3.org/2000/svg",
245
+ role: "img",
246
+ "aria-labelledby": "play playButton"
247
+ }, /*#__PURE__*/React.createElement("title", {
248
+ id: "play"
249
+ }, "Play"), /*#__PURE__*/React.createElement("desc", {
250
+ id: "playButton"
251
+ }, "Play Button"), /*#__PURE__*/React.createElement("path", {
252
+ d: "M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"
253
+ }));
254
+ };
255
+
256
+ var Pause = function Pause() {
257
+ return /*#__PURE__*/React.createElement("svg", {
258
+ stroke: "currentColor",
259
+ fill: "currentColor",
260
+ strokeWidth: "0",
261
+ viewBox: "0 0 448 512",
262
+ height: "1em",
263
+ width: "1em",
264
+ xmlns: "http://www.w3.org/2000/svg",
265
+ role: "img",
266
+ "aria-labelledby": "pause pauseButton"
267
+ }, /*#__PURE__*/React.createElement("title", {
268
+ id: "pause"
269
+ }, "Pause"), /*#__PURE__*/React.createElement("desc", {
270
+ id: "pauseButton"
271
+ }, "Pause Button"), /*#__PURE__*/React.createElement("path", {
272
+ d: "M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z"
273
+ }));
274
+ };
275
+
276
+ var getTypeFromExtension = function getTypeFromExtension(url) {
277
+ var extension = url.split('.').pop().split('?')[0];
278
+ switch (extension) {
279
+ case 'm3u8':
280
+ return 'application/x-mpegURL';
281
+ case 'aac':
282
+ return 'audio/aac';
283
+ case 'mp3':
284
+ return 'audio/mpeg';
285
+ case 'ogg':
286
+ return 'audio/ogg';
287
+ case 'wav':
288
+ return 'audio/wav';
289
+ default:
290
+ return undefined;
291
+ // Let browser auto-detect
292
+ }
293
+ };
294
+ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
295
+ var _props$audioPlayerRef, _props$progressBarRef;
296
+ // references
297
+ var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : useRef(); // reference our audio component
298
+ var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : useRef(); // reference our progress bar
299
+
300
+ var customStyles = props ? props.style : '';
301
+ var title = props.title,
302
+ audioSrc = props.audioSrc,
303
+ volumeCtrl = props.volumeCtrl,
304
+ playBtnClass = props.playBtnClass,
305
+ customHtml = props.customHtml,
306
+ onLoadedMetadata = props.onLoadedMetadata,
307
+ calculateTime = props.calculateTime,
308
+ togglePlaying = props.togglePlaying,
309
+ changePlayerCurrentTime = props.changePlayerCurrentTime,
310
+ isPlaying = props.isPlaying,
311
+ currentTime = props.currentTime,
312
+ duration = props.duration,
313
+ volumeControl = props.volumeControl,
314
+ toggleMute = props.toggleMute,
315
+ isMuted = props.isMuted,
316
+ formatCalculateTime = props.formatCalculateTime,
317
+ rewindControl = props.rewindControl,
318
+ forwardControl = props.forwardControl,
319
+ subtitle = props.subtitle,
320
+ prefix = props.prefix;
321
+ var audioDuration = duration && !isNaN(duration) && calculateTime(duration);
322
+ var formatDuration = duration && !isNaN(duration) && audioDuration && formatCalculateTime(audioDuration);
323
+
324
+ // Reload audio when audioSrc changes
325
+ // Use JSON.stringify to handle array comparisons by value instead of reference
326
+ useEffect(function () {
327
+ if (audioPlayerRef.current && audioSrc) {
328
+ try {
329
+ audioPlayerRef.current.load();
330
+ } catch (err) {
331
+ console.warn('Failed to reload audio source:', err);
332
+ }
333
+ }
334
+ }, [JSON.stringify(audioSrc)]);
335
+
336
+ // Set initial volume to 100%
337
+ useEffect(function () {
338
+ if (audioPlayerRef.current) {
339
+ audioPlayerRef.current.volume = 1.0;
340
+ }
341
+ }, []);
342
+
343
+ // Helper to determine if controls should show
344
+ var showControls = duration !== Infinity && duration !== undefined && !isNaN(duration) && isFinite(duration);
345
+ return audioSrc && /*#__PURE__*/React.createElement("div", {
346
+ className: "audioPlayer",
347
+ style: customStyles && customStyles.audioPlayer
348
+ }, /*#__PURE__*/React.createElement("audio", {
349
+ ref: audioPlayerRef,
350
+ preload: "none",
351
+ onLoadedMetadata: onLoadedMetadata,
352
+ muted: isMuted
353
+ }, Array.isArray(audioSrc) ? audioSrc.map(function (src, index) {
354
+ return /*#__PURE__*/React.createElement("source", {
355
+ key: index,
356
+ src: src,
357
+ type: getTypeFromExtension(src)
358
+ });
359
+ }) : audioSrc ? /*#__PURE__*/React.createElement("source", {
360
+ src: audioSrc,
361
+ type: getTypeFromExtension(audioSrc)
362
+ }) : null, "Your browser does not support the audio element."), /*#__PURE__*/React.createElement("div", {
363
+ className: "player-layout"
364
+ }, volumeCtrl && /*#__PURE__*/React.createElement("div", {
365
+ className: "player-controls-secondary-outer"
366
+ }, /*#__PURE__*/React.createElement("div", {
367
+ className: "player-volume-control"
368
+ }, /*#__PURE__*/React.createElement("div", {
369
+ className: "player-volume-icon"
370
+ }, /*#__PURE__*/React.createElement("button", {
371
+ onClick: toggleMute,
372
+ "aria-label": isMuted === true ? 'Muted' : 'Not Muted',
373
+ title: isMuted === true ? 'Muted' : 'Not Muted'
374
+ }, !isMuted ? /*#__PURE__*/React.createElement("img", {
375
+ src: "/img/icon-volume-low.svg",
376
+ alt: "Volume Button"
377
+ }) : /*#__PURE__*/React.createElement("img", {
378
+ src: "/img/icon-volume-mute.svg",
379
+ alt: "Volume Mute Button"
380
+ }))), /*#__PURE__*/React.createElement("div", {
381
+ className: "player-timeline player-controls-secondary"
382
+ }, /*#__PURE__*/React.createElement("input", {
383
+ id: "player-volume",
384
+ type: "range",
385
+ className: "player-volume-progress",
386
+ min: 0,
387
+ max: 100,
388
+ defaultValue: 100,
389
+ "aria-hidden": "true",
390
+ "aria-valuetext": "100%",
391
+ onChange: function onChange(e) {
392
+ return volumeControl(e);
393
+ }
394
+ })), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("img", {
395
+ src: "/img/icon-volume-high.svg",
396
+ alt: "Volume Button"
397
+ }))), /*#__PURE__*/React.createElement("div", {
398
+ className: "player-volume-label"
399
+ }, "Volume")), /*#__PURE__*/React.createElement("div", {
400
+ className: "player-controls"
401
+ }, showControls && /*#__PURE__*/React.createElement("div", {
402
+ className: "player-backward-forward-controls"
403
+ }, /*#__PURE__*/React.createElement("button", {
404
+ onClick: rewindControl
405
+ }, /*#__PURE__*/React.createElement("img", {
406
+ src: "/img/icon-rewind-15.svg",
407
+ alt: "Backward 15 seconds"
408
+ }))), /*#__PURE__*/React.createElement("div", {
409
+ className: "".concat(isPlaying ? 'is-playing' : '', " player-btn-play-pause-outer")
410
+ }, /*#__PURE__*/React.createElement("button", {
411
+ onClick: togglePlaying,
412
+ className: playBtnClass,
413
+ style: customStyles && customStyles.playPause,
414
+ id: "playbutton"
415
+ }, isPlaying ? /*#__PURE__*/React.createElement(Pause, null) : /*#__PURE__*/React.createElement(Play, null))), showControls && /*#__PURE__*/React.createElement("div", {
416
+ className: "player-backward-forward-controls"
417
+ }, /*#__PURE__*/React.createElement("button", {
418
+ onClick: forwardControl
419
+ }, /*#__PURE__*/React.createElement("img", {
420
+ src: "/img/icon-forward-15.svg",
421
+ alt: "Forward 15 seconds"
422
+ })))), showControls && /*#__PURE__*/React.createElement("div", {
423
+ className: "player-timeline"
424
+ }, /*#__PURE__*/React.createElement("div", {
425
+ className: "player-currentTime"
426
+ }, calculateTime(currentTime)), /*#__PURE__*/React.createElement("div", {
427
+ className: "player-timeline-progress-outer"
428
+ }, /*#__PURE__*/React.createElement("input", {
429
+ type: "range",
430
+ className: "player-timeline-progress",
431
+ defaultValue: "0",
432
+ ref: progressBarRef,
433
+ onChange: changePlayerCurrentTime,
434
+ "aria-label": "Audio progress",
435
+ max: duration
436
+ })), /*#__PURE__*/React.createElement("div", {
437
+ className: "player-duration",
438
+ style: customStyles && customStyles.duration
439
+ }, duration && !isNaN(duration) ? calculateTime(duration) : '-- : --')), /*#__PURE__*/React.createElement("div", {
440
+ className: "player-content"
441
+ }, customHtml && customHtml, /*#__PURE__*/React.createElement("div", {
442
+ className: "player-audio-type type-sm"
443
+ }, duration === Infinity ? /*#__PURE__*/React.createElement("div", {
444
+ className: "player-live-label"
445
+ }, prefix ? prefix : 'On Air') : /*#__PURE__*/React.createElement("div", {
446
+ className: "player-label"
447
+ }, "listen", /*#__PURE__*/React.createElement("div", {
448
+ className: "player-label-duration"
449
+ }, "[".concat(formatDuration, "]"))), /*#__PURE__*/React.createElement("div", {
450
+ className: "player-title"
451
+ }, title || '', " ", subtitle && "by ".concat(subtitle), ' ')))));
452
+ };
453
+
454
+ var ReactAudioPlayer = function ReactAudioPlayer(props) {
455
+ var _props$audioPlayerRef, _props$progressBarRef;
456
+ // references
457
+ var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : useRef(); // reference our audio component
458
+ var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : useRef(); // reference our progress bar
459
+
460
+ var customStyles = props ? props.style : '';
461
+
462
+ // hooks
463
+ var _useAudioPlayer = useAudioPlayer(audioPlayerRef, progressBarRef),
464
+ isPlaying = _useAudioPlayer.isPlaying,
465
+ currentTime = _useAudioPlayer.currentTime,
466
+ duration = _useAudioPlayer.duration,
467
+ isMuted = _useAudioPlayer.isMuted,
468
+ onLoadedMetadata = _useAudioPlayer.onLoadedMetadata,
469
+ calculateTime = _useAudioPlayer.calculateTime,
470
+ togglePlaying = _useAudioPlayer.togglePlaying,
471
+ changePlayerCurrentTime = _useAudioPlayer.changePlayerCurrentTime,
472
+ volumeControl = _useAudioPlayer.volumeControl,
473
+ toggleMute = _useAudioPlayer.toggleMute,
474
+ formatCalculateTime = _useAudioPlayer.formatCalculateTime,
475
+ rewindControl = _useAudioPlayer.rewindControl,
476
+ forwardControl = _useAudioPlayer.forwardControl;
477
+ return /*#__PURE__*/React.createElement(ReactAudioPlayerInner, _extends({}, props, {
478
+ audioPlayerRef: audioPlayerRef,
479
+ progressBarRef: progressBarRef,
480
+ isPlaying: isPlaying,
481
+ isMuted: isMuted,
482
+ currentTime: currentTime,
483
+ duration: duration,
484
+ customStyles: customStyles,
485
+ onLoadedMetadata: onLoadedMetadata,
486
+ calculateTime: calculateTime,
487
+ togglePlaying: togglePlaying,
488
+ changePlayerCurrentTime: changePlayerCurrentTime,
489
+ volumeControl: volumeControl,
490
+ toggleMute: toggleMute,
491
+ formatCalculateTime: formatCalculateTime,
492
+ rewindControl: rewindControl,
493
+ forwardControl: forwardControl
494
+ }));
495
+ };
496
+
497
+ export { ReactAudioPlayer, ReactAudioPlayerInner, useAudioPlayer };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "apm-react-audio-player",
3
- "version": "1.0.27",
3
+ "version": "1.1.0",
4
4
  "author": "Jason Phan <jphan@mpr.org>",
5
5
  "license": "MIT",
6
6
  "private": false,
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "APMG/apm-react-audio-player.git"
9
+ "url": "git+https://github.com/APMG/apm-react-audio-player.git"
10
10
  },
11
11
  "main": "dist/index.js",
12
12
  "module": "dist/index.modern.js",
@@ -16,6 +16,8 @@
16
16
  },
17
17
  "scripts": {
18
18
  "build": "rollup -c rollup.config.mjs",
19
+ "build:examples": "esbuild examples/hls-example.jsx --bundle --format=esm --outfile=examples/bundle.js --external:react --external:react-dom --external:react/jsx-runtime --loader:.js=jsx",
20
+ "serve:examples": "npx http-server . -p 8000 -o /examples/index.html",
19
21
  "dev": "rollup -c -w",
20
22
  "test": "jest --watch",
21
23
  "test:ci": "jest",
@@ -32,7 +34,7 @@
32
34
  "react": "^16.0.0"
33
35
  },
34
36
  "dependencies": {
35
- "rollup": "^4.29.1",
37
+ "rollup": "^4.59.0",
36
38
  "rollup-plugin-babel": "^4.3.3"
37
39
  },
38
40
  "devDependencies": {
@@ -41,8 +43,9 @@
41
43
  "@babel/plugin-syntax-dynamic-import": "^7.2.0",
42
44
  "@babel/preset-env": "^7.5.5",
43
45
  "@babel/preset-react": "^7.0.0",
44
- "@testing-library/react": "^8.0.5",
46
+ "@testing-library/react": "^14.0.0",
45
47
  "cross-env": "^7.0.2",
48
+ "esbuild": "^0.27.4",
46
49
  "eslint": "^6.8.0",
47
50
  "eslint-config-prettier": "^6.7.0",
48
51
  "eslint-config-standard": "^14.1.0",
@@ -53,8 +56,8 @@
53
56
  "eslint-plugin-promise": "^4.2.1",
54
57
  "eslint-plugin-react": "^7.17.0",
55
58
  "eslint-plugin-standard": "^4.0.1",
56
- "jest": "^24.8.0",
57
- "jest-environment-jsdom-fourteen": "^0.1.0",
59
+ "jest": "^29.0.0",
60
+ "jest-environment-jsdom": "^30.3.0",
58
61
  "jest-prop-type-error": "^1.1.0",
59
62
  "prettier": "^2.0.4",
60
63
  "react": "^18",
@@ -63,9 +66,25 @@
63
66
  },
64
67
  "resolutions": {
65
68
  "braces": ">=3.0.3",
66
- "react-scripts": "^5.0.1"
69
+ "react-scripts": "^5.0.1",
70
+ "micromatch": ">=4.0.8",
71
+ "minimatch": ">=3.1.4",
72
+ "tough-cookie": ">=4.1.3",
73
+ "ajv": ">=6.14.0",
74
+ "qs": ">=6.14.1",
75
+ "lodash": ">=4.17.21",
76
+ "js-yaml": ">=4.1.0",
77
+ "@babel/helpers": ">=7.26.0",
78
+ "@babel/runtime": ">=7.26.0",
79
+ "flatted": ">=3.3.3",
80
+ "brace-expansion": ">=2.0.1",
81
+ "tmp": ">=0.2.3"
67
82
  },
68
83
  "files": [
69
84
  "dist"
70
- ]
85
+ ],
86
+ "jest": {
87
+ "testEnvironment": "jsdom",
88
+ "setupFilesAfterEnv": ["<rootDir>/jest.setup.js"]
89
+ }
71
90
  }