apm-react-audio-player 1.1.3 → 2.0.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/dist/index.js +207 -44
- package/dist/index.modern.js +207 -44
- package/package.json +10 -6
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
|
+
var Hls = require('hls.js');
|
|
4
5
|
|
|
5
6
|
function _arrayLikeToArray(r, a) {
|
|
6
7
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -10,6 +11,14 @@ function _arrayLikeToArray(r, a) {
|
|
|
10
11
|
function _arrayWithHoles(r) {
|
|
11
12
|
if (Array.isArray(r)) return r;
|
|
12
13
|
}
|
|
14
|
+
function _defineProperty(e, r, t) {
|
|
15
|
+
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
16
|
+
value: t,
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true
|
|
20
|
+
}) : e[r] = t, e;
|
|
21
|
+
}
|
|
13
22
|
function _extends() {
|
|
14
23
|
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
15
24
|
for (var e = 1; e < arguments.length; e++) {
|
|
@@ -49,9 +58,44 @@ function _iterableToArrayLimit(r, l) {
|
|
|
49
58
|
function _nonIterableRest() {
|
|
50
59
|
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
51
60
|
}
|
|
61
|
+
function ownKeys(e, r) {
|
|
62
|
+
var t = Object.keys(e);
|
|
63
|
+
if (Object.getOwnPropertySymbols) {
|
|
64
|
+
var o = Object.getOwnPropertySymbols(e);
|
|
65
|
+
r && (o = o.filter(function (r) {
|
|
66
|
+
return Object.getOwnPropertyDescriptor(e, r).enumerable;
|
|
67
|
+
})), t.push.apply(t, o);
|
|
68
|
+
}
|
|
69
|
+
return t;
|
|
70
|
+
}
|
|
71
|
+
function _objectSpread2(e) {
|
|
72
|
+
for (var r = 1; r < arguments.length; r++) {
|
|
73
|
+
var t = null != arguments[r] ? arguments[r] : {};
|
|
74
|
+
r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
|
|
75
|
+
_defineProperty(e, r, t[r]);
|
|
76
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
|
|
77
|
+
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return e;
|
|
81
|
+
}
|
|
52
82
|
function _slicedToArray(r, e) {
|
|
53
83
|
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
54
84
|
}
|
|
85
|
+
function _toPrimitive(t, r) {
|
|
86
|
+
if ("object" != typeof t || !t) return t;
|
|
87
|
+
var e = t[Symbol.toPrimitive];
|
|
88
|
+
if (void 0 !== e) {
|
|
89
|
+
var i = e.call(t, r);
|
|
90
|
+
if ("object" != typeof i) return i;
|
|
91
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
92
|
+
}
|
|
93
|
+
return ("string" === r ? String : Number)(t);
|
|
94
|
+
}
|
|
95
|
+
function _toPropertyKey(t) {
|
|
96
|
+
var i = _toPrimitive(t, "string");
|
|
97
|
+
return "symbol" == typeof i ? i : i + "";
|
|
98
|
+
}
|
|
55
99
|
function _unsupportedIterableToArray(r, a) {
|
|
56
100
|
if (r) {
|
|
57
101
|
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
@@ -60,8 +104,11 @@ function _unsupportedIterableToArray(r, a) {
|
|
|
60
104
|
}
|
|
61
105
|
}
|
|
62
106
|
|
|
63
|
-
var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef
|
|
64
|
-
var
|
|
107
|
+
var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef) {
|
|
108
|
+
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
|
|
109
|
+
volumeCtrl = _ref.volumeCtrl,
|
|
110
|
+
initialDuration = _ref.initialDuration,
|
|
111
|
+
hlsRef = _ref.hlsRef;
|
|
65
112
|
var _useState = React.useState(false),
|
|
66
113
|
_useState2 = _slicedToArray(_useState, 2),
|
|
67
114
|
isPlaying = _useState2[0],
|
|
@@ -78,11 +125,12 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
|
|
|
78
125
|
_useState8 = _slicedToArray(_useState7, 2),
|
|
79
126
|
isFinishedPlaying = _useState8[0],
|
|
80
127
|
setIsFinishedPlaying = _useState8[1];
|
|
81
|
-
var animationRef = React.useRef();
|
|
128
|
+
var animationRef = React.useRef();
|
|
129
|
+
var pendingPlayAbortRef = React.useRef(null);
|
|
82
130
|
var _useState9 = React.useState(false),
|
|
83
|
-
|
|
84
|
-
isMuted =
|
|
85
|
-
setIsMuted =
|
|
131
|
+
_useState0 = _slicedToArray(_useState9, 2),
|
|
132
|
+
isMuted = _useState0[0],
|
|
133
|
+
setIsMuted = _useState0[1];
|
|
86
134
|
var isStream = audioRef.current && audioRef.current.duration === Infinity;
|
|
87
135
|
React.useEffect(function () {
|
|
88
136
|
if (currentTime === Number(duration)) {
|
|
@@ -141,6 +189,10 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
|
|
|
141
189
|
animationRef.current = window.requestAnimationFrame(_whilePlaying);
|
|
142
190
|
};
|
|
143
191
|
var pause = function pause() {
|
|
192
|
+
if (pendingPlayAbortRef.current) {
|
|
193
|
+
pendingPlayAbortRef.current();
|
|
194
|
+
pendingPlayAbortRef.current = null;
|
|
195
|
+
}
|
|
144
196
|
setIsPlaying(false);
|
|
145
197
|
audioRef.current.pause();
|
|
146
198
|
window.cancelAnimationFrame(animationRef.current);
|
|
@@ -152,26 +204,80 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
|
|
|
152
204
|
// pause()
|
|
153
205
|
// }
|
|
154
206
|
|
|
207
|
+
var _safePlay = function safePlay(audio) {
|
|
208
|
+
var promise = audio.play();
|
|
209
|
+
if (promise !== undefined) {
|
|
210
|
+
promise["catch"](function (err) {
|
|
211
|
+
if (err.name === 'NotAllowedError') {
|
|
212
|
+
setIsPlaying(false);
|
|
213
|
+
} else if (err.name === 'AbortError') {
|
|
214
|
+
// play() was interrupted by a concurrent load() (e.g. ReactAudioPlayerInner
|
|
215
|
+
// calling load() after a source change) — retry once canplay fires.
|
|
216
|
+
// If the audio element was already unlocked by a prior play() call within
|
|
217
|
+
// a user gesture (e.g. the Safari fix in AudioContext), this retry succeeds.
|
|
218
|
+
audio.addEventListener('canplay', function () {
|
|
219
|
+
return _safePlay(audio);
|
|
220
|
+
}, {
|
|
221
|
+
once: true
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
};
|
|
155
227
|
var play = function play() {
|
|
156
228
|
setIsPlaying(true);
|
|
157
229
|
setIsFinishedPlaying(false);
|
|
230
|
+
var elDuration = audioRef.current.duration;
|
|
231
|
+
var isLiveOrUnloaded = elDuration === Infinity || isNaN(elDuration);
|
|
232
|
+
if (isLiveOrUnloaded) {
|
|
233
|
+
var _audioRef$current$cur;
|
|
234
|
+
if (hlsRef !== null && hlsRef !== void 0 && hlsRef.current) {
|
|
235
|
+
var audio = audioRef.current;
|
|
236
|
+
var hls = hlsRef.current;
|
|
237
|
+
// If data is already buffered (e.g. resuming after pause), play immediately.
|
|
238
|
+
// Otherwise wait for the first fragment so Safari's audio decoder is warm
|
|
239
|
+
// before output starts, preventing the first syllable from being cut.
|
|
240
|
+
if (audio.buffered.length > 0) {
|
|
241
|
+
_safePlay(audio);
|
|
242
|
+
} else {
|
|
243
|
+
var _onFragBuffered = function onFragBuffered() {
|
|
244
|
+
pendingPlayAbortRef.current = null;
|
|
245
|
+
hls.off('hlsFragBuffered', _onFragBuffered);
|
|
246
|
+
_safePlay(audio);
|
|
247
|
+
};
|
|
248
|
+
pendingPlayAbortRef.current = function () {
|
|
249
|
+
return hls.off('hlsFragBuffered', _onFragBuffered);
|
|
250
|
+
};
|
|
251
|
+
hls.on('hlsFragBuffered', _onFragBuffered);
|
|
252
|
+
}
|
|
253
|
+
} else if (elDuration === Infinity && (_audioRef$current$cur = audioRef.current.currentSrc) !== null && _audioRef$current$cur !== void 0 && _audioRef$current$cur.split('?')[0].endsWith('.m3u8')) {
|
|
254
|
+
// Native live stream (no hls.js, e.g. iOS Safari): force a fresh manifest
|
|
255
|
+
// fetch so we don't play from a stale buffer position across a discontinuity.
|
|
256
|
+
var onCanPlay = function onCanPlay() {
|
|
257
|
+
pendingPlayAbortRef.current = null;
|
|
258
|
+
if (audioRef.current) _safePlay(audioRef.current);
|
|
259
|
+
};
|
|
260
|
+
pendingPlayAbortRef.current = function () {
|
|
261
|
+
var _audioRef$current;
|
|
262
|
+
return (_audioRef$current = audioRef.current) === null || _audioRef$current === void 0 ? void 0 : _audioRef$current.removeEventListener('canplay', onCanPlay);
|
|
263
|
+
};
|
|
264
|
+
audioRef.current.addEventListener('canplay', onCanPlay, {
|
|
265
|
+
once: true
|
|
266
|
+
});
|
|
267
|
+
audioRef.current.load();
|
|
268
|
+
} else {
|
|
269
|
+
// Finite audio not yet loaded (duration is NaN): call play() directly so
|
|
270
|
+
// Safari's user-gesture scope isn't lost waiting for an async canplay event.
|
|
271
|
+
_safePlay(audioRef.current);
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
_safePlay(audioRef.current);
|
|
158
275
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// position (e.g. 20s in), causing ads to start mid-stream. Calling load()
|
|
165
|
-
// here reconnects to the current live edge and gets a fresh manifest.
|
|
166
|
-
if (duration === Infinity) {
|
|
167
|
-
audioRef.current.load();
|
|
168
|
-
}
|
|
169
|
-
audioRef.current.play();
|
|
170
|
-
|
|
171
|
-
// Only start RAF loop for non-live streams with valid duration
|
|
172
|
-
var dur = audioRef.current.duration;
|
|
173
|
-
if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
|
|
174
|
-
animationRef.current = window.requestAnimationFrame(_whilePlaying);
|
|
276
|
+
// Only start RAF loop for non-live streams with valid duration
|
|
277
|
+
var dur = audioRef.current.duration;
|
|
278
|
+
if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
|
|
279
|
+
animationRef.current = window.requestAnimationFrame(_whilePlaying);
|
|
280
|
+
}
|
|
175
281
|
}
|
|
176
282
|
};
|
|
177
283
|
var toggleMute = function toggleMute() {
|
|
@@ -300,6 +406,13 @@ var Pause = function Pause() {
|
|
|
300
406
|
}));
|
|
301
407
|
};
|
|
302
408
|
|
|
409
|
+
var getHlsSrc = function getHlsSrc(audioSrc) {
|
|
410
|
+
var _urls$find;
|
|
411
|
+
var urls = Array.isArray(audioSrc) ? audioSrc : [audioSrc];
|
|
412
|
+
return (_urls$find = urls.find(function (url) {
|
|
413
|
+
return url && url.split('?')[0].endsWith('.m3u8');
|
|
414
|
+
})) !== null && _urls$find !== void 0 ? _urls$find : null;
|
|
415
|
+
};
|
|
303
416
|
var getTypeFromExtension = function getTypeFromExtension(url) {
|
|
304
417
|
var extension = url.split('.').pop().split('?')[0];
|
|
305
418
|
switch (extension) {
|
|
@@ -319,11 +432,15 @@ var getTypeFromExtension = function getTypeFromExtension(url) {
|
|
|
319
432
|
}
|
|
320
433
|
};
|
|
321
434
|
var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
|
|
322
|
-
var _props$audioPlayerRef, _props$progressBarRef;
|
|
323
|
-
//
|
|
324
|
-
var
|
|
325
|
-
var
|
|
326
|
-
|
|
435
|
+
var _props$audioPlayerRef, _props$progressBarRef, _props$hlsRef;
|
|
436
|
+
// Always call hooks unconditionally — use internal refs when props don't provide them
|
|
437
|
+
var internalAudioRef = React.useRef();
|
|
438
|
+
var internalProgressBarRef = React.useRef();
|
|
439
|
+
var internalHlsRef = React.useRef(null);
|
|
440
|
+
var hasInitializedRef = React.useRef(false);
|
|
441
|
+
// Track isPlaying via a ref so the source-change useEffect can read the
|
|
442
|
+
// current value without adding isPlaying to its dependency array.
|
|
443
|
+
var isPlayingRef = React.useRef(false);
|
|
327
444
|
var customStyles = props ? props.style : '';
|
|
328
445
|
var title = props.title,
|
|
329
446
|
audioSrc = props.audioSrc,
|
|
@@ -346,20 +463,66 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
|
|
|
346
463
|
forwardControl = props.forwardControl,
|
|
347
464
|
subtitle = props.subtitle,
|
|
348
465
|
prefix = props.prefix;
|
|
466
|
+
var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : internalAudioRef;
|
|
467
|
+
var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : internalProgressBarRef;
|
|
468
|
+
var hlsRef = (_props$hlsRef = props.hlsRef) !== null && _props$hlsRef !== void 0 ? _props$hlsRef : internalHlsRef;
|
|
469
|
+
var hlsSrcForRender = getHlsSrc(audioSrc);
|
|
470
|
+
var isHlsManaged = !!(hlsSrcForRender && Hls.isSupported());
|
|
471
|
+
|
|
472
|
+
// Keep isPlayingRef in sync on every render (runs before effects).
|
|
473
|
+
isPlayingRef.current = isPlaying;
|
|
349
474
|
var audioDuration = duration && !isNaN(duration) && calculateTime(duration);
|
|
350
475
|
var formatDuration = duration && !isNaN(duration) && audioDuration && formatCalculateTime(audioDuration);
|
|
351
476
|
|
|
352
|
-
//
|
|
353
|
-
//
|
|
477
|
+
// Manage audio source changes. For HLS sources hls.js owns the loading cycle;
|
|
478
|
+
// for everything else we fall back to the native load() path.
|
|
479
|
+
// JSON.stringify handles array-valued audioSrc comparisons by value.
|
|
354
480
|
React.useEffect(function () {
|
|
355
|
-
if (audioPlayerRef.current
|
|
481
|
+
if (!audioPlayerRef.current || !audioSrc) return;
|
|
482
|
+
if (hasInitializedRef.current) {
|
|
356
483
|
resetDuration === null || resetDuration === void 0 ? void 0 : resetDuration();
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Tear down any existing hls.js instance before re-evaluating the source.
|
|
487
|
+
if (hlsRef.current) {
|
|
488
|
+
hlsRef.current.destroy();
|
|
489
|
+
hlsRef.current = null;
|
|
490
|
+
}
|
|
491
|
+
var hlsSrc = getHlsSrc(audioSrc);
|
|
492
|
+
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
493
|
+
if (hlsSrc && Hls.isSupported()) {
|
|
494
|
+
var hls = new Hls(_objectSpread2({
|
|
495
|
+
liveSyncDurationCount: 3,
|
|
496
|
+
liveMaxLatencyDurationCount: 5,
|
|
497
|
+
enableWorker: !isSafari
|
|
498
|
+
}, isSafari && {
|
|
499
|
+
maxBufferHole: 2,
|
|
500
|
+
maxSeekHole: 2
|
|
501
|
+
}));
|
|
502
|
+
hls.loadSource(hlsSrc);
|
|
503
|
+
hls.attachMedia(audioPlayerRef.current);
|
|
504
|
+
hlsRef.current = hls;
|
|
505
|
+
} else {
|
|
506
|
+
// Non-HLS: call load() to prime the new source.
|
|
507
|
+
// Safari: skip load() when playing — the synchronous play() called in the
|
|
508
|
+
// gesture handler would be aborted (AbortError), losing the user activation token.
|
|
509
|
+
// Chrome/Firefox: always call load(); AudioContext defers play() via pendingPlayRef
|
|
510
|
+
// until after this effect, so load() and play() are sequenced with no concurrent abort.
|
|
511
|
+
if (hasInitializedRef.current && (!isSafari || !isPlayingRef.current)) {
|
|
512
|
+
try {
|
|
513
|
+
audioPlayerRef.current.load();
|
|
514
|
+
} catch (err) {
|
|
515
|
+
console.warn('Failed to reload audio source:', err);
|
|
516
|
+
}
|
|
361
517
|
}
|
|
362
518
|
}
|
|
519
|
+
hasInitializedRef.current = true;
|
|
520
|
+
return function () {
|
|
521
|
+
if (hlsRef.current) {
|
|
522
|
+
hlsRef.current.destroy();
|
|
523
|
+
hlsRef.current = null;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
363
526
|
}, [JSON.stringify(audioSrc)]);
|
|
364
527
|
|
|
365
528
|
// Set initial volume to 100%
|
|
@@ -379,16 +542,13 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
|
|
|
379
542
|
preload: "none",
|
|
380
543
|
onLoadedMetadata: onLoadedMetadata,
|
|
381
544
|
muted: isMuted
|
|
382
|
-
}, Array.isArray(audioSrc) ? audioSrc.map(function (
|
|
545
|
+
}, !isHlsManaged && (Array.isArray(audioSrc) ? audioSrc : [audioSrc]).map(function (url, i) {
|
|
383
546
|
return /*#__PURE__*/React.createElement("source", {
|
|
384
|
-
key:
|
|
385
|
-
src:
|
|
386
|
-
type: getTypeFromExtension(
|
|
547
|
+
key: i,
|
|
548
|
+
src: url,
|
|
549
|
+
type: getTypeFromExtension(url)
|
|
387
550
|
});
|
|
388
|
-
})
|
|
389
|
-
src: audioSrc,
|
|
390
|
-
type: getTypeFromExtension(audioSrc)
|
|
391
|
-
}) : null, "Your browser does not support the audio element."), /*#__PURE__*/React.createElement("div", {
|
|
551
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
392
552
|
className: "player-layout"
|
|
393
553
|
}, volumeCtrl && /*#__PURE__*/React.createElement("div", {
|
|
394
554
|
className: "player-controls-secondary-outer"
|
|
@@ -485,11 +645,13 @@ var ReactAudioPlayer = function ReactAudioPlayer(props) {
|
|
|
485
645
|
// references
|
|
486
646
|
var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : React.useRef(); // reference our audio component
|
|
487
647
|
var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : React.useRef(); // reference our progress bar
|
|
488
|
-
|
|
648
|
+
var hlsRef = React.useRef(null);
|
|
489
649
|
var customStyles = props ? props.style : '';
|
|
490
650
|
|
|
491
651
|
// hooks
|
|
492
|
-
var _useAudioPlayer = useAudioPlayer(audioPlayerRef, progressBarRef
|
|
652
|
+
var _useAudioPlayer = useAudioPlayer(audioPlayerRef, progressBarRef, {
|
|
653
|
+
hlsRef: hlsRef
|
|
654
|
+
}),
|
|
493
655
|
isPlaying = _useAudioPlayer.isPlaying,
|
|
494
656
|
currentTime = _useAudioPlayer.currentTime,
|
|
495
657
|
duration = _useAudioPlayer.duration,
|
|
@@ -506,6 +668,7 @@ var ReactAudioPlayer = function ReactAudioPlayer(props) {
|
|
|
506
668
|
return /*#__PURE__*/React.createElement(ReactAudioPlayerInner, _extends({}, props, {
|
|
507
669
|
audioPlayerRef: audioPlayerRef,
|
|
508
670
|
progressBarRef: progressBarRef,
|
|
671
|
+
hlsRef: hlsRef,
|
|
509
672
|
isPlaying: isPlaying,
|
|
510
673
|
isMuted: isMuted,
|
|
511
674
|
currentTime: currentTime,
|
package/dist/index.modern.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import Hls from 'hls.js';
|
|
2
3
|
|
|
3
4
|
function _arrayLikeToArray(r, a) {
|
|
4
5
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -8,6 +9,14 @@ function _arrayLikeToArray(r, a) {
|
|
|
8
9
|
function _arrayWithHoles(r) {
|
|
9
10
|
if (Array.isArray(r)) return r;
|
|
10
11
|
}
|
|
12
|
+
function _defineProperty(e, r, t) {
|
|
13
|
+
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
14
|
+
value: t,
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true
|
|
18
|
+
}) : e[r] = t, e;
|
|
19
|
+
}
|
|
11
20
|
function _extends() {
|
|
12
21
|
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
13
22
|
for (var e = 1; e < arguments.length; e++) {
|
|
@@ -47,9 +56,44 @@ function _iterableToArrayLimit(r, l) {
|
|
|
47
56
|
function _nonIterableRest() {
|
|
48
57
|
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
58
|
}
|
|
59
|
+
function ownKeys(e, r) {
|
|
60
|
+
var t = Object.keys(e);
|
|
61
|
+
if (Object.getOwnPropertySymbols) {
|
|
62
|
+
var o = Object.getOwnPropertySymbols(e);
|
|
63
|
+
r && (o = o.filter(function (r) {
|
|
64
|
+
return Object.getOwnPropertyDescriptor(e, r).enumerable;
|
|
65
|
+
})), t.push.apply(t, o);
|
|
66
|
+
}
|
|
67
|
+
return t;
|
|
68
|
+
}
|
|
69
|
+
function _objectSpread2(e) {
|
|
70
|
+
for (var r = 1; r < arguments.length; r++) {
|
|
71
|
+
var t = null != arguments[r] ? arguments[r] : {};
|
|
72
|
+
r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
|
|
73
|
+
_defineProperty(e, r, t[r]);
|
|
74
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
|
|
75
|
+
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return e;
|
|
79
|
+
}
|
|
50
80
|
function _slicedToArray(r, e) {
|
|
51
81
|
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
52
82
|
}
|
|
83
|
+
function _toPrimitive(t, r) {
|
|
84
|
+
if ("object" != typeof t || !t) return t;
|
|
85
|
+
var e = t[Symbol.toPrimitive];
|
|
86
|
+
if (void 0 !== e) {
|
|
87
|
+
var i = e.call(t, r);
|
|
88
|
+
if ("object" != typeof i) return i;
|
|
89
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
90
|
+
}
|
|
91
|
+
return ("string" === r ? String : Number)(t);
|
|
92
|
+
}
|
|
93
|
+
function _toPropertyKey(t) {
|
|
94
|
+
var i = _toPrimitive(t, "string");
|
|
95
|
+
return "symbol" == typeof i ? i : i + "";
|
|
96
|
+
}
|
|
53
97
|
function _unsupportedIterableToArray(r, a) {
|
|
54
98
|
if (r) {
|
|
55
99
|
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
@@ -58,8 +102,11 @@ function _unsupportedIterableToArray(r, a) {
|
|
|
58
102
|
}
|
|
59
103
|
}
|
|
60
104
|
|
|
61
|
-
var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef
|
|
62
|
-
var
|
|
105
|
+
var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef) {
|
|
106
|
+
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
|
|
107
|
+
volumeCtrl = _ref.volumeCtrl,
|
|
108
|
+
initialDuration = _ref.initialDuration,
|
|
109
|
+
hlsRef = _ref.hlsRef;
|
|
63
110
|
var _useState = useState(false),
|
|
64
111
|
_useState2 = _slicedToArray(_useState, 2),
|
|
65
112
|
isPlaying = _useState2[0],
|
|
@@ -76,11 +123,12 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
|
|
|
76
123
|
_useState8 = _slicedToArray(_useState7, 2),
|
|
77
124
|
isFinishedPlaying = _useState8[0],
|
|
78
125
|
setIsFinishedPlaying = _useState8[1];
|
|
79
|
-
var animationRef = useRef();
|
|
126
|
+
var animationRef = useRef();
|
|
127
|
+
var pendingPlayAbortRef = useRef(null);
|
|
80
128
|
var _useState9 = useState(false),
|
|
81
|
-
|
|
82
|
-
isMuted =
|
|
83
|
-
setIsMuted =
|
|
129
|
+
_useState0 = _slicedToArray(_useState9, 2),
|
|
130
|
+
isMuted = _useState0[0],
|
|
131
|
+
setIsMuted = _useState0[1];
|
|
84
132
|
var isStream = audioRef.current && audioRef.current.duration === Infinity;
|
|
85
133
|
useEffect(function () {
|
|
86
134
|
if (currentTime === Number(duration)) {
|
|
@@ -139,6 +187,10 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
|
|
|
139
187
|
animationRef.current = window.requestAnimationFrame(_whilePlaying);
|
|
140
188
|
};
|
|
141
189
|
var pause = function pause() {
|
|
190
|
+
if (pendingPlayAbortRef.current) {
|
|
191
|
+
pendingPlayAbortRef.current();
|
|
192
|
+
pendingPlayAbortRef.current = null;
|
|
193
|
+
}
|
|
142
194
|
setIsPlaying(false);
|
|
143
195
|
audioRef.current.pause();
|
|
144
196
|
window.cancelAnimationFrame(animationRef.current);
|
|
@@ -150,26 +202,80 @@ var useAudioPlayer = function useAudioPlayer(audioRef, progressBarRef, volumeCtr
|
|
|
150
202
|
// pause()
|
|
151
203
|
// }
|
|
152
204
|
|
|
205
|
+
var _safePlay = function safePlay(audio) {
|
|
206
|
+
var promise = audio.play();
|
|
207
|
+
if (promise !== undefined) {
|
|
208
|
+
promise["catch"](function (err) {
|
|
209
|
+
if (err.name === 'NotAllowedError') {
|
|
210
|
+
setIsPlaying(false);
|
|
211
|
+
} else if (err.name === 'AbortError') {
|
|
212
|
+
// play() was interrupted by a concurrent load() (e.g. ReactAudioPlayerInner
|
|
213
|
+
// calling load() after a source change) — retry once canplay fires.
|
|
214
|
+
// If the audio element was already unlocked by a prior play() call within
|
|
215
|
+
// a user gesture (e.g. the Safari fix in AudioContext), this retry succeeds.
|
|
216
|
+
audio.addEventListener('canplay', function () {
|
|
217
|
+
return _safePlay(audio);
|
|
218
|
+
}, {
|
|
219
|
+
once: true
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
};
|
|
153
225
|
var play = function play() {
|
|
154
226
|
setIsPlaying(true);
|
|
155
227
|
setIsFinishedPlaying(false);
|
|
228
|
+
var elDuration = audioRef.current.duration;
|
|
229
|
+
var isLiveOrUnloaded = elDuration === Infinity || isNaN(elDuration);
|
|
230
|
+
if (isLiveOrUnloaded) {
|
|
231
|
+
var _audioRef$current$cur;
|
|
232
|
+
if (hlsRef !== null && hlsRef !== void 0 && hlsRef.current) {
|
|
233
|
+
var audio = audioRef.current;
|
|
234
|
+
var hls = hlsRef.current;
|
|
235
|
+
// If data is already buffered (e.g. resuming after pause), play immediately.
|
|
236
|
+
// Otherwise wait for the first fragment so Safari's audio decoder is warm
|
|
237
|
+
// before output starts, preventing the first syllable from being cut.
|
|
238
|
+
if (audio.buffered.length > 0) {
|
|
239
|
+
_safePlay(audio);
|
|
240
|
+
} else {
|
|
241
|
+
var _onFragBuffered = function onFragBuffered() {
|
|
242
|
+
pendingPlayAbortRef.current = null;
|
|
243
|
+
hls.off('hlsFragBuffered', _onFragBuffered);
|
|
244
|
+
_safePlay(audio);
|
|
245
|
+
};
|
|
246
|
+
pendingPlayAbortRef.current = function () {
|
|
247
|
+
return hls.off('hlsFragBuffered', _onFragBuffered);
|
|
248
|
+
};
|
|
249
|
+
hls.on('hlsFragBuffered', _onFragBuffered);
|
|
250
|
+
}
|
|
251
|
+
} else if (elDuration === Infinity && (_audioRef$current$cur = audioRef.current.currentSrc) !== null && _audioRef$current$cur !== void 0 && _audioRef$current$cur.split('?')[0].endsWith('.m3u8')) {
|
|
252
|
+
// Native live stream (no hls.js, e.g. iOS Safari): force a fresh manifest
|
|
253
|
+
// fetch so we don't play from a stale buffer position across a discontinuity.
|
|
254
|
+
var onCanPlay = function onCanPlay() {
|
|
255
|
+
pendingPlayAbortRef.current = null;
|
|
256
|
+
if (audioRef.current) _safePlay(audioRef.current);
|
|
257
|
+
};
|
|
258
|
+
pendingPlayAbortRef.current = function () {
|
|
259
|
+
var _audioRef$current;
|
|
260
|
+
return (_audioRef$current = audioRef.current) === null || _audioRef$current === void 0 ? void 0 : _audioRef$current.removeEventListener('canplay', onCanPlay);
|
|
261
|
+
};
|
|
262
|
+
audioRef.current.addEventListener('canplay', onCanPlay, {
|
|
263
|
+
once: true
|
|
264
|
+
});
|
|
265
|
+
audioRef.current.load();
|
|
266
|
+
} else {
|
|
267
|
+
// Finite audio not yet loaded (duration is NaN): call play() directly so
|
|
268
|
+
// Safari's user-gesture scope isn't lost waiting for an async canplay event.
|
|
269
|
+
_safePlay(audioRef.current);
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
_safePlay(audioRef.current);
|
|
156
273
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// position (e.g. 20s in), causing ads to start mid-stream. Calling load()
|
|
163
|
-
// here reconnects to the current live edge and gets a fresh manifest.
|
|
164
|
-
if (duration === Infinity) {
|
|
165
|
-
audioRef.current.load();
|
|
166
|
-
}
|
|
167
|
-
audioRef.current.play();
|
|
168
|
-
|
|
169
|
-
// Only start RAF loop for non-live streams with valid duration
|
|
170
|
-
var dur = audioRef.current.duration;
|
|
171
|
-
if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
|
|
172
|
-
animationRef.current = window.requestAnimationFrame(_whilePlaying);
|
|
274
|
+
// Only start RAF loop for non-live streams with valid duration
|
|
275
|
+
var dur = audioRef.current.duration;
|
|
276
|
+
if (dur !== Infinity && !isNaN(dur) && isFinite(dur)) {
|
|
277
|
+
animationRef.current = window.requestAnimationFrame(_whilePlaying);
|
|
278
|
+
}
|
|
173
279
|
}
|
|
174
280
|
};
|
|
175
281
|
var toggleMute = function toggleMute() {
|
|
@@ -298,6 +404,13 @@ var Pause = function Pause() {
|
|
|
298
404
|
}));
|
|
299
405
|
};
|
|
300
406
|
|
|
407
|
+
var getHlsSrc = function getHlsSrc(audioSrc) {
|
|
408
|
+
var _urls$find;
|
|
409
|
+
var urls = Array.isArray(audioSrc) ? audioSrc : [audioSrc];
|
|
410
|
+
return (_urls$find = urls.find(function (url) {
|
|
411
|
+
return url && url.split('?')[0].endsWith('.m3u8');
|
|
412
|
+
})) !== null && _urls$find !== void 0 ? _urls$find : null;
|
|
413
|
+
};
|
|
301
414
|
var getTypeFromExtension = function getTypeFromExtension(url) {
|
|
302
415
|
var extension = url.split('.').pop().split('?')[0];
|
|
303
416
|
switch (extension) {
|
|
@@ -317,11 +430,15 @@ var getTypeFromExtension = function getTypeFromExtension(url) {
|
|
|
317
430
|
}
|
|
318
431
|
};
|
|
319
432
|
var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
|
|
320
|
-
var _props$audioPlayerRef, _props$progressBarRef;
|
|
321
|
-
//
|
|
322
|
-
var
|
|
323
|
-
var
|
|
324
|
-
|
|
433
|
+
var _props$audioPlayerRef, _props$progressBarRef, _props$hlsRef;
|
|
434
|
+
// Always call hooks unconditionally — use internal refs when props don't provide them
|
|
435
|
+
var internalAudioRef = useRef();
|
|
436
|
+
var internalProgressBarRef = useRef();
|
|
437
|
+
var internalHlsRef = useRef(null);
|
|
438
|
+
var hasInitializedRef = useRef(false);
|
|
439
|
+
// Track isPlaying via a ref so the source-change useEffect can read the
|
|
440
|
+
// current value without adding isPlaying to its dependency array.
|
|
441
|
+
var isPlayingRef = useRef(false);
|
|
325
442
|
var customStyles = props ? props.style : '';
|
|
326
443
|
var title = props.title,
|
|
327
444
|
audioSrc = props.audioSrc,
|
|
@@ -344,20 +461,66 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
|
|
|
344
461
|
forwardControl = props.forwardControl,
|
|
345
462
|
subtitle = props.subtitle,
|
|
346
463
|
prefix = props.prefix;
|
|
464
|
+
var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : internalAudioRef;
|
|
465
|
+
var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : internalProgressBarRef;
|
|
466
|
+
var hlsRef = (_props$hlsRef = props.hlsRef) !== null && _props$hlsRef !== void 0 ? _props$hlsRef : internalHlsRef;
|
|
467
|
+
var hlsSrcForRender = getHlsSrc(audioSrc);
|
|
468
|
+
var isHlsManaged = !!(hlsSrcForRender && Hls.isSupported());
|
|
469
|
+
|
|
470
|
+
// Keep isPlayingRef in sync on every render (runs before effects).
|
|
471
|
+
isPlayingRef.current = isPlaying;
|
|
347
472
|
var audioDuration = duration && !isNaN(duration) && calculateTime(duration);
|
|
348
473
|
var formatDuration = duration && !isNaN(duration) && audioDuration && formatCalculateTime(audioDuration);
|
|
349
474
|
|
|
350
|
-
//
|
|
351
|
-
//
|
|
475
|
+
// Manage audio source changes. For HLS sources hls.js owns the loading cycle;
|
|
476
|
+
// for everything else we fall back to the native load() path.
|
|
477
|
+
// JSON.stringify handles array-valued audioSrc comparisons by value.
|
|
352
478
|
useEffect(function () {
|
|
353
|
-
if (audioPlayerRef.current
|
|
479
|
+
if (!audioPlayerRef.current || !audioSrc) return;
|
|
480
|
+
if (hasInitializedRef.current) {
|
|
354
481
|
resetDuration === null || resetDuration === void 0 ? void 0 : resetDuration();
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Tear down any existing hls.js instance before re-evaluating the source.
|
|
485
|
+
if (hlsRef.current) {
|
|
486
|
+
hlsRef.current.destroy();
|
|
487
|
+
hlsRef.current = null;
|
|
488
|
+
}
|
|
489
|
+
var hlsSrc = getHlsSrc(audioSrc);
|
|
490
|
+
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
491
|
+
if (hlsSrc && Hls.isSupported()) {
|
|
492
|
+
var hls = new Hls(_objectSpread2({
|
|
493
|
+
liveSyncDurationCount: 3,
|
|
494
|
+
liveMaxLatencyDurationCount: 5,
|
|
495
|
+
enableWorker: !isSafari
|
|
496
|
+
}, isSafari && {
|
|
497
|
+
maxBufferHole: 2,
|
|
498
|
+
maxSeekHole: 2
|
|
499
|
+
}));
|
|
500
|
+
hls.loadSource(hlsSrc);
|
|
501
|
+
hls.attachMedia(audioPlayerRef.current);
|
|
502
|
+
hlsRef.current = hls;
|
|
503
|
+
} else {
|
|
504
|
+
// Non-HLS: call load() to prime the new source.
|
|
505
|
+
// Safari: skip load() when playing — the synchronous play() called in the
|
|
506
|
+
// gesture handler would be aborted (AbortError), losing the user activation token.
|
|
507
|
+
// Chrome/Firefox: always call load(); AudioContext defers play() via pendingPlayRef
|
|
508
|
+
// until after this effect, so load() and play() are sequenced with no concurrent abort.
|
|
509
|
+
if (hasInitializedRef.current && (!isSafari || !isPlayingRef.current)) {
|
|
510
|
+
try {
|
|
511
|
+
audioPlayerRef.current.load();
|
|
512
|
+
} catch (err) {
|
|
513
|
+
console.warn('Failed to reload audio source:', err);
|
|
514
|
+
}
|
|
359
515
|
}
|
|
360
516
|
}
|
|
517
|
+
hasInitializedRef.current = true;
|
|
518
|
+
return function () {
|
|
519
|
+
if (hlsRef.current) {
|
|
520
|
+
hlsRef.current.destroy();
|
|
521
|
+
hlsRef.current = null;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
361
524
|
}, [JSON.stringify(audioSrc)]);
|
|
362
525
|
|
|
363
526
|
// Set initial volume to 100%
|
|
@@ -377,16 +540,13 @@ var ReactAudioPlayerInner = function ReactAudioPlayerInner(props) {
|
|
|
377
540
|
preload: "none",
|
|
378
541
|
onLoadedMetadata: onLoadedMetadata,
|
|
379
542
|
muted: isMuted
|
|
380
|
-
}, Array.isArray(audioSrc) ? audioSrc.map(function (
|
|
543
|
+
}, !isHlsManaged && (Array.isArray(audioSrc) ? audioSrc : [audioSrc]).map(function (url, i) {
|
|
381
544
|
return /*#__PURE__*/React.createElement("source", {
|
|
382
|
-
key:
|
|
383
|
-
src:
|
|
384
|
-
type: getTypeFromExtension(
|
|
545
|
+
key: i,
|
|
546
|
+
src: url,
|
|
547
|
+
type: getTypeFromExtension(url)
|
|
385
548
|
});
|
|
386
|
-
})
|
|
387
|
-
src: audioSrc,
|
|
388
|
-
type: getTypeFromExtension(audioSrc)
|
|
389
|
-
}) : null, "Your browser does not support the audio element."), /*#__PURE__*/React.createElement("div", {
|
|
549
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
390
550
|
className: "player-layout"
|
|
391
551
|
}, volumeCtrl && /*#__PURE__*/React.createElement("div", {
|
|
392
552
|
className: "player-controls-secondary-outer"
|
|
@@ -483,11 +643,13 @@ var ReactAudioPlayer = function ReactAudioPlayer(props) {
|
|
|
483
643
|
// references
|
|
484
644
|
var audioPlayerRef = (_props$audioPlayerRef = props.audioPlayerRef) !== null && _props$audioPlayerRef !== void 0 ? _props$audioPlayerRef : useRef(); // reference our audio component
|
|
485
645
|
var progressBarRef = (_props$progressBarRef = props.progressBarRef) !== null && _props$progressBarRef !== void 0 ? _props$progressBarRef : useRef(); // reference our progress bar
|
|
486
|
-
|
|
646
|
+
var hlsRef = useRef(null);
|
|
487
647
|
var customStyles = props ? props.style : '';
|
|
488
648
|
|
|
489
649
|
// hooks
|
|
490
|
-
var _useAudioPlayer = useAudioPlayer(audioPlayerRef, progressBarRef
|
|
650
|
+
var _useAudioPlayer = useAudioPlayer(audioPlayerRef, progressBarRef, {
|
|
651
|
+
hlsRef: hlsRef
|
|
652
|
+
}),
|
|
491
653
|
isPlaying = _useAudioPlayer.isPlaying,
|
|
492
654
|
currentTime = _useAudioPlayer.currentTime,
|
|
493
655
|
duration = _useAudioPlayer.duration,
|
|
@@ -504,6 +666,7 @@ var ReactAudioPlayer = function ReactAudioPlayer(props) {
|
|
|
504
666
|
return /*#__PURE__*/React.createElement(ReactAudioPlayerInner, _extends({}, props, {
|
|
505
667
|
audioPlayerRef: audioPlayerRef,
|
|
506
668
|
progressBarRef: progressBarRef,
|
|
669
|
+
hlsRef: hlsRef,
|
|
507
670
|
isPlaying: isPlaying,
|
|
508
671
|
isMuted: isMuted,
|
|
509
672
|
currentTime: currentTime,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apm-react-audio-player",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"author": "Jason Phan <jphan@mpr.org>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"react": "^16.0.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"hls.js": "^1.6.16",
|
|
34
35
|
"rollup": "^4.59.0",
|
|
35
36
|
"rollup-plugin-babel": "^4.3.3"
|
|
36
37
|
},
|
|
@@ -69,12 +70,15 @@
|
|
|
69
70
|
"tough-cookie": ">=4.1.3",
|
|
70
71
|
"ajv": ">=6.14.0",
|
|
71
72
|
"qs": ">=6.14.1",
|
|
72
|
-
"lodash": ">=4.
|
|
73
|
+
"lodash": ">=4.18.0",
|
|
73
74
|
"js-yaml": ">=4.1.0",
|
|
74
|
-
"@babel/helpers": ">=7.26.
|
|
75
|
-
"@babel/runtime": ">=7.26.
|
|
76
|
-
"
|
|
77
|
-
"
|
|
75
|
+
"@babel/helpers": ">=7.26.10",
|
|
76
|
+
"@babel/runtime": ">=7.26.10",
|
|
77
|
+
"@babel/plugin-transform-modules-systemjs": ">=7.29.0",
|
|
78
|
+
"flatted": ">=3.4.2",
|
|
79
|
+
"brace-expansion": ">=5.0.5",
|
|
80
|
+
"fast-uri": ">=3.1.2",
|
|
81
|
+
"picomatch": ">=2.3.2",
|
|
78
82
|
"tmp": ">=0.2.3"
|
|
79
83
|
},
|
|
80
84
|
"files": [
|