apm-react-audio-player 1.0.27 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
@@ -134,9 +143,22 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
134
143
  var play = function play() {
135
144
  setIsPlaying(true);
136
145
  setIsFinishedPlaying(false);
146
+
147
+ // For live streams (duration === Infinity), reset the audio element before
148
+ // playing. The audioSrc useEffect in ReactAudioPlayerInner calls load() on
149
+ // mount, which causes the browser to pre-buffer the HLS stream. By the time
150
+ // the user clicks play the manifest's seekable window has moved forward and
151
+ // old segments are gone, so the browser auto-seeks to the earliest available
152
+ // position (e.g. 20s in), causing ads to start mid-stream. Calling load()
153
+ // here reconnects to the current live edge and gets a fresh manifest.
154
+ if (duration === Infinity) {
155
+ audioRef.current.load();
156
+ }
137
157
  audioRef.current.play();
138
- if (!audioRef.current.currentSrc.includes('stream')) {
139
- // isStream isn't correctly set here
158
+
159
+ // Only start RAF loop for non-live streams with valid duration
160
+ var dur = audioRef.current.duration;
161
+ if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
140
162
  animationRef.current = window.requestAnimationFrame(_whilePlaying);
141
163
  }
142
164
  };
@@ -158,24 +180,29 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
158
180
  }
159
181
  };
160
182
  var changePlayerCurrentTime = function changePlayerCurrentTime() {
183
+ if (!progressBarRef.current || !audioRef.current) return;
161
184
  audioRef.current.currentTime = progressBarRef.current.value;
162
185
  setCurrentTime(progressBarRef.current.value);
163
186
  progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
164
187
  };
165
188
  var changeRange = function changeRange() {
189
+ if (!progressBarRef.current || !audioRef.current) return;
166
190
  audioRef.current.currentTime = progressBarRef.current.value;
167
191
  updateCurrentTime();
168
192
  changePlayerCurrentTime();
169
193
  };
170
194
  var rewindControl = function rewindControl() {
195
+ if (!progressBarRef.current) return;
171
196
  progressBarRef.current.value = Number(progressBarRef.current.value) - 15;
172
197
  changeRange();
173
198
  };
174
199
  var forwardControl = function forwardControl() {
200
+ if (!progressBarRef.current) return;
175
201
  progressBarRef.current.value = Number(progressBarRef.current.value) + 15;
176
202
  changeRange();
177
203
  };
178
204
  var volumeControl = function volumeControl(e) {
205
+ if (!audioRef.current) return;
179
206
  var value = e.target.value;
180
207
  var volume = value / 100;
181
208
  audioRef.current.volume = volume;
@@ -259,6 +286,24 @@ var Pause = function Pause() {
259
286
  }));
260
287
  };
261
288
 
289
+ var getTypeFromExtension = function getTypeFromExtension(url) {
290
+ var extension = url.split('.').pop().split('?')[0];
291
+ switch (extension) {
292
+ case 'm3u8':
293
+ return 'application/x-mpegURL';
294
+ case 'aac':
295
+ return 'audio/aac';
296
+ case 'mp3':
297
+ return 'audio/mpeg';
298
+ case 'ogg':
299
+ return 'audio/ogg';
300
+ case 'wav':
301
+ return 'audio/wav';
302
+ default:
303
+ return undefined;
304
+ // Let browser auto-detect
305
+ }
306
+ };
262
307
  var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
263
308
  var _props$audioPlayerRef, _props$progressBarRef;
264
309
  // references
@@ -271,7 +316,6 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
271
316
  volumeCtrl = props.volumeCtrl,
272
317
  playBtnClass = props.playBtnClass,
273
318
  customHtml = props.customHtml,
274
- isLive = props.isLive,
275
319
  onLoadedMetadata = props.onLoadedMetadata,
276
320
  calculateTime = props.calculateTime,
277
321
  togglePlaying = props.togglePlaying,
@@ -289,16 +333,46 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
289
333
  prefix = props.prefix;
290
334
  var audioDuration = duration && !isNaN(duration) && calculateTime(duration);
291
335
  var formatDuration = duration && !isNaN(duration) && audioDuration && formatCalculateTime(audioDuration);
