apm-react-audio-player 1.0.26 → 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;
@@ -61,11 +61,12 @@ function _unsupportedIterableToArray(r, a) {
61
61
  }
62
62
 
63
63
  var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtrl) {
64
+ var initialDuration = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined;
64
65
  var _useState = React.useState(false),
65
66
  _useState2 = _slicedToArray(_useState, 2),
66
67
  isPlaying = _useState2[0],
67
68
  setIsPlaying = _useState2[1];
68
- var _useState3 = React.useState(0),
69
+ var _useState3 = React.useState(initialDuration),
69
70
  _useState4 = _slicedToArray(_useState3, 2),
70
71
  duration = _useState4[0],
71
72
  setDuration = _useState4[1];
@@ -82,32 +83,41 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
82
83
  _useState10 = _slicedToArray(_useState9, 2),
83
84
  isMuted = _useState10[0],
84
85
  setIsMuted = _useState10[1];
85
- var isStream = audioRef.current && audioRef.current.currentSrc.includes('stream');
86
+ var isStream = audioRef.current && audioRef.current.duration === Infinity;
86
87
  React.useEffect(function () {
87
88
  if (currentTime === Number(duration)) {
88
89
  // restart()
89
90
  setIsFinishedPlaying(true);
90
91
  }
91
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]);
92
99
  var onLoadedMetadata = function onLoadedMetadata() {
93
- var _audioRef$current;
100
+ if (!audioRef.current) return;
94
101
  var seconds = Math.floor(audioRef.current.duration);
95
102
  setDuration(seconds);
96
- 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) {
97
104
  progressBarRef.current.max = seconds;
98
105
  }
99
106
  };
100
107
  var updateCurrentTime = function updateCurrentTime() {
101
- setCurrentTime(progressBarRef.current.value);
108
+ if (progressBarRef.current) {
109
+ setCurrentTime(progressBarRef.current.value);
110
+ }
102
111
  };
103
112
  var _whilePlaying = function whilePlaying() {
104
- var _audioRef$current2;
105
- if (!(audioRef !== null && audioRef !== void 0 && (_audioRef$current2 = audioRef.current) !== null && _audioRef$current2 !== void 0 && _audioRef$current2.currentSrc.includes('stream'))) {
106
- // isStream isn't correct here
107
-
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) {
108
118
  progressBarRef.current.value = Math.floor(audioRef.current.currentTime);
119
+ progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
109
120
  }
110
- progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
111
121
  updateCurrentTime();
112
122
 
113
123
  // when you reach the end of the song
@@ -134,8 +144,10 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
134
144
  setIsPlaying(true);
135
145
  setIsFinishedPlaying(false);
136
146
  audioRef.current.play();
137
- if (!audioRef.current.currentSrc.includes('stream')) {
138
- // 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)) {
139
151
  animationRef.current = window.requestAnimationFrame(_whilePlaying);
140
152
  }
141
153
  };
@@ -157,24 +169,29 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
157
169
  }
158
170
  };
159
171
  var changePlayerCurrentTime = function changePlayerCurrentTime() {
172
+ if (!progressBarRef.current || !audioRef.current) return;
160
173
  audioRef.current.currentTime = progressBarRef.current.value;
161
174
  setCurrentTime(progressBarRef.current.value);
162
175
  progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
163
176
  };
164
177
  var changeRange = function changeRange() {
178
+ if (!progressBarRef.current || !audioRef.current) return;
165
179
  audioRef.current.currentTime = progressBarRef.current.value;
166
180
  updateCurrentTime();
167
181
  changePlayerCurrentTime();
168
182
  };
169
183
  var rewindControl = function rewindControl() {
184
+ if (!progressBarRef.current) return;
170
185
  progressBarRef.current.value = Number(progressBarRef.current.value) - 15;
171
186
  changeRange();
172
187
  };
173
188
  var forwardControl = function forwardControl() {
189
+ if (!progressBarRef.current) return;
174
190
  progressBarRef.current.value = Number(progressBarRef.current.value) + 15;
175
191
  changeRange();
176
192
  };
