@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
|
@@ -239,10 +239,10 @@
|
|
|
239
239
|
this.els.playButton.classList.add(_style.hidden);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
// Set up the unlock audio button for
|
|
243
|
-
//
|
|
244
|
-
// streaming players we set up a muted speaker
|
|
245
|
-
// others, we can simply use the play button.
|
|
242
|
+
// Set up the unlock audio button for the browsers that do not allow audio
|
|
243
|
+
// before a user action (iOS, and Chrome/Safari under their autoplay
|
|
244
|
+
// policy). For autoplay or streaming players we set up a muted speaker
|
|
245
|
+
// icon as the button. For all others, we can simply use the play button.
|
|
246
246
|
if (this.player.audioOut && !this.player.audioOut.unlocked) {
|
|
247
247
|
var unlockAudioElement = this.els.wrapper;
|
|
248
248
|
if (this.options.autoplay || this.player.options.streaming) {
|
|
@@ -255,6 +255,12 @@
|
|
|
255
255
|
this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement);
|
|
256
256
|
unlockAudioElement.addEventListener('touchstart', this.unlockAudioBound, false);
|
|
257
257
|
unlockAudioElement.addEventListener('click', this.unlockAudioBound, true);
|
|
258
|
+
|
|
259
|
+
// Any gesture on the page may unlock the audio, so the icon has to go
|
|
260
|
+
// away even when it was not the element that was clicked.
|
|
261
|
+
this.player.audioOut.onUnlock = function () {
|
|
262
|
+
return _this.onAudioUnlocked(unlockAudioElement);
|
|
263
|
+
};
|
|
258
264
|
}
|
|
259
265
|
};
|
|
260
266
|
_proto.initPlayButton = function initPlayButton() {
|
|
@@ -281,6 +287,15 @@
|
|
|
281
287
|
});
|
|
282
288
|
});
|
|
283
289
|
};
|
|
290
|
+
_proto.onAudioUnlocked = function onAudioUnlocked(element) {
|
|
291
|
+
if (this.els.unmuteButton) {
|
|
292
|
+
this.els.unmuteButton.classList.add(_style.hidden);
|
|
293
|
+
}
|
|
294
|
+
if (element && this.unlockAudioBound) {
|
|
295
|
+
element.removeEventListener('touchstart', this.unlockAudioBound);
|
|
296
|
+
element.removeEventListener('click', this.unlockAudioBound);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
284
299
|
_proto.onUnlockAudio = function onUnlockAudio(element, ev) {
|
|
285
300
|
var _this3 = this;
|
|
286
301
|
if (this.els.unmuteButton) {
|
|
@@ -288,11 +303,7 @@
|
|
|
288
303
|
ev.stopPropagation();
|
|
289
304
|
}
|
|
290
305
|
this.player.audioOut.unlock(function () {
|
|
291
|
-
|
|
292
|
-
_this3.els.unmuteButton.classList.add(_style.hidden);
|
|
293
|
-
}
|
|
294
|
-
element.removeEventListener('touchstart', _this3.unlockAudioBound);
|
|
295
|
-
element.removeEventListener('click', _this3.unlockAudioBound);
|
|
306
|
+
return _this3.onAudioUnlocked(element);
|
|
296
307
|
});
|
|
297
308
|
};
|
|
298
309
|
_proto.onClick = function onClick() {
|
|
@@ -397,6 +408,11 @@
|
|
|
397
408
|
// Nothing to do here
|
|
398
409
|
};
|
|
399
410
|
_proto.destroy = function destroy() {
|
|
411
|
+
// destroy() may run before start(), e.g. when a player is torn down while
|
|
412
|
+
// the WASM module is still compiling and the source was never started.
|
|
413
|
+
if (!this.request) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
400
416
|
this.request.abort();
|
|
401
417
|
};
|
|
402
418
|
_proto.onProgress = function onProgress(ev) {
|
|
@@ -591,6 +607,12 @@
|
|
|
591
607
|
_proto.destroy = function destroy() {
|
|
592
608
|
clearTimeout(this.reconnectTimeoutId);
|
|
593
609
|
this.shouldAttemptReconnect = false;
|
|
610
|
+
|
|
611
|
+
// destroy() may run before start(), e.g. when a player is torn down while
|
|
612
|
+
// the WASM module is still compiling and the socket was never opened.
|
|
613
|
+
if (!this.socket) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
594
616
|
this.socket.close();
|
|
595
617
|
};
|
|
596
618
|
_proto.start = function start() {
|
|
@@ -4042,11 +4064,15 @@
|
|
|
4042
4064
|
_this.bufferSize = options.audioBufferSize || 128 * 1024;
|
|
4043
4065
|
_this.bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
|
|
4044
4066
|
_this.sampleRate = 0;
|
|
4067
|
+
_this.destroyed = false;
|
|
4045
4068
|
return _this;
|
|
4046
4069
|
}
|
|
4047
4070
|
_inheritsLoose(MP2WASM, _BaseDecoder);
|
|
4048
4071
|
var _proto = MP2WASM.prototype;
|
|
4049
4072
|
_proto.initializeWasmDecoder = function initializeWasmDecoder() {
|
|
4073
|
+
if (this.destroyed) {
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4050
4076
|
if (!this.module.instance) {
|
|
4051
4077
|
console.warn('JSMpeg: WASM module not compiled yet');
|
|
4052
4078
|
return;
|
|
@@ -4056,10 +4082,23 @@
|
|
|
4056
4082
|
this.decoder = this.functions._mp2_decoder_create(this.bufferSize, this.bufferMode);
|
|
4057
4083
|
};
|
|
4058
4084
|
_proto.destroy = function destroy() {
|
|
4085
|
+
// A source can still deliver one last chunk inside the very callback that
|
|
4086
|
+
// destroys the player, so nothing may reach the module from now on.
|
|
4087
|
+
this.destroyed = true;
|
|
4059
4088
|
if (!this.decoder) {
|
|
4060
4089
|
return;
|
|
4061
4090
|
}
|
|
4062
4091
|
this.functions._mp2_decoder_destroy(this.decoder);
|
|
4092
|
+
// The pointer refers to a freed block now, and the module may hand the same
|
|
4093
|
+
// address to the decoder of the next player: it must never be used again.
|
|
4094
|
+
this.decoder = null;
|
|
4095
|
+
this.canPlay = false;
|
|
4096
|
+
};
|
|
4097
|
+
_proto.write = function write(pts, buffers) {
|
|
4098
|
+
if (this.destroyed) {
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
_BaseDecoder.prototype.write.call(this, pts, buffers);
|
|
4063
4102
|
};
|
|
4064
4103
|
_proto.bufferGetIndex = function bufferGetIndex() {
|
|
4065
4104
|
if (!this.decoder) {
|
|
@@ -4075,9 +4114,16 @@
|
|
|
4075
4114
|
this.functions._mp2_decoder_set_index(this.decoder, index);
|
|
4076
4115
|
};
|
|
4077
4116
|
_proto.bufferWrite = function bufferWrite(buffers) {
|
|
4117
|
+
if (this.destroyed) {
|
|
4118
|
+
return 0;
|
|
4119
|
+
}
|
|
4078
4120
|
if (!this.decoder) {
|
|
4079
4121
|
this.initializeWasmDecoder();
|
|
4080
4122
|
}
|
|
4123
|
+
if (!this.decoder) {
|
|
4124
|
+
// The module is not compiled yet; nothing can be written.
|
|
4125
|
+
return 0;
|
|
4126
|
+
}
|
|
4081
4127
|
var totalLength = 0;
|
|
4082
4128
|
for (var i = 0; i < buffers.length; i++) {
|
|
4083
4129
|
totalLength += buffers[i].length;
|
|
@@ -4434,6 +4480,9 @@
|
|
|
4434
4480
|
return CanvasRenderer;
|
|
4435
4481
|
}();
|
|
4436
4482
|
|
|
4483
|
+
// Chrome grants the user activation on pointerup/touchend, not on the
|
|
4484
|
+
// touchstart/pointerdown that precedes them, so the touch events are paired.
|
|
4485
|
+
var UNLOCK_EVENTS = ['pointerdown', 'pointerup', 'keydown', 'touchend'];
|
|
4437
4486
|
var WebAudioOut = /*#__PURE__*/function () {
|
|
4438
4487
|
function WebAudioOut() {
|
|
4439
4488
|
this.context = WebAudioOut.CachedContext = WebAudioOut.CachedContext || new (window.AudioContext || window.webkitAudioContext)();
|
|
@@ -4449,13 +4498,27 @@
|
|
|
4449
4498
|
this.wallclockStartTime = 0;
|
|
4450
4499
|
this.volume = 1;
|
|
4451
4500
|
this.enabled = true;
|
|
4452
|
-
this.
|
|
4501
|
+
this.sources = [];
|
|
4502
|
+
this.skippedAudio = false;
|
|
4503
|
+
this.destroyed = false;
|
|
4504
|
+
this.gestureListener = null;
|
|
4505
|
+
this.unlockCallback = null;
|
|
4506
|
+
this.onUnlock = null;
|
|
4507
|
+
this.unlocked = !WebAudioOut.NeedsUnlocking() && WebAudioOut.IsRunning(this.context);
|
|
4453
4508
|
Object.defineProperty(this, 'enqueuedTime', {
|
|
4454
4509
|
get: this.getEnqueuedTime
|
|
4455
4510
|
});
|
|
4511
|
+
if (!this.unlocked) {
|
|
4512
|
+
this.listenForGesture();
|
|
4513
|
+
}
|
|
4456
4514
|
}
|
|
4457
4515
|
var _proto = WebAudioOut.prototype;
|
|
4458
4516
|
_proto.destroy = function destroy() {
|
|
4517
|
+
this.destroyed = true;
|
|
4518
|
+
this.unlockCallback = null;
|
|
4519
|
+
this.onUnlock = null;
|
|
4520
|
+
this.removeGestureListener();
|
|
4521
|
+
this.stopSources();
|
|
4459
4522
|
this.gain.disconnect();
|
|
4460
4523
|
this.context._connections--;
|
|
4461
4524
|
if (this.context._connections === 0) {
|
|
@@ -4464,20 +4527,46 @@
|
|
|
4464
4527
|
}
|
|
4465
4528
|
};
|
|
4466
4529
|
_proto.play = function play(sampleRate, left, right) {
|
|
4467
|
-
|
|
4530
|
+
var _this = this;
|
|
4531
|
+
if (!this.enabled || this.destroyed) {
|
|
4468
4532
|
return;
|
|
4469
4533
|
}
|
|
4470
4534
|
|
|
4471
|
-
//
|
|
4472
|
-
//
|
|
4473
|
-
|
|
4535
|
+
// Browsers that need no explicit unlock resume the context by themselves
|
|
4536
|
+
// once the page has been interacted with, so the unlock does not necessarily
|
|
4537
|
+
// go through unlock(). iOS keeps requiring the gesture.
|
|
4538
|
+
if (!this.unlocked && !WebAudioOut.NeedsUnlocking() && WebAudioOut.IsRunning(this.context)) {
|
|
4539
|
+
this.onUnlocked();
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
// A context that is not running, or that has not been unlocked yet, cannot
|
|
4543
|
+
// play anything: the browser autoplay policy keeps it suspended until the
|
|
4544
|
+
// page has received a user gesture, and its currentTime does not advance
|
|
4545
|
+
// meanwhile. Scheduling buffers against it would queue all the audio
|
|
4546
|
+
// decoded in the meantime and play it back from the very beginning once
|
|
4547
|
+
// the context resumes. We simply advance the start time to "fake"
|
|
4548
|
+
// actually playing audio instead. This will keep the video in sync.
|
|
4549
|
+
if (!this.isPlayable()) {
|
|
4550
|
+
this.listenForGesture();
|
|
4474
4551
|
var ts = Now();
|
|
4475
4552
|
if (this.wallclockStartTime < ts) {
|
|
4476
4553
|
this.wallclockStartTime = ts;
|
|
4477
4554
|
}
|
|
4478
4555
|
this.wallclockStartTime += left.length / sampleRate;
|
|
4556
|
+
this.skippedAudio = true;
|
|
4479
4557
|
return;
|
|
4480
4558
|
}
|
|
4559
|
+
|
|
4560
|
+
// The audio is available again: whatever was queued while it could not be
|
|
4561
|
+
// played is dropped and the time base is rebuilt, so the audio joins the
|
|
4562
|
+
// picture at the current point in time instead of staying behind it. The
|
|
4563
|
+
// audio decoded meanwhile is lost once, which is what keeps it in sync
|
|
4564
|
+
// with the picture.
|
|
4565
|
+
if (this.skippedAudio) {
|
|
4566
|
+
this.skippedAudio = false;
|
|
4567
|
+
this.stopSources();
|
|
4568
|
+
this.resetEnqueuedTime();
|
|
4569
|
+
}
|
|
4481
4570
|
this.gain.gain.value = this.volume;
|
|
4482
4571
|
var buffer = this.context.createBuffer(2, left.length, sampleRate);
|
|
4483
4572
|
buffer.getChannelData(0).set(left);
|
|
@@ -4485,6 +4574,13 @@
|
|
|
4485
4574
|
var source = this.context.createBufferSource();
|
|
4486
4575
|
source.buffer = buffer;
|
|
4487
4576
|
source.connect(this.destination);
|
|
4577
|
+
source.onended = function () {
|
|
4578
|
+
var index = _this.sources.indexOf(source);
|
|
4579
|
+
if (index !== -1) {
|
|
4580
|
+
_this.sources.splice(index, 1);
|
|
4581
|
+
}
|
|
4582
|
+
};
|
|
4583
|
+
this.sources.push(source);
|
|
4488
4584
|
var now = this.context.currentTime;
|
|
4489
4585
|
var duration = buffer.duration;
|
|
4490
4586
|
if (this.startTime < now) {
|
|
@@ -4496,11 +4592,13 @@
|
|
|
4496
4592
|
this.wallclockStartTime += duration;
|
|
4497
4593
|
};
|
|
4498
4594
|
_proto.stop = function stop() {
|
|
4499
|
-
//
|
|
4500
|
-
//
|
|
4501
|
-
//
|
|
4502
|
-
//
|
|
4595
|
+
// Cut off everything that is still queued: the gain alone would only mute
|
|
4596
|
+
// the buffers that are already scheduled, and they would become audible
|
|
4597
|
+
// again as soon as play() restores it. The time base is rebuilt so the
|
|
4598
|
+
// next buffer joins the current context time instead of the old queue.
|
|
4503
4599
|
this.gain.gain.value = 0;
|
|
4600
|
+
this.stopSources();
|
|
4601
|
+
this.resetEnqueuedTime();
|
|
4504
4602
|
};
|
|
4505
4603
|
_proto.getEnqueuedTime = function getEnqueuedTime() {
|
|
4506
4604
|
// The AudioContext.currentTime is only updated every so often, so if we
|
|
@@ -4511,16 +4609,65 @@
|
|
|
4511
4609
|
this.startTime = this.context.currentTime;
|
|
4512
4610
|
this.wallclockStartTime = Now();
|
|
4513
4611
|
};
|
|
4612
|
+
_proto.isPlayable = function isPlayable() {
|
|
4613
|
+
return this.unlocked && WebAudioOut.IsRunning(this.context);
|
|
4614
|
+
};
|
|
4615
|
+
_proto.stopSources = function stopSources() {
|
|
4616
|
+
var sources = this.sources;
|
|
4617
|
+
this.sources = [];
|
|
4618
|
+
sources.forEach(function (source) {
|
|
4619
|
+
try {
|
|
4620
|
+
source.stop();
|
|
4621
|
+
} catch (e) {
|
|
4622
|
+
// The source has already ended and cannot be stopped any more.
|
|
4623
|
+
}
|
|
4624
|
+
try {
|
|
4625
|
+
source.disconnect();
|
|
4626
|
+
} catch (e) {
|
|
4627
|
+
// It was never connected to anything.
|
|
4628
|
+
}
|
|
4629
|
+
});
|
|
4630
|
+
};
|
|
4631
|
+
_proto.listenForGesture = function listenForGesture() {
|
|
4632
|
+
var _this2 = this;
|
|
4633
|
+
if (this.gestureListener || this.isPlayable() || this.destroyed || WebAudioOut.NeedsUnlocking() || typeof document === 'undefined') {
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
// Chrome and desktop Safari keep the context suspended until it is resumed
|
|
4638
|
+
// inside a user gesture, and they do not resume it by themselves when the
|
|
4639
|
+
// page is tapped somewhere else. iOS keeps requiring the unlock icon.
|
|
4640
|
+
this.gestureListener = function () {
|
|
4641
|
+
return _this2.unlock();
|
|
4642
|
+
};
|
|
4643
|
+
UNLOCK_EVENTS.forEach(function (type) {
|
|
4644
|
+
document.addEventListener(type, _this2.gestureListener, true);
|
|
4645
|
+
});
|
|
4646
|
+
};
|
|
4647
|
+
_proto.removeGestureListener = function removeGestureListener() {
|
|
4648
|
+
var _this3 = this;
|
|
4649
|
+
if (!this.gestureListener) {
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4652
|
+
UNLOCK_EVENTS.forEach(function (type) {
|
|
4653
|
+
document.removeEventListener(type, _this3.gestureListener, true);
|
|
4654
|
+
});
|
|
4655
|
+
this.gestureListener = null;
|
|
4656
|
+
};
|
|
4514
4657
|
_proto.unlock = function unlock(callback) {
|
|
4515
|
-
|
|
4658
|
+
var _this4 = this;
|
|
4659
|
+
// Nothing to do while the audio plays; a context that was suspended again
|
|
4660
|
+
// still has to be resumed, and a destroyed one has nothing left to unlock.
|
|
4661
|
+
if (this.isPlayable() || this.destroyed || this.context.state === 'closed') {
|
|
4516
4662
|
if (callback) {
|
|
4517
4663
|
callback();
|
|
4518
4664
|
}
|
|
4519
4665
|
return;
|
|
4520
4666
|
}
|
|
4521
|
-
this.unlockCallback = callback;
|
|
4667
|
+
this.unlockCallback = callback || this.unlockCallback;
|
|
4522
4668
|
|
|
4523
|
-
// Create empty buffer and play it
|
|
4669
|
+
// Create empty buffer and play it, so the browsers that only start their
|
|
4670
|
+
// audio pipeline from within a user gesture (iOS Safari) really unlock.
|
|
4524
4671
|
var buffer = this.context.createBuffer(1, 1, 22050);
|
|
4525
4672
|
var source = this.context.createBufferSource();
|
|
4526
4673
|
source.buffer = buffer;
|
|
@@ -4532,23 +4679,73 @@
|
|
|
4532
4679
|
} else {
|
|
4533
4680
|
source.noteOn(0);
|
|
4534
4681
|
}
|
|
4682
|
+
|
|
4683
|
+
// Chrome and desktop Safari keep the context suspended until it is resumed
|
|
4684
|
+
// explicitly, and resume() only takes effect inside a user gesture - which
|
|
4685
|
+
// is exactly where unlock() is called from.
|
|
4686
|
+
var resumed = null;
|
|
4687
|
+
try {
|
|
4688
|
+
resumed = this.context.resume && this.context.resume();
|
|
4689
|
+
} catch (e) {
|
|
4690
|
+
// An unsupported or interrupted context: fall through to the polling
|
|
4691
|
+
// check, which also covers the browsers that resume on their own.
|
|
4692
|
+
}
|
|
4693
|
+
if (resumed && resumed.then) {
|
|
4694
|
+
resumed.then(function () {
|
|
4695
|
+
return _this4.checkIfUnlocked(source, 0);
|
|
4696
|
+
}, function () {
|
|
4697
|
+
return _this4.checkIfUnlocked(source, 0);
|
|
4698
|
+
});
|
|
4699
|
+
return;
|
|
4700
|
+
}
|
|
4535
4701
|
setTimeout(this.checkIfUnlocked.bind(this, source, 0), 0);
|
|
4536
4702
|
};
|
|
4537
4703
|
_proto.checkIfUnlocked = function checkIfUnlocked(source, attempt) {
|
|
4538
|
-
if (
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4704
|
+
if (this.destroyed || this.context.state === 'closed') {
|
|
4705
|
+
// The player was destroyed while waiting for the unlock.
|
|
4706
|
+
this.unlockCallback = null;
|
|
4707
|
+
return;
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
// `playbackState` and its constants were removed from the Web Audio API,
|
|
4711
|
+
// and comparing two undefined values is always true, so the legacy check
|
|
4712
|
+
// may only be used when the browser actually exposes it.
|
|
4713
|
+
var legacyUnlocked = source.playbackState !== undefined && (source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE);
|
|
4714
|
+
var unlocked = this.context.state ? this.context.state === 'running' : legacyUnlocked;
|
|
4715
|
+
if (unlocked) {
|
|
4716
|
+
this.onUnlocked();
|
|
4544
4717
|
} else if (attempt < 10) {
|
|
4545
4718
|
// Jeez, what a shit show. Thanks iOS!
|
|
4546
4719
|
setTimeout(this.checkIfUnlocked.bind(this, source, attempt + 1), 100);
|
|
4547
4720
|
}
|
|
4548
4721
|
};
|
|
4722
|
+
_proto.onUnlocked = function onUnlocked() {
|
|
4723
|
+
if (this.destroyed) {
|
|
4724
|
+
return;
|
|
4725
|
+
}
|
|
4726
|
+
this.removeGestureListener();
|
|
4727
|
+
|
|
4728
|
+
// The output may be unlocked again after its context had been suspended, so
|
|
4729
|
+
// a pending callback is always served; only the onUnlock hook, which drives
|
|
4730
|
+
// the unlock UI, is limited to the first time.
|
|
4731
|
+
var firstUnlock = !this.unlocked;
|
|
4732
|
+
this.unlocked = true;
|
|
4733
|
+
var callbacks = [this.unlockCallback, firstUnlock ? this.onUnlock : null];
|
|
4734
|
+
this.unlockCallback = null;
|
|
4735
|
+
callbacks.forEach(function (callback) {
|
|
4736
|
+
if (callback) {
|
|
4737
|
+
callback();
|
|
4738
|
+
}
|
|
4739
|
+
});
|
|
4740
|
+
};
|
|
4549
4741
|
WebAudioOut.NeedsUnlocking = function NeedsUnlocking() {
|
|
4550
4742
|
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
4551
4743
|
};
|
|
4744
|
+
WebAudioOut.IsRunning = function IsRunning(context) {
|
|
4745
|
+
// Old WebKit versions expose no state at all; there, NeedsUnlocking()
|
|
4746
|
+
// alone decides whether the context has to be unlocked.
|
|
4747
|
+
return !context.state || context.state === 'running';
|
|
4748
|
+
};
|
|
4552
4749
|
WebAudioOut.IsSupported = function IsSupported() {
|
|
4553
4750
|
return window.AudioContext || window.webkitAudioContext;
|
|
4554
4751
|
};
|
|
@@ -4816,6 +5013,7 @@
|
|
|
4816
5013
|
set: this.setVolume
|
|
4817
5014
|
});
|
|
4818
5015
|
this.paused = true;
|
|
5016
|
+
this.destroyed = false;
|
|
4819
5017
|
this.unpauseOnShow = false;
|
|
4820
5018
|
if (options.pauseWhenHidden !== false) {
|
|
4821
5019
|
// Keep the bound reference so destroy() can remove this listener again,
|
|
@@ -4841,6 +5039,13 @@
|
|
|
4841
5039
|
}
|
|
4842
5040
|
var _proto = Player.prototype;
|
|
4843
5041
|
_proto.startLoading = function startLoading() {
|
|
5042
|
+
// The WASM module is compiled asynchronously, so this may still run through
|
|
5043
|
+
// the module's init callback after destroy() has already torn the player
|
|
5044
|
+
// down. Starting the source then would open a connection that nothing owns
|
|
5045
|
+
// any more and restart the decode loop.
|
|
5046
|
+
if (this.destroyed) {
|
|
5047
|
+
return;
|
|
5048
|
+
}
|
|
4844
5049
|
this.source.start();
|
|
4845
5050
|
if (this.autoplay) {
|
|
4846
5051
|
this.play();
|
|
@@ -4872,10 +5077,11 @@
|
|
|
4872
5077
|
this.isPlaying = false;
|
|
4873
5078
|
this.paused = true;
|
|
4874
5079
|
if (this.audio && this.audio.canPlay) {
|
|
4875
|
-
// Seek to the
|
|
4876
|
-
// further, so
|
|
5080
|
+
// Seek back to the frame that is being heard: stop() drops the queue that
|
|
5081
|
+
// was already enqueued further ahead, so the time has to be read first.
|
|
5082
|
+
var time = this.currentTime;
|
|
4877
5083
|
this.audioOut.stop();
|
|
4878
|
-
this.seek(
|
|
5084
|
+
this.seek(time);
|
|
4879
5085
|
}
|
|
4880
5086
|
if (this.hooks.pause) {
|
|
4881
5087
|
this.hooks.pause();
|
|
@@ -4903,6 +5109,9 @@
|
|
|
4903
5109
|
}
|
|
4904
5110
|
};
|
|
4905
5111
|
_proto.destroy = function destroy() {
|
|
5112
|
+
// Nothing may start the source from here on: the WASM init callback may
|
|
5113
|
+
// still fire later, and destroy() itself may run before the source started.
|
|
5114
|
+
this.destroyed = true;
|
|
4906
5115
|
this.pause();
|
|
4907
5116
|
this.source.destroy();
|
|
4908
5117
|
this.video && this.video.destroy();
|
|
@@ -5109,6 +5318,12 @@
|
|
|
5109
5318
|
_proto.abort = function abort() {
|
|
5110
5319
|
this.aborted = true;
|
|
5111
5320
|
};
|
|
5321
|
+
_proto.destroy = function destroy() {
|
|
5322
|
+
// The player always calls source.destroy(); without this the Fetch source
|
|
5323
|
+
// would throw on teardown. The pump loop cancels the reader once it sees
|
|
5324
|
+
// the flag on the next chunk.
|
|
5325
|
+
this.aborted = true;
|
|
5326
|
+
};
|
|
5112
5327
|
return FetchSource;
|
|
5113
5328
|
}();
|
|
5114
5329
|
|