336
+
337
+ // Reload audio when audioSrc changes
338
+ // Use JSON.stringify to handle array comparisons by value instead of reference
339
+ React.useEffect(function () {
340
+ if (audioPlayerRef.current && audioSrc) {
341
+ try {
342
+ audioPlayerRef.current.load();
343
+ } catch (err) {
344
+ console.warn('Failed to reload audio source:', err);
345
+ }
346
+ }
347
+ }, [JSON.stringify(audioSrc)]);
348
+
349
+ // Set initial volume to 100%
350
+ React.useEffect(function () {
351
+ if (audioPlayerRef.current) {
352
+ audioPlayerRef.current.volume = 1.0;
353
+ }
354
+ }, []);
355
+
356
+ // Helper to determine if controls should show
357
+ var showControls = duration !== Infinity && duration !== undefined && !isNaN(duration) && isFinite(duration);
292
358
  return audioSrc && /*#__PURE__*/React.createElement("div", {
293
359
  className: "audioPlayer",
294
360
  style: customStyles && customStyles.audioPlayer
295
361
  }, /*#__PURE__*/React.createElement("audio", {
296
362
  ref: audioPlayerRef,
297
- src: audioSrc,
298
363
  preload: "none",
299
364
  onLoadedMetadata: onLoadedMetadata,
300
365
  muted: isMuted
301
- }), /*#__PURE__*/React.createElement("div", {
366
+ }, Array.isArray(audioSrc) ? audioSrc.map(function (src, index) {
367
+ return /*#__PURE__*/React.createElement("source", {
368
+ key: index,
369
+ src: src,
370
+ type: getTypeFromExtension(src)
371
+ });
372
+ }) : audioSrc ? /*#__PURE__*/React.createElement("source", {
373
+ src: audioSrc,
374
+ type: getTypeFromExtension(audioSrc)
375
+ }) : null, "Your browser does not support the audio element."), /*#__PURE__*/React.createElement("div", {
302
376
  className: "player-layout"
303
377
  }, volumeCtrl && /*#__PURE__*/React.createElement("div", {
304
378
  className: "player-controls-secondary-outer"
@@ -324,6 +398,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
324
398
  className: "player-volume-progress",
325
399
  min: 0,
326
400
  max: 100,
401
+ defaultValue: 100,
327
402
  "aria-hidden": "true",
328
403
  "aria-valuetext": "100%",
329
404
  onChange: function onChange(e) {
@@ -336,7 +411,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
336
411
  className: "player-volume-label"
337
412
  }, "Volume")), /*#__PURE__*/React.createElement("div", {
338
413
  className: "player-controls"
339
- }, !isLive && /*#__PURE__*/React.createElement("div", {
414
+ }, showControls && /*#__PURE__*/React.createElement("div", {
340
415
  className: "player-backward-forward-controls"
341
416
  }, /*#__PURE__*/React.createElement("button", {
342
417
  onClick: rewindControl
@@ -350,14 +425,14 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
350
425
  className: playBtnClass,
351
426
  style: customStyles && customStyles.playPause,
352
427
  id: "playbutton"
353
- }, isPlaying ? /*#__PURE__*/React.createElement(Pause, null) : /*#__PURE__*/React.createElement(Play, null))), !isLive && /*#__PURE__*/React.createElement("div", {
428
+ }, isPlaying ? /*#__PURE__*/React.createElement(Pause, null) : /*#__PURE__*/React.createElement(Play, null))), showControls && /*#__PURE__*/React.createElement("div", {
354
429
  className: "player-backward-forward-controls"
355
430
  }, /*#__PURE__*/React.createElement("button", {
356
431
  onClick: forwardControl
357
432
  }, /*#__PURE__*/React.createElement("img", {
358
433
  src: "/img/icon-forward-15.svg",
359
434
  alt: "Forward 15 seconds"
360
- })))), !isLive && /*#__PURE__*/React.createElement("div", {
435
+ })))), showControls && /*#__PURE__*/React.createElement("div", {
361
436
  className: "player-timeline"
362
437
  }, /*#__PURE__*/React.createElement("div", {
363
438
  className: "player-currentTime"
@@ -378,7 +453,7 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
378
453
  className: "player-content"
379
454
  }, customHtml && customHtml, /*#__PURE__*/React.createElement("div", {
380
455
  className: "player-audio-type type-sm"
381
- }, isLive ? /*#__PURE__*/React.createElement("div", {
456
+ }, duration === Infinity ? /*#__PURE__*/React.createElement("div", {
382
457
  className: "player-live-label"
383
458
  }, prefix ? prefix : 'On Air') : /*#__PURE__*/React.createElement("div", {
384
459
  className: "player-label"
@@ -0,0 +1,508 @@
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
+
145
+ // For live streams (duration === Infinity), reset the audio element before
146
+ // playing. The audioSrc useEffect in ReactAudioPlayerInner calls load() on
147
+ // mount, which causes the browser to pre-buffer the HLS stream. By the time
148
+ // the user clicks play the manifest's seekable window has moved forward and
149
+ // old segments are gone, so the browser auto-seeks to the earliest available
150
+ // position (e.g. 20s in), causing ads to start mid-stream. Calling load()
151
+ // here reconnects to the current live edge and gets a fresh manifest.
152
+ if (duration === Infinity) {
153
+ audioRef.current.load();
154
+ }
155
+ audioRef.current.play();
156
+
157
+ // Only start RAF loop for non-live streams with valid duration
158
+ var dur = audioRef.current.duration;
159
+ if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
160
+ animationRef.current = window.requestAnimationFrame(_whilePlaying);
161
+ }
162
+ };
163
+ var toggleMute = function toggleMute() {
164
+ setIsMuted(!isMuted);
165
+ };
166
+ var calculateTime = function calculateTime(secs) {
167
+ var minutes = Math.floor(secs / 60);
168
+ var returnedMinutes = minutes < 10 ? "0".concat(minutes) : "".concat(minutes);
169
+ var seconds = Math.floor(secs % 60);
170
+ var returnedSeconds = seconds < 10 ? "0".concat(seconds) : "".concat(seconds);
171
+ return "".concat(returnedMinutes, ":").concat(returnedSeconds);
172
+ };
173
+ var togglePlaying = function togglePlaying() {
174
+ if (!isPlaying) {
175
+ play();
176
+ } else {
177
+ pause();
178
+ }
179
+ };
180
+ var changePlayerCurrentTime = function changePlayerCurrentTime() {
181
+ if (!progressBarRef.current || !audioRef.current) return;
182
+ audioRef.current.currentTime = progressBarRef.current.value;
183
+ setCurrentTime(progressBarRef.current.value);
184
+ progressBarRef.current.style.setProperty('--seek-before-width', "".concat(progressBarRef.current.value / duration * 100, "%"));
185
+ };
186
+ var changeRange = function changeRange() {
187
+ if (!progressBarRef.current || !audioRef.current) return;
188
+ audioRef.current.currentTime = progressBarRef.current.value;
189
+ updateCurrentTime();
190
+ changePlayerCurrentTime();
191
+ };
192
+ var rewindControl = function rewindControl() {
193
+ if (!progressBarRef.current) return;
194
+ progressBarRef.current.value = Number(progressBarRef.current.value) - 15;
195
+ changeRange();
196
+ };
197
+ var forwardControl = function forwardControl() {
198
+ if (!progressBarRef.current) return;
199
+ progressBarRef.current.value = Number(progressBarRef.current.value) + 15;
200
+ changeRange();
201
+ };
202
+ var volumeControl = function volumeControl(e) {
203
+ if (!audioRef.current) return;
204
+ var value = e.target.value;
205
+ var volume = value / 100;
206
+ audioRef.current.volume = volume;
207
+ };
208
+ var formatCalculateTime = function formatCalculateTime(timeString) {
209
+ var toString = String(timeString);
210
+ if (toString.split(':').length === 3) {
211
+ var _toString$split = toString.split(':'),
212
+ _toString$split2 = _slicedToArray(_toString$split, 3),
213
+ hours = _toString$split2[0],
214
+ minutes = _toString$split2[1],
215
+ seconds = _toString$split2[2];
216
+ return "".concat(parseInt(hours), "hr ").concat(parseInt(minutes), "min ").concat(parseInt(seconds), "sec");
217
+ } else if (toString.split(':').length === 2) {
218
+ var _toString$split3 = toString.split(':'),
219
+ _toString$split4 = _slicedToArray(_toString$split3, 2),
220
+ _minutes = _toString$split4[0],
221
+ _seconds = _toString$split4[1];
222
+ return "".concat(parseInt(_minutes), "min ").concat(parseInt(_seconds), "sec");
223
+ }
224
+ };
225
+ return {
226
+ onLoadedMetadata: onLoadedMetadata,
227
+ calculateTime: calculateTime,
228
+ togglePlaying: togglePlaying,
229
+ changePlayerCurrentTime: changePlayerCurrentTime,
230
+ rewindControl: rewindControl,
231
+ forwardControl: forwardControl,
232
+ play: play,
233
+ pause: pause,
234
+ isPlaying: isPlaying,
235
+ isFinishedPlaying: isFinishedPlaying,
236
+ currentTime: currentTime,
237
+ duration: duration,
238
+ volumeCtrl: volumeCtrl,
239
+ isMuted: isMuted,
240
+ volumeControl: volumeControl,
241
+ toggleMute: toggleMute,
242
+ formatCalculateTime: formatCalculateTime
243
+ };
244
+ };
245
+
246
+ var Play = function Play() {
247
+ return /*#__PURE__*/React.createElement("svg", {
248
+ stroke: "currentColor",
249
+ fill: "currentColor",
250
+ strokeWidth: "0",
251
+ viewBox: "0 0 448 512",
252
+ className: "play",
253
+ height: "1em",
254
+ width: "1em",
255
+ xmlns: "http://www.w3.org/2000/svg",
256
+ role: "img",
257
+ "aria-labelledby": "play playButton"
258
+ }, /*#__PURE__*/React.createElement("title", {
259
+ id: "play"
260
+ }, "Play"), /*#__PURE__*/React.createElement("desc", {
261
+ id: "playButton"
262
+ }, "Play Button"), /*#__PURE__*/React.createElement("path", {
263
+ 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"
264
+ }));
265
+ };
266
+
267
+ var Pause = function Pause() {
268
+ return /*#__PURE__*/React.createElement("svg", {
269
+ stroke: "currentColor",
270
+ fill: "currentColor",
271
+ strokeWidth: "0",
272
+ viewBox: "0 0 448 512",
273
+ height: "1em",
274
+ width: "1em",
275
+ xmlns: "http://www.w3.org/2000/svg",
276
+ role: "img",
277
+ "aria-labelledby": "pause pauseButton"
278
+ }, /*#__PURE__*/React.createElement("title", {
279
+ id: "pause"
280
+ }, "Pause"), /*#__PURE__*/React.createElement("desc", {
281
+ id: "pauseButton"
282
+ }, "Pause Button"), /*#__PURE__*/React.createElement("path", {
283
+ 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"
284
+ }));
285
+ };
286
+
287
+ var getTypeFromExtension = function getTypeFromExtension(url) {
288
+ var extension = url.split('.').pop().split('?')[0];
289
+ switch (extension) {
290
+ case 'm3u8':
291
+ return 'application/x-mpegURL';
292
+ case 'aac':
293
+ return 'audio/aac';
294
+ case 'mp3':
295
+ return 'audio/mpeg';
296
+ case 'ogg':
297
+ return 'audio/ogg';
298
+ case 'wav':
299
+ return 'audio/wav';
300
+ default:
301
+ return undefined;
302
+ // Let browser auto-detect
303
+ }
304
+ };
305
+ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
306
+ var _props$audioPlayerRef, _props$progressBarRef;
307
+ // references
308
+ var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : useRef(); // reference our audio component
309
+ var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : useRef(); // reference our progress bar
310
+
311
+ var customStyles = props ? props.style : '';
312
+ var title = props.title,
313
+ audioSrc = props.audioSrc,
314
+ volumeCtrl = props.volumeCtrl,
315
+ playBtnClass = props.playBtnClass,
316
+ customHtml = props.customHtml,
317
+ onLoadedMetadata = props.onLoadedMetadata,
318
+ calculateTime = props.calculateTime,
319
+ togglePlaying = props.togglePlaying,
320
+ changePlayerCurrentTime = props.changePlayerCurrentTime,
321
+ isPlaying = props.isPlaying,
322
+ currentTime = props.currentTime,
323
+ duration = props.duration,
324
+ volumeControl = props.volumeControl,
325
+ toggleMute = props.toggleMute,
326
+ isMuted = props.isMuted,
327
+ formatCalculateTime = props.formatCalculateTime,
328
+ rewindControl = props.rewindControl,
329
+ forwardControl = props.forwardControl,
330
+ subtitle = props.subtitle,
331
+ prefix = props.prefix;
332
+ var audioDuration = duration && !isNaN(duration) && calculateTime(duration);
333
+ var formatDuration = duration && !isNaN(duration) && audioDuration && formatCalculateTime(audioDuration);
334
+
335
+ // Reload audio when audioSrc changes
336
+ // Use JSON.stringify to handle array comparisons by value instead of reference
337
+ useEffect(function () {
338
+ if (audioPlayerRef.current && audioSrc) {
339
+ try {
340
+ audioPlayerRef.current.load();
341
+ } catch (err) {
342
+ console.warn('Failed to reload audio source:', err);
343
+ }
344
+ }
345
+ }, [JSON.stringify(audioSrc)]);
346
+
347
+ // Set initial volume to 100%
348
+ useEffect(function () {
349
+ if (audioPlayerRef.current) {
350
+ audioPlayerRef.current.volume = 1.0;
351
+ }
352
+ }, []);
353
+
354
+ // Helper to determine if controls should show
355
+ var showControls = duration !== Infinity && duration !== undefined && !isNaN(duration) && isFinite(duration);
356
+ return audioSrc && /*#__PURE__*/React.createElement("div", {
357
+ className: "audioPlayer",
358
+ style: customStyles && customStyles.audioPlayer
359
+ }, /*#__PURE__*/React.createElement("audio", {
360
+ ref: audioPlayerRef,
361
+ preload: "none",
362
+ onLoadedMetadata: onLoadedMetadata,
363
+ muted: isMuted
364
+ }, Array.isArray(audioSrc) ? audioSrc.map(function (src, index) {
365
+ return /*#__PURE__*/React.createElement("source", {
366
+ key: index,
367
+ src: src,
368
+ type: getTypeFromExtension(src)
369
+ });
370
+ }) : audioSrc ? /*#__PURE__*/React.createElement("source", {
371
+ src: audioSrc,
372
+ type: getTypeFromExtension(audioSrc)
373
+ }) : null, "Your browser does not support the audio element."), /*#__PURE__*/React.createElement("div", {
374
+ className: "player-layout"
375
+ }, volumeCtrl && /*#__PURE__*/React.createElement("div", {
376
+ className: "player-controls-secondary-outer"
377
+ }, /*#__PURE__*/React.createElement("div", {
378
+ className: "player-volume-control"
379
+ }, /*#__PURE__*/React.createElement("div", {
380
+ className: "player-volume-icon"
381
+ }, /*#__PURE__*/React.createElement("button", {
382
+ onClick: toggleMute,
383
+ "aria-label": isMuted === true ? 'Muted' : 'Not Muted',
384
+ title: isMuted === true ? 'Muted' : 'Not Muted'
385
+ }, !isMuted ? /*#__PURE__*/React.createElement("img", {
386
+ src: "/img/icon-volume-low.svg",
387
+ alt: "Volume Button"
388
+ }) : /*#__PURE__*/React.createElement("img", {
389
+ src: "/img/icon-volume-mute.svg",
390
+ alt: "Volume Mute Button"
391
+ }))), /*#__PURE__*/React.createElement("div", {
392
+ className: "player-timeline player-controls-secondary"
393
+ }, /*#__PURE__*/React.createElement("input", {
394
+ id: "player-volume",
395
+ type: "range",
396
+ className: "player-volume-progress",
397
+ min: 0,
398
+ max: 100,
399
+ defaultValue: 100,
400
+ "aria-hidden": "true",
401
+ "aria-valuetext": "100%",
402
+ onChange: function onChange(e) {
403
+ return volumeControl(e);
404
+ }
405
+ })), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("img", {
406
+ src: "/img/icon-volume-high.svg",
407
+ alt: "Volume Button"
408
+ }))), /*#__PURE__*/React.createElement("div", {
409
+ className: "player-volume-label"
410
+ }, "Volume")), /*#__PURE__*/React.createElement("div", {
411
+ className: "player-controls"
412
+ }, showControls && /*#__PURE__*/React.createElement("div", {
413
+ className: "player-backward-forward-controls"
414
+ }, /*#__PURE__*/React.createElement("button", {
415
+ onClick: rewindControl
416
+ }, /*#__PURE__*/React.createElement("img", {
417
+ src: "/img/icon-rewind-15.svg",
418
+ alt: "Backward 15 seconds"
419
+ }))), /*#__PURE__*/React.createElement("div", {
420
+ className: "".concat(isPlaying ? 'is-playing' : '', " player-btn-play-pause-outer")
421
+ }, /*#__PURE__*/React.createElement("button", {
422
+ onClick: togglePlaying,
423
+ className: playBtnClass,
424
+ style: customStyles && customStyles.playPause,
425
+ id: "playbutton"
426
+ }, isPlaying ? /*#__PURE__*/React.createElement(Pause, null) : /*#__PURE__*/React.createElement(Play, null))), showControls && /*#__PURE__*/React.createElement("div", {
427
+ className: "player-backward-forward-controls"
428
+ }, /*#__PURE__*/React.createElement("button", {
429
+ onClick: forwardControl
430
+ }, /*#__PURE__*/React.createElement("img", {
431
+ src: "/img/icon-forward-15.svg",
432
+ alt: "Forward 15 seconds"
433
+ })))), showControls && /*#__PURE__*/React.createElement("div", {
434
+ className: "player-timeline"
435
+ }, /*#__PURE__*/React.createElement("div", {
436
+ className: "player-currentTime"
437
+ }, calculateTime(currentTime)), /*#__PURE__*/React.createElement("div", {
438
+ className: "player-timeline-progress-outer"
439
+ }, /*#__PURE__*/React.createElement("input", {
440
+ type: "range",
441
+ className: "player-timeline-progress",
442
+ defaultValue: "0",
443
+ ref: progressBarRef,
444
+ onChange: changePlayerCurrentTime,
445
+ "aria-label": "Audio progress",
446
+ max: duration
447
+ })), /*#__PURE__*/React.createElement("div", {
448
+ className: "player-duration",
449
+ style: customStyles && customStyles.duration
450
+ }, duration && !isNaN(duration) ? calculateTime(duration) : '-- : --')), /*#__PURE__*/React.createElement("div", {
451
+ className: "player-content"
452
+ }, customHtml && customHtml, /*#__PURE__*/React.createElement("div", {
453
+ className: "player-audio-type type-sm"
454
+ }, duration === Infinity ? /*#__PURE__*/React.createElement("div", {
455
+ className: "player-live-label"
456
+ }, prefix ? prefix : 'On Air') : /*#__PURE__*/React.createElement("div", {
457
+ className: "player-label"
458
+ }, "listen", /*#__PURE__*/React.createElement("div", {
459
+ className: "player-label-duration"
460
+ }, "[".concat(formatDuration, "]"))), /*#__PURE__*/React.createElement("div", {
461
+ className: "player-title"
462
+ }, title || '', " ", subtitle && "by ".concat(subtitle), ' ')))));
463
+ };
464
+
465
+ var ReactAudioPlayer = function ReactAudioPlayer(props) {
466
+ var _props$audioPlayerRef, _props$progressBarRef;
467
+ // references
468
+ var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : useRef(); // reference our audio component
469
+ var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : useRef(); // reference our progress bar
470
+
471
+ var customStyles = props ? props.style : '';
472
+
473
+ // hooks
474
+ var _useAudioPlayer = useAudioPlayer(audioPlayerRef, progressBarRef),
475
+ isPlaying = _useAudioPlayer.isPlaying,
476
+ currentTime = _useAudioPlayer.currentTime,
477
+ duration = _useAudioPlayer.duration,
478
+ isMuted = _useAudioPlayer.isMuted,
479
+ onLoadedMetadata = _useAudioPlayer.onLoadedMetadata,
480
+ calculateTime = _useAudioPlayer.calculateTime,
481
+ togglePlaying = _useAudioPlayer.togglePlaying,
482
+ changePlayerCurrentTime = _useAudioPlayer.changePlayerCurrentTime,
483
+ volumeControl = _useAudioPlayer.volumeControl,
484
+ toggleMute = _useAudioPlayer.toggleMute,
485
+ formatCalculateTime = _useAudioPlayer.formatCalculateTime,
486
+ rewindControl = _useAudioPlayer.rewindControl,
487
+ forwardControl = _useAudioPlayer.forwardControl;
488
+ return /*#__PURE__*/React.createElement(ReactAudioPlayerInner, _extends({}, props, {
489
+ audioPlayerRef: audioPlayerRef,
490
+ progressBarRef: progressBarRef,
491
+ isPlaying: isPlaying,
492
+ isMuted: isMuted,
493
+ currentTime: currentTime,
494
+ duration: duration,
495
+ customStyles: customStyles,
496
+ onLoadedMetadata: onLoadedMetadata,
497
+ calculateTime: calculateTime,
498
+ togglePlaying: togglePlaying,
499
+ changePlayerCurrentTime: changePlayerCurrentTime,
500
+ volumeControl: volumeControl,
501
+ toggleMute: toggleMute,
502
+ formatCalculateTime: formatCalculateTime,
503
+ rewindControl: rewindControl,
504
+ forwardControl: forwardControl
505
+ }));
506
+ };
507
+
508
+ 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.1",
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",
@@ -25,14 +27,11 @@
25
27
  "prettier:fix": "prettier --check '**/**.js' --write",
26
28
  "clean": "rm -rf node_modules dist package-lock.json yarn.lock"
27
29
  },