177
193
  var volumeControl = function volumeControl(e) {
194
+ if (!audioRef.current) return;
178
195
  var value = e.target.value;
179
196
  var volume = value / 100;
180
197
  audioRef.current.volume = volume;
@@ -258,6 +275,24 @@ var Pause = function Pause() {
258
275
  }));
259
276
  };
260
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
+ };
261
296
  var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
262
297
  var _props$audioPlayerRef, _props$progressBarRef;
263
298
  // references
@@ -270,7 +305,6 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
270
305
  volumeCtrl = props.volumeCtrl,
271
306
  playBtnClass = props.playBtnClass,
272
307
  customHtml = props.customHtml,
273
- isLive = props.isLive,
274
308
  onLoadedMetadata = props.onLoadedMetadata,
275
309
  calculateTime = props.calculateTime,
276
310
  togglePlaying = props.togglePlaying,
@@ -288,16 +322,46 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
288
322
  prefix = props.prefix;
289
323
  var audioDuration = duration && !isNaN(duration) && calculateTime(duration);
290
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);
291
347
  return audioSrc && /*#__PURE__*/React.createElement("div", {
292
348
  className: "audioPlayer",
293
349
  style: customStyles && customStyles.audioPlayer
294
350
  }, /*#__PURE__*/React.createElement("audio", {
295
351
  ref: audioPlayerRef,
296
- src: audioSrc,
297
352
  preload: "none",
298
353
  onLoadedMetadata: onLoadedMetadata,
299
354
  muted: isMuted
300
- }), /*#__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", {
301
365
  className: "player-layout"
302
366
  }, volumeCtrl && /*#__PURE__*/React.createElement("div", {
303
367
  className: "player-controls-secondary-outer"
@@ -323,6 +387,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
323
387
  className: "player-volume-progress",
324
388
  min: 0,
325
389
  max: 100,
390
+ defaultValue: 100,
326
391
  "aria-hidden": "true",
327
392
  "aria-valuetext": "100%",
328
393
  onChange: function onChange(e) {
@@ -335,7 +400,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
335
400
  className: "player-volume-label"
336
401
  }, "Volume")), /*#__PURE__*/React.createElement("div", {
337
402
  className: "player-controls"
338
- }, !isLive && /*#__PURE__*/React.createElement("div", {
403
+ }, showControls && /*#__PURE__*/React.createElement("div", {
339
404
  className: "player-backward-forward-controls"
340
405
  }, /*#__PURE__*/React.createElement("button", {
341
406
  onClick: rewindControl
@@ -349,14 +414,14 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
349
414
  className: playBtnClass,
350
415
  style: customStyles && customStyles.playPause,
351
416
  id: "playbutton"
352
- }, 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", {
353
418
  className: "player-backward-forward-controls"
354
419
  }, /*#__PURE__*/React.createElement("button", {
355
420
  onClick: forwardControl
356
421
  }, /*#__PURE__*/React.createElement("img", {
357
422
  src: "/img/icon-forward-15.svg",
358
423
  alt: "Forward 15 seconds"
359
- })))), !isLive && /*#__PURE__*/React.createElement("div", {
424
+ })))), showControls && /*#__PURE__*/React.createElement("div", {
360
425
  className: "player-timeline"
361
426
  }, /*#__PURE__*/React.createElement("div", {
362
427
  className: "player-currentTime"
@@ -373,11 +438,11 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
373
438
  })), /*#__PURE__*/React.createElement("div", {
374
439
  className: "player-duration",
375
440
  style: customStyles && customStyles.duration
376
- }, duration && !isNaN(duration) && calculateTime(duration))), /*#__PURE__*/React.createElement("div", {
441
+ }, duration && !isNaN(duration) ? calculateTime(duration) : '-- : --')), /*#__PURE__*/React.createElement("div", {
377
442
  className: "player-content"
378
443
  }, customHtml && customHtml, /*#__PURE__*/React.createElement("div", {
379
444
  className: "player-audio-type type-sm"
380
- }, isLive ? /*#__PURE__*/React.createElement("div", {
445
+ }, duration === Infinity ? /*#__PURE__*/React.createElement("div", {
381
446
  className: "player-live-label"
382
447
  }, prefix ? prefix : 'On Air') : /*#__PURE__*/React.createElement("div", {
383
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.26",
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
  }