@cycjimmy/jsmpeg-player 6.1.6 → 6.1.8
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 +3 -3
- package/dist/jsmpeg-player.cjs +245 -30
- package/dist/jsmpeg-player.esm.js +245 -30
- package/dist/jsmpeg-player.umd.js +245 -30
- package/dist/jsmpeg-player.umd.min.js +2 -2
- package/package.json +1 -1
|
@@ -233,10 +233,10 @@ var VideoElement = /*#__PURE__*/function () {
|
|
|
233
233
|
this.els.playButton.classList.add(_style.hidden);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
// Set up the unlock audio button for
|
|
237
|
-
//
|
|
238
|
-
// streaming players we set up a muted speaker
|
|
239
|
-
// others, we can simply use the play button.
|
|
236
|
+
// Set up the unlock audio button for the browsers that do not allow audio
|
|
237
|
+
// before a user action (iOS, and Chrome/Safari under their autoplay
|
|
238
|
+
// policy). For autoplay or streaming players we set up a muted speaker
|
|
239
|
+
// icon as the button. For all others, we can simply use the play button.
|
|
240
240
|
if (this.player.audioOut && !this.player.audioOut.unlocked) {
|
|
241
241
|
var unlockAudioElement = this.els.wrapper;
|
|
242
242
|
if (this.options.autoplay || this.player.options.streaming) {
|
|
@@ -249,6 +249,12 @@ var VideoElement = /*#__PURE__*/function () {
|
|
|
249
249
|
this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement);
|
|
250
250
|
unlockAudioElement.addEventListener('touchstart', this.unlockAudioBound, false);
|
|
251
251
|
unlockAudioElement.addEventListener('click', this.unlockAudioBound, true);
|
|
252
|
+
|
|
253
|
+
// Any gesture on the page may unlock the audio, so the icon has to go
|
|
254
|
+
// away even when it was not the element that was clicked.
|
|
255
|
+
this.player.audioOut.onUnlock = function () {
|
|
256
|
+
return _this.onAudioUnlocked(unlockAudioElement);
|
|
257
|
+
};
|
|
252
258
|
}
|
|
253
259
|
};
|
|
254
260
|
_proto.initPlayButton = function initPlayButton() {
|
|
@@ -275,6 +281,15 @@ var VideoElement = /*#__PURE__*/function () {
|
|
|
275
281
|
});
|
|
276
282
|
});
|
|
277
283
|
};
|
|
284
|
+
_proto.onAudioUnlocked = function onAudioUnlocked(element) {
|
|
285
|
+
if (this.els.unmuteButton) {
|
|
286
|
+
this.els.unmuteButton.classList.add(_style.hidden);
|
|
287
|
+
}
|
|
288
|
+
if (element && this.unlockAudioBound) {
|
|
289
|
+
element.removeEventListener('touchstart', this.unlockAudioBound);
|
|
290
|
+
element.removeEventListener('click', this.unlockAudioBound);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
278
293
|
_proto.onUnlockAudio = function onUnlockAudio(element, ev) {
|
|
279
294
|
var _this3 = this;
|
|
280
295
|
if (this.els.unmuteButton) {
|
|
@@ -282,11 +297,7 @@ var VideoElement = /*#__PURE__*/function () {
|
|
|
282
297
|
ev.stopPropagation();
|
|
283
298
|
}
|
|
284
299
|
this.player.audioOut.unlock(function () {
|
|
285
|
-
|
|
286
|
-
_this3.els.unmuteButton.classList.add(_style.hidden);
|
|
287
|
-
}
|
|
288
|
-
element.removeEventListener('touchstart', _this3.unlockAudioBound);
|
|
289
|
-
element.removeEventListener('click', _this3.unlockAudioBound);
|
|
300
|
+
return _this3.onAudioUnlocked(element);
|
|
290
301
|
});
|
|
291
302
|
};
|
|
292
303
|
_proto.onClick = function onClick() {
|
|
@@ -391,6 +402,11 @@ var AjaxSource = /*#__PURE__*/function () {
|
|
|
391
402
|
// Nothing to do here
|
|
392
403
|
};
|
|
393
404
|
_proto.destroy = function destroy() {
|
|
405
|
+
// destroy() may run before start(), e.g. when a player is torn down while
|
|
406
|
+
// the WASM module is still compiling and the source was never started.
|
|
407
|
+
if (!this.request) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
394
410
|
this.request.abort();
|
|
395
411
|
};
|
|
396
412
|
_proto.onProgress = function onProgress(ev) {
|
|
@@ -585,6 +601,12 @@ var WSSource = /*#__PURE__*/function () {
|
|
|
585
601
|
_proto.destroy = function destroy() {
|
|
586
602
|
clearTimeout(this.reconnectTimeoutId);
|
|
587
603
|
this.shouldAttemptReconnect = false;
|
|
604
|
+
|
|
605
|
+
// destroy() may run before start(), e.g. when a player is torn down while
|
|
606
|
+
// the WASM module is still compiling and the socket was never opened.
|
|
607
|
+
if (!this.socket) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
588
610
|
this.socket.close();
|
|
589
611
|
};
|
|
590
612
|
_proto.start = function start() {
|
|
@@ -4036,11 +4058,15 @@ var MP2WASM = /*#__PURE__*/function (_BaseDecoder) {
|
|
|
4036
4058
|
_this.bufferSize = options.audioBufferSize || 128 * 1024;
|
|
4037
4059
|
_this.bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
|
|
4038
4060
|
_this.sampleRate = 0;
|
|
4061
|
+
_this.destroyed = false;
|
|
4039
4062
|
return _this;
|
|
4040
4063
|
}
|
|
4041
4064
|
_inheritsLoose(MP2WASM, _BaseDecoder);
|
|
4042
4065
|
var _proto = MP2WASM.prototype;
|
|
4043
4066
|
_proto.initializeWasmDecoder = function initializeWasmDecoder() {
|
|
4067
|
+
if (this.destroyed) {
|
|
4068
|
+
return;
|
|
4069
|
+
}
|
|
4044
4070
|
if (!this.module.instance) {
|
|
4045
4071
|
console.warn('JSMpeg: WASM module not compiled yet');
|
|
4046
4072
|
return;
|
|
@@ -4050,10 +4076,23 @@ var MP2WASM = /*#__PURE__*/function (_BaseDecoder) {
|
|
|
4050
4076
|
this.decoder = this.functions._mp2_decoder_create(this.bufferSize, this.bufferMode);
|
|
4051
4077
|
};
|
|
4052
4078
|
_proto.destroy = function destroy() {
|
|
4079
|
+
// A source can still deliver one last chunk inside the very callback that
|
|
4080
|
+
// destroys the player, so nothing may reach the module from now on.
|
|
4081
|
+
this.destroyed = true;
|
|
4053
4082
|
if (!this.decoder) {
|
|
4054
4083
|
return;
|
|
4055
4084
|
}
|
|
4056
4085
|
this.functions._mp2_decoder_destroy(this.decoder);
|
|
4086
|
+
// The pointer refers to a freed block now, and the module may hand the same
|
|
4087
|
+
// address to the decoder of the next player: it must never be used again.
|
|
4088
|
+
this.decoder = null;
|
|
4089
|
+
this.canPlay = false;
|
|
4090
|
+
};
|
|
4091
|
+
_proto.write = function write(pts, buffers) {
|
|
4092
|
+
if (this.destroyed) {
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
_BaseDecoder.prototype.write.call(this, pts, buffers);
|
|
4057
4096
|
};
|
|
4058
4097
|
_proto.bufferGetIndex = function bufferGetIndex() {
|
|
4059
4098
|
if (!this.decoder) {
|
|
@@ -4069,9 +4108,16 @@ var MP2WASM = /*#__PURE__*/function (_BaseDecoder) {
|
|
|
4069
4108
|
this.functions._mp2_decoder_set_index(this.decoder, index);
|
|
4070
4109
|
};
|
|
4071
4110
|
_proto.bufferWrite = function bufferWrite(buffers) {
|
|
4111
|
+
if (this.destroyed) {
|
|
4112
|
+
return 0;
|
|
4113
|
+
}
|
|
4072
4114
|
if (!this.decoder) {
|
|
4073
4115
|
this.initializeWasmDecoder();
|
|
4074
4116
|
}
|
|
4117
|
+
if (!this.decoder) {
|
|
4118
|
+
// The module is not compiled yet; nothing can be written.
|
|
4119
|
+
return 0;
|
|
4120
|
+
}
|
|
4075
4121
|
var totalLength = 0;
|
|
4076
4122
|
for (var i = 0; i < buffers.length; i++) {
|
|
4077
4123
|
totalLength += buffers[i].length;
|
|
@@ -4428,6 +4474,9 @@ var CanvasRenderer = /*#__PURE__*/function () {
|
|
|
4428
4474
|
return CanvasRenderer;
|
|
4429
4475
|
}();
|
|
4430
4476
|
|
|
4477
|
+
// Chrome grants the user activation on pointerup/touchend, not on the
|
|
4478
|
+
// touchstart/pointerdown that precedes them, so the touch events are paired.
|
|
4479
|
+
var UNLOCK_EVENTS = ['pointerdown', 'pointerup', 'keydown', 'touchend'];
|
|
4431
4480
|
var WebAudioOut = /*#__PURE__*/function () {
|
|
4432
4481
|
function WebAudioOut() {
|
|
4433
4482
|
this.context = WebAudioOut.CachedContext = WebAudioOut.CachedContext || new (window.AudioContext || window.webkitAudioContext)();
|
|
@@ -4443,13 +4492,27 @@ var WebAudioOut = /*#__PURE__*/function () {
|
|
|
4443
4492
|
this.wallclockStartTime = 0;
|
|
4444
4493
|
this.volume = 1;
|
|
4445
4494
|
this.enabled = true;
|
|
4446
|
-
this.
|
|
4495
|
+
this.sources = [];
|
|
4496
|
+
this.skippedAudio = false;
|
|
4497
|
+
this.destroyed = false;
|
|
4498
|
+
this.gestureListener = null;
|
|
4499
|
+
this.unlockCallback = null;
|
|
4500
|
+
this.onUnlock = null;
|
|
4501
|
+
this.unlocked = !WebAudioOut.NeedsUnlocking() && WebAudioOut.IsRunning(this.context);
|
|
4447
4502
|
Object.defineProperty(this, 'enqueuedTime', {
|
|
4448
4503
|
get: this.getEnqueuedTime
|
|
4449
4504
|
});
|
|
4505
|
+
if (!this.unlocked) {
|
|
4506
|
+
this.listenForGesture();
|
|
4507
|
+
}
|
|
4450
4508
|
}
|
|
4451
4509
|
var _proto = WebAudioOut.prototype;
|
|
4452
4510
|
_proto.destroy = function destroy() {
|
|
4511
|
+
this.destroyed = true;
|
|
4512
|
+
this.unlockCallback = null;
|
|
4513
|
+
this.onUnlock = null;
|
|
4514
|
+
this.removeGestureListener();
|
|
4515
|
+
this.stopSources();
|
|
4453
4516
|
this.gain.disconnect();
|
|
4454
4517
|
this.context._connections--;
|
|
4455
4518
|
if (this.context._connections === 0) {
|
|
@@ -4458,20 +4521,46 @@ var WebAudioOut = /*#__PURE__*/function () {
|
|
|
4458
4521
|
}
|
|
4459
4522
|
};
|
|
4460
4523
|
_proto.play = function play(sampleRate, left, right) {
|
|
4461
|
-
|
|
4524
|
+
var _this = this;
|
|
4525
|
+
if (!this.enabled || this.destroyed) {
|
|
4462
4526
|
return;
|
|
4463
4527
|
}
|
|
4464
4528
|
|
|
4465
|
-
//
|
|
4466
|
-
//
|
|
4467
|
-
|
|
4529
|
+
// Browsers that need no explicit unlock resume the context by themselves
|
|
4530
|
+
// once the page has been interacted with, so the unlock does not necessarily
|
|
4531
|
+
// go through unlock(). iOS keeps requiring the gesture.
|
|
4532
|
+
if (!this.unlocked && !WebAudioOut.NeedsUnlocking() && WebAudioOut.IsRunning(this.context)) {
|
|
4533
|
+
this.onUnlocked();
|
|
4534
|
+
}
|
|
4535
|
+
|
|
4536
|
+
// A context that is not running, or that has not been unlocked yet, cannot
|
|
4537
|
+
// play anything: the browser autoplay policy keeps it suspended until the
|
|
4538
|
+
// page has received a user gesture, and its currentTime does not advance
|
|
4539
|
+
// meanwhile. Scheduling buffers against it would queue all the audio
|
|
4540
|
+
// decoded in the meantime and play it back from the very beginning once
|
|
4541
|
+
// the context resumes. We simply advance the start time to "fake"
|
|
4542
|
+
// actually playing audio instead. This will keep the video in sync.
|
|
4543
|
+
if (!this.isPlayable()) {
|
|
4544
|
+
this.listenForGesture();
|
|
4468
4545
|
var ts = Now();
|
|
4469
4546
|
if (this.wallclockStartTime < ts) {
|
|
4470
4547
|
this.wallclockStartTime = ts;
|
|
4471
4548
|
}
|
|
4472
4549
|
this.wallclockStartTime += left.length / sampleRate;
|
|
4550
|
+
this.skippedAudio = true;
|
|
4473
4551
|
return;
|
|
4474
4552
|
}
|
|
4553
|
+
|
|
4554
|
+
// The audio is available again: whatever was queued while it could not be
|
|
4555
|
+
// played is dropped and the time base is rebuilt, so the audio joins the
|
|
4556
|
+
// picture at the current point in time instead of staying behind it. The
|
|
4557
|
+
// audio decoded meanwhile is lost once, which is what keeps it in sync
|
|
4558
|
+
// with the picture.
|
|
4559
|
+
if (this.skippedAudio) {
|
|
4560
|
+
this.skippedAudio = false;
|
|
4561
|
+
this.stopSources();
|
|
4562
|
+
this.resetEnqueuedTime();
|
|
4563
|
+
}
|
|
4475
4564
|
this.gain.gain.value = this.volume;
|
|
4476
4565
|
var buffer = this.context.createBuffer(2, left.length, sampleRate);
|
|
4477
4566
|
buffer.getChannelData(0).set(left);
|
|
@@ -4479,6 +4568,13 @@ var WebAudioOut = /*#__PURE__*/function () {
|
|
|
4479
4568
|
var source = this.context.createBufferSource();
|
|
4480
4569
|
source.buffer = buffer;
|
|
4481
4570
|
source.connect(this.destination);
|
|
4571
|
+
source.onended = function () {
|
|
4572
|
+
var index = _this.sources.indexOf(source);
|
|
4573
|
+
if (index !== -1) {
|
|
4574
|
+
_this.sources.splice(index, 1);
|
|
4575
|
+
}
|
|
4576
|
+
};
|
|
4577
|
+
this.sources.push(source);
|
|
4482
4578
|
var now = this.context.currentTime;
|
|
4483
4579
|
var duration = buffer.duration;
|
|
4484
4580
|
if (this.startTime < now) {
|
|
@@ -4490,11 +4586,13 @@ var WebAudioOut = /*#__PURE__*/function () {
|
|
|
4490
4586
|
this.wallclockStartTime += duration;
|
|
4491
4587
|
};
|
|
4492
4588
|
_proto.stop = function stop() {
|
|
4493
|
-
//
|
|
4494
|
-
//
|
|
4495
|
-
//
|
|
4496
|
-
//
|
|
4589
|
+
// Cut off everything that is still queued: the gain alone would only mute
|
|
4590
|
+
// the buffers that are already scheduled, and they would become audible
|
|
4591
|
+
// again as soon as play() restores it. The time base is rebuilt so the
|
|
4592
|
+
// next buffer joins the current context time instead of the old queue.
|
|
4497
4593
|
this.gain.gain.value = 0;
|
|
4594
|
+
this.stopSources();
|
|
4595
|
+
this.resetEnqueuedTime();
|
|
4498
4596
|
};
|
|
4499
4597
|
_proto.getEnqueuedTime = function getEnqueuedTime() {
|
|
4500
4598
|
// The AudioContext.currentTime is only updated every so often, so if we
|
|
@@ -4505,16 +4603,65 @@ var WebAudioOut = /*#__PURE__*/function () {
|
|
|
4505
4603
|
this.startTime = this.context.currentTime;
|
|
4506
4604
|
this.wallclockStartTime = Now();
|
|
4507
4605
|
};
|
|
4606
|
+
_proto.isPlayable = function isPlayable() {
|
|
4607
|
+
return this.unlocked && WebAudioOut.IsRunning(this.context);
|
|
4608
|
+
};
|
|
4609
|
+
_proto.stopSources = function stopSources() {
|
|
4610
|
+
var sources = this.sources;
|
|
4611
|
+
this.sources = [];
|
|
4612
|
+
sources.forEach(function (source) {
|
|
4613
|
+
try {
|
|
4614
|
+
source.stop();
|
|
4615
|
+
} catch (e) {
|
|
4616
|
+
// The source has already ended and cannot be stopped any more.
|
|
4617
|
+
}
|
|
4618
|
+
try {
|
|
4619
|
+
source.disconnect();
|
|
4620
|
+
} catch (e) {
|
|
4621
|
+
// It was never connected to anything.
|
|
4622
|
+
}
|
|
4623
|
+
});
|
|
4624
|
+
};
|
|
4625
|
+
_proto.listenForGesture = function listenForGesture() {
|
|
4626
|
+
var _this2 = this;
|
|
4627
|
+
if (this.gestureListener || this.isPlayable() || this.destroyed || WebAudioOut.NeedsUnlocking() || typeof document === 'undefined') {
|
|
4628
|
+
return;
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
// Chrome and desktop Safari keep the context suspended until it is resumed
|
|
4632
|
+
// inside a user gesture, and they do not resume it by themselves when the
|
|
4633
|
+
// page is tapped somewhere else. iOS keeps requiring the unlock icon.
|
|
4634
|
+
this.gestureListener = function () {
|
|
4635
|
+
return _this2.unlock();
|
|
4636
|
+
};
|
|
4637
|
+
UNLOCK_EVENTS.forEach(function (type) {
|
|
4638
|
+
document.addEventListener(type, _this2.gestureListener, true);
|
|
4639
|
+
});
|
|
4640
|
+
};
|
|
4641
|
+
_proto.removeGestureListener = function removeGestureListener() {
|
|
4642
|
+
var _this3 = this;
|
|
4643
|
+
if (!this.gestureListener) {
|
|
4644
|
+
return;
|
|
4645
|
+
}
|
|
4646
|
+
UNLOCK_EVENTS.forEach(function (type) {
|
|
4647
|
+
document.removeEventListener(type, _this3.gestureListener, true);
|
|
4648
|
+
});
|
|
4649
|
+
this.gestureListener = null;
|
|
4650
|
+
};
|
|
4508
4651
|
_proto.unlock = function unlock(callback) {
|
|
4509
|
-
|
|
4652
|
+
var _this4 = this;
|
|
4653
|
+
// Nothing to do while the audio plays; a context that was suspended again
|
|
4654
|
+
// still has to be resumed, and a destroyed one has nothing left to unlock.
|
|
4655
|
+
if (this.isPlayable() || this.destroyed || this.context.state === 'closed') {
|
|
4510
4656
|
if (callback) {
|
|
4511
4657
|
callback();
|
|
4512
4658
|
}
|
|
4513
4659
|
return;
|
|
4514
4660
|
}
|
|
4515
|
-
this.unlockCallback = callback;
|
|
4661
|
+
this.unlockCallback = callback || this.unlockCallback;
|
|
4516
4662
|
|
|
4517
|
-
// Create empty buffer and play it
|
|
4663
|
+
// Create empty buffer and play it, so the browsers that only start their
|
|
4664
|
+
// audio pipeline from within a user gesture (iOS Safari) really unlock.
|
|
4518
4665
|
var buffer = this.context.createBuffer(1, 1, 22050);
|
|
4519
4666
|
var source = this.context.createBufferSource();
|
|
4520
4667
|
source.buffer = buffer;
|
|
@@ -4526,23 +4673,73 @@ var WebAudioOut = /*#__PURE__*/function () {
|
|
|
4526
4673
|
} else {
|
|
4527
4674
|
source.noteOn(0);
|
|
4528
4675
|
}
|
|
4676
|
+
|
|
4677
|
+
// Chrome and desktop Safari keep the context suspended until it is resumed
|
|
4678
|
+
// explicitly, and resume() only takes effect inside a user gesture - which
|
|
4679
|
+
// is exactly where unlock() is called from.
|
|
4680
|
+
var resumed = null;
|
|
4681
|
+
try {
|
|
4682
|
+
resumed = this.context.resume && this.context.resume();
|
|
4683
|
+
} catch (e) {
|
|
4684
|
+
// An unsupported or interrupted context: fall through to the polling
|
|
4685
|
+
// check, which also covers the browsers that resume on their own.
|
|
4686
|
+
}
|
|
4687
|
+
if (resumed && resumed.then) {
|
|
4688
|
+
resumed.then(function () {
|
|
4689
|
+
return _this4.checkIfUnlocked(source, 0);
|
|
4690
|
+
}, function () {
|
|
4691
|
+
return _this4.checkIfUnlocked(source, 0);
|
|
4692
|
+
});
|
|
4693
|
+
return;
|
|
4694
|
+
}
|
|
4529
4695
|
setTimeout(this.checkIfUnlocked.bind(this, source, 0), 0);
|
|
4530
4696
|
};
|
|
4531
4697
|
_proto.checkIfUnlocked = function checkIfUnlocked(source, attempt) {
|
|
4532
|
-
if (
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4698
|
+
if (this.destroyed || this.context.state === 'closed') {
|
|
4699
|
+
// The player was destroyed while waiting for the unlock.
|
|
4700
|
+
this.unlockCallback = null;
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
// `playbackState` and its constants were removed from the Web Audio API,
|
|
4705
|
+
// and comparing two undefined values is always true, so the legacy check
|
|
4706
|
+
// may only be used when the browser actually exposes it.
|
|
4707
|
+
var legacyUnlocked = source.playbackState !== undefined && (source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE);
|
|
4708
|
+
var unlocked = this.context.state ? this.context.state === 'running' : legacyUnlocked;
|
|
4709
|
+
if (unlocked) {
|
|
4710
|
+
this.onUnlocked();
|
|
4538
4711
|
} else if (attempt < 10) {
|
|
4539
4712
|
// Jeez, what a shit show. Thanks iOS!
|
|
4540
4713
|
setTimeout(this.checkIfUnlocked.bind(this, source, attempt + 1), 100);
|
|
4541
4714
|
}
|
|
4542
4715
|
};
|
|
4716
|
+
_proto.onUnlocked = function onUnlocked() {
|
|
4717
|
+
if (this.destroyed) {
|
|
4718
|
+
return;
|
|
4719
|
+
}
|
|
4720
|
+
this.removeGestureListener();
|
|
4721
|
+
|
|
4722
|
+
// The output may be unlocked again after its context had been suspended, so
|
|
4723
|
+
// a pending callback is always served; only the onUnlock hook, which drives
|
|
4724
|
+
// the unlock UI, is limited to the first time.
|
|
4725
|
+
var firstUnlock = !this.unlocked;
|
|
4726
|
+
this.unlocked = true;
|
|
4727
|
+
var callbacks = [this.unlockCallback, firstUnlock ? this.onUnlock : null];
|
|
4728
|
+
this.unlockCallback = null;
|
|
4729
|
+
callbacks.forEach(function (callback) {
|
|
4730
|
+
if (callback) {
|
|
4731
|
+
callback();
|
|
4732
|
+
}
|
|
4733
|
+
});
|
|
4734
|
+
};
|
|
4543
4735
|
WebAudioOut.NeedsUnlocking = function NeedsUnlocking() {
|
|
4544
4736
|
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
4545
4737
|
};
|
|
4738
|
+
WebAudioOut.IsRunning = function IsRunning(context) {
|
|
4739
|
+
// Old WebKit versions expose no state at all; there, NeedsUnlocking()
|
|
4740
|
+
// alone decides whether the context has to be unlocked.
|
|
4741
|
+
return !context.state || context.state === 'running';
|
|
4742
|
+
};
|
|
4546
4743
|
WebAudioOut.IsSupported = function IsSupported() {
|
|
4547
4744
|
return window.AudioContext || window.webkitAudioContext;
|
|
4548
4745
|
};
|
|
@@ -4810,6 +5007,7 @@ var Player = /*#__PURE__*/function () {
|
|
|
4810
5007
|
set: this.setVolume
|
|
4811
5008
|
});
|
|
4812
5009
|
this.paused = true;
|
|
5010
|
+
this.destroyed = false;
|
|
4813
5011
|
this.unpauseOnShow = false;
|
|
4814
5012
|
if (options.pauseWhenHidden !== false) {
|
|
4815
5013
|
// Keep the bound reference so destroy() can remove this listener again,
|
|
@@ -4835,6 +5033,13 @@ var Player = /*#__PURE__*/function () {
|
|
|
4835
5033
|
}
|
|
4836
5034
|
var _proto = Player.prototype;
|
|
4837
5035
|
_proto.startLoading = function startLoading() {
|
|
5036
|
+
// The WASM module is compiled asynchronously, so this may still run through
|
|
5037
|
+
// the module's init callback after destroy() has already torn the player
|
|
5038
|
+
// down. Starting the source then would open a connection that nothing owns
|
|
5039
|
+
// any more and restart the decode loop.
|
|
5040
|
+
if (this.destroyed) {
|
|
5041
|
+
return;
|
|
5042
|
+
}
|
|
4838
5043
|
this.source.start();
|
|
4839
5044
|
if (this.autoplay) {
|
|
4840
5045
|
this.play();
|
|
@@ -4866,10 +5071,11 @@ var Player = /*#__PURE__*/function () {
|
|
|
4866
5071
|
this.isPlaying = false;
|
|
4867
5072
|
this.paused = true;
|
|
4868
5073
|
if (this.audio && this.audio.canPlay) {
|
|
4869
|
-
// Seek to the
|
|
4870
|
-
// further, so
|
|
5074
|
+
// Seek back to the frame that is being heard: stop() drops the queue that
|
|
5075
|
+
// was already enqueued further ahead, so the time has to be read first.
|
|
5076
|
+
var time = this.currentTime;
|
|
4871
5077
|
this.audioOut.stop();
|
|
4872
|
-
this.seek(
|
|
5078
|
+
this.seek(time);
|
|
4873
5079
|
}
|
|
4874
5080
|
if (this.hooks.pause) {
|
|
4875
5081
|
this.hooks.pause();
|
|
@@ -4897,6 +5103,9 @@ var Player = /*#__PURE__*/function () {
|
|
|
4897
5103
|
}
|
|
4898
5104
|
};
|
|
4899
5105
|
_proto.destroy = function destroy() {
|
|
5106
|
+
// Nothing may start the source from here on: the WASM init callback may
|
|
5107
|
+
// still fire later, and destroy() itself may run before the source started.
|
|
5108
|
+
this.destroyed = true;
|
|
4900
5109
|
this.pause();
|
|
4901
5110
|
this.source.destroy();
|
|
4902
5111
|
this.video && this.video.destroy();
|
|
@@ -5103,6 +5312,12 @@ var FetchSource = /*#__PURE__*/function () {
|
|
|
5103
5312
|
_proto.abort = function abort() {
|
|
5104
5313
|
this.aborted = true;
|
|
5105
5314
|
};
|
|
5315
|
+
_proto.destroy = function destroy() {
|
|
5316
|
+
// The player always calls source.destroy(); without this the Fetch source
|
|
5317
|
+
// would throw on teardown. The pump loop cancels the reader once it sees
|
|
5318
|
+
// the flag on the next chunk.
|
|
5319
|
+
this.aborted = true;
|
|
5320
|
+
};
|
|
5106
5321
|
return FetchSource;
|
|
5107
5322
|
}();
|
|
5108
5323
|
|