28
- "assert": {
29
- "type": "json"
30
- },
31
30
  "peerDependencies": {
32
31
  "react": "^16.0.0"
33
32
  },
34
33
  "dependencies": {
35
- "rollup": "^4.29.1",
34
+ "rollup": "^4.59.0",
36
35
  "rollup-plugin-babel": "^4.3.3"
37
36
  },
38
37
  "devDependencies": {
@@ -41,8 +40,9 @@
41
40
  "@babel/plugin-syntax-dynamic-import": "^7.2.0",
42
41
  "@babel/preset-env": "^7.5.5",
43
42
  "@babel/preset-react": "^7.0.0",
44
- "@testing-library/react": "^8.0.5",
43
+ "@testing-library/react": "^14.0.0",
45
44
  "cross-env": "^7.0.2",
45
+ "esbuild": "^0.27.4",
46
46
  "eslint": "^6.8.0",
47
47
  "eslint-config-prettier": "^6.7.0",
48
48
  "eslint-config-standard": "^14.1.0",
@@ -53,8 +53,8 @@
53
53
  "eslint-plugin-promise": "^4.2.1",
54
54
  "eslint-plugin-react": "^7.17.0",
55
55
  "eslint-plugin-standard": "^4.0.1",
56
- "jest": "^24.8.0",
57
- "jest-environment-jsdom-fourteen": "^0.1.0",
56
+ "jest": "^29.0.0",
57
+ "jest-environment-jsdom": "^30.3.0",
58
58
  "jest-prop-type-error": "^1.1.0",
59
59
  "prettier": "^2.0.4",
60
60
  "react": "^18",
@@ -63,9 +63,27 @@
63
63
  },
64
64
  "resolutions": {
65
65
  "braces": ">=3.0.3",
66
- "react-scripts": "^5.0.1"
66
+ "react-scripts": "^5.0.1",
67
+ "micromatch": ">=4.0.8",
68
+ "minimatch": ">=3.1.4",
69
+ "tough-cookie": ">=4.1.3",
70
+ "ajv": ">=6.14.0",
71
+ "qs": ">=6.14.1",
72
+ "lodash": ">=4.17.21",
73
+ "js-yaml": ">=4.1.0",
74
+ "@babel/helpers": ">=7.26.0",
75
+ "@babel/runtime": ">=7.26.0",
76
+ "flatted": ">=3.3.3",
77
+ "brace-expansion": ">=2.0.1",
78
+ "tmp": ">=0.2.3"
67
79
  },
68
80
  "files": [
69
81
  "dist"
70
- ]
82
+ ],
83
+ "jest": {
84
+ "testEnvironment": "jsdom",
85
+ "setupFilesAfterEnv": [
86
+ "<rootDir>/jest.setup.js"
87
+ ]
88
+ }
71
89
  }