shaka-player 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/config.yml +1 -0
- package/.github/workflows/release-please.yaml +6 -11
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +4 -4
- package/build/misspellings.txt +1 -0
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/demo.less +0 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +4 -4
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +4 -4
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +3 -3
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +2 -2
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +2 -2
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +15 -8
- package/dist/shaka-player.compiled.debug.d.ts +15 -8
- package/dist/shaka-player.compiled.debug.externs.js +11 -2
- package/dist/shaka-player.compiled.debug.js +266 -253
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +11 -2
- package/dist/shaka-player.compiled.js +792 -784
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +16 -9
- package/dist/shaka-player.ui.debug.d.ts +16 -9
- package/dist/shaka-player.ui.debug.externs.js +11 -2
- package/dist/shaka-player.ui.debug.js +299 -282
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +11 -2
- package/dist/shaka-player.ui.js +948 -941
- package/dist/shaka-player.ui.map +3 -3
- package/docs/design/chromecast.md +3 -1
- package/docs/tutorials/drm-config.md +16 -0
- package/docs/tutorials/ui.md +26 -0
- package/docs/tutorials/upgrade.md +4 -2
- package/externs/chromecast.js +9 -2
- package/externs/shaka/manifest.js +7 -0
- package/externs/shaka/player.js +32 -2
- package/externs/shaka/text.js +7 -0
- package/karma.conf.js +4 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- package/lib/cast/cast_proxy.js +19 -8
- package/lib/cast/cast_receiver.js +3 -3
- package/lib/cast/cast_sender.js +12 -2
- package/lib/dash/dash_parser.js +2 -2
- package/lib/hls/hls_parser.js +46 -15
- package/lib/media/drm_engine.js +36 -8
- package/lib/media/gap_jumping_controller.js +23 -1
- package/lib/media/media_source_engine.js +91 -37
- package/lib/media/playhead.js +47 -4
- package/lib/media/stall_detector.js +19 -2
- package/lib/media/streaming_engine.js +9 -3
- package/lib/offline/indexeddb/storage_mechanism.js +4 -2
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/player.js +339 -175
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +11 -7
- package/lib/util/config_utils.js +25 -0
- package/lib/util/error.js +13 -0
- package/lib/util/fake_event.js +39 -0
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mime_utils.js +62 -6
- package/lib/util/periods.js +5 -4
- package/lib/util/player_configuration.js +16 -14
- package/lib/util/stats.js +28 -1
- package/lib/util/stream_utils.js +11 -2
- package/package.json +5 -2
- package/test/cast/cast_proxy_unit.js +3 -1
- package/test/cast/cast_sender_unit.js +21 -6
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +79 -7
- package/test/hls/manifest_text_parser_unit.js +19 -0
- package/test/media/adaptation_set_unit.js +1 -0
- package/test/media/playhead_unit.js +114 -25
- package/test/media/stall_detector_unit.js +20 -2
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +1 -0
- package/test/offline/indexeddb_storage_unit.js +1 -1
- package/test/offline/storage_compatibility_unit.js +1 -1
- package/test/player_external.js +7 -1
- package/test/player_integration.js +9 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +240 -2
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -0
- package/test/test/util/manifest_generator.js +2 -0
- package/test/test/util/simple_fakes.js +45 -2
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/text/vtt_text_parser_unit.js +68 -5
- package/test/ui/ui_unit.js +18 -5
- package/test/util/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -0
- package/test/util/stream_utils_unit.js +108 -0
- package/ui/controls.js +12 -8
- package/ui/externs/ui.js +10 -1
- package/ui/less/containers.less +1 -1
- package/ui/less/general.less +1 -1
- package/ui/less/range_elements.less +1 -0
- package/ui/resolution_selection.js +1 -1
- package/ui/statistics_button.js +10 -0
- package/ui/ui.js +12 -1
- package/.github/pull_request_template.md +0 -10
- package/docs/tutorials/gsoc-ideas-list.md +0 -3
|
@@ -11,6 +11,7 @@ goog.require('shaka.media.PresentationTimeline');
|
|
|
11
11
|
goog.require('shaka.media.StallDetector');
|
|
12
12
|
goog.require('shaka.media.TimeRangesUtils');
|
|
13
13
|
goog.require('shaka.util.EventManager');
|
|
14
|
+
goog.require('shaka.util.FakeEvent');
|
|
14
15
|
goog.require('shaka.util.IReleasable');
|
|
15
16
|
goog.require('shaka.util.Timer');
|
|
16
17
|
|
|
@@ -32,8 +33,13 @@ shaka.media.GapJumpingController = class {
|
|
|
32
33
|
* playable region. The gap jumping controller takes ownership over the
|
|
33
34
|
* stall detector.
|
|
34
35
|
* If no stall detection logic is desired, |null| may be provided.
|
|
36
|
+
* @param {function(!Event)} onEvent
|
|
37
|
+
* Called when an event is raised to be sent to the application.
|
|
35
38
|
*/
|
|
36
|
-
constructor(video, timeline, config, stallDetector) {
|
|
39
|
+
constructor(video, timeline, config, stallDetector, onEvent) {
|
|
40
|
+
/** @private {?function(!Event)} */
|
|
41
|
+
this.onEvent_ = onEvent;
|
|
42
|
+
|
|
37
43
|
/** @private {HTMLMediaElement} */
|
|
38
44
|
this.video_ = video;
|
|
39
45
|
|
|
@@ -52,6 +58,9 @@ shaka.media.GapJumpingController = class {
|
|
|
52
58
|
/** @private {number} */
|
|
53
59
|
this.prevReadyState_ = video.readyState;
|
|
54
60
|
|
|
61
|
+
/** @private {number} */
|
|
62
|
+
this.gapsJumped_ = 0;
|
|
63
|
+
|
|
55
64
|
/**
|
|
56
65
|
* The stall detector tries to keep the playhead moving forward. It is
|
|
57
66
|
* managed by the gap-jumping controller to avoid conflicts. On some
|
|
@@ -98,6 +107,7 @@ shaka.media.GapJumpingController = class {
|
|
|
98
107
|
this.stallDetector_ = null;
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
this.onEvent_ = null;
|
|
101
111
|
this.timeline_ = null;
|
|
102
112
|
this.video_ = null;
|
|
103
113
|
}
|
|
@@ -121,6 +131,15 @@ shaka.media.GapJumpingController = class {
|
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Returns the total number of playback gaps jumped.
|
|
136
|
+
* @return {number}
|
|
137
|
+
*/
|
|
138
|
+
getGapsJumped() {
|
|
139
|
+
return this.gapsJumped_;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
124
143
|
/**
|
|
125
144
|
* Called on a recurring timer to check for gaps in the media. This is also
|
|
126
145
|
* called in a 'waiting' event.
|
|
@@ -209,6 +228,9 @@ shaka.media.GapJumpingController = class {
|
|
|
209
228
|
}
|
|
210
229
|
|
|
211
230
|
this.video_.currentTime = jumpTo;
|
|
231
|
+
this.gapsJumped_++;
|
|
232
|
+
this.onEvent_(
|
|
233
|
+
new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
|
|
212
234
|
}
|
|
213
235
|
};
|
|
214
236
|
|
|
@@ -106,6 +106,12 @@ shaka.media.MediaSourceEngine = class {
|
|
|
106
106
|
|
|
107
107
|
/** @private {string} */
|
|
108
108
|
this.url_ = '';
|
|
109
|
+
|
|
110
|
+
/** @private {boolean} */
|
|
111
|
+
this.sequenceMode_ = false;
|
|
112
|
+
|
|
113
|
+
/** @private {!shaka.util.PublicPromise.<number>} */
|
|
114
|
+
this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
/**
|
|
@@ -331,6 +337,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
331
337
|
|
|
332
338
|
await this.mediaSourceOpen_;
|
|
333
339
|
|
|
340
|
+
this.sequenceMode_ = sequenceMode;
|
|
341
|
+
|
|
334
342
|
for (const contentType of streamsByType.keys()) {
|
|
335
343
|
const stream = streamsByType.get(contentType);
|
|
336
344
|
goog.asserts.assert(
|
|
@@ -348,11 +356,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
348
356
|
mimeType =
|
|
349
357
|
shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
|
|
350
358
|
}
|
|
359
|
+
|
|
351
360
|
const sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
|
|
352
|
-
|
|
353
|
-
sourceBuffer.mode =
|
|
354
|
-
shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
|
|
355
|
-
}
|
|
361
|
+
|
|
356
362
|
this.eventManager_.listen(
|
|
357
363
|
sourceBuffer, 'error',
|
|
358
364
|
() => this.onError_(contentType));
|
|
@@ -515,35 +521,29 @@ shaka.media.MediaSourceEngine = class {
|
|
|
515
521
|
* @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
|
|
516
522
|
* captions
|
|
517
523
|
* @param {boolean=} seeked True if we just seeked
|
|
518
|
-
* @param {boolean=} sequenceMode True if sequence mode
|
|
519
524
|
* @return {!Promise}
|
|
520
525
|
*/
|
|
521
|
-
async appendBuffer(
|
|
522
|
-
|
|
526
|
+
async appendBuffer(
|
|
527
|
+
contentType, data, startTime, endTime, hasClosedCaptions, seeked) {
|
|
523
528
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
524
529
|
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (seeked) {
|
|
531
|
-
const timestampOffset = /** @type {number} */ (startTime);
|
|
532
|
-
this.enqueueOperation_(
|
|
533
|
-
contentType,
|
|
534
|
-
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
530
|
+
if (contentType == ContentType.TEXT) {
|
|
531
|
+
if (this.sequenceMode_) {
|
|
532
|
+
// This won't be known until the first video segment is appended.
|
|
533
|
+
const offset = await this.textSequenceModeOffset_;
|
|
534
|
+
this.textEngine_.setTimestampOffset(offset);
|
|
535
535
|
}
|
|
536
|
+
await this.textEngine_.appendBuffer(data, startTime, endTime);
|
|
537
|
+
return;
|
|
536
538
|
}
|
|
537
539
|
|
|
538
|
-
if (contentType
|
|
539
|
-
await this.textEngine_.appendBuffer(data, startTime, endTime);
|
|
540
|
-
} else if (this.transmuxers_[contentType]) {
|
|
540
|
+
if (this.transmuxers_[contentType]) {
|
|
541
541
|
const transmuxedData =
|
|
542
542
|
await this.transmuxers_[contentType].transmux(data);
|
|
543
543
|
// For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
|
|
544
544
|
// the video stream, so textEngine may not have been initialized.
|
|
545
545
|
if (!this.textEngine_) {
|
|
546
|
-
this.reinitText('text/vtt',
|
|
546
|
+
this.reinitText('text/vtt', this.sequenceMode_);
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
if (transmuxedData.metadata) {
|
|
@@ -562,15 +562,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
562
562
|
closedCaptions, startTime, endTime, videoOffset);
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
|
|
566
|
-
transmuxedSegment = this.workAroundBrokenPlatforms_(
|
|
567
|
-
transmuxedSegment, startTime, contentType);
|
|
568
|
-
|
|
569
|
-
await this.enqueueOperation_(
|
|
570
|
-
contentType, () => this.append_(contentType, transmuxedSegment));
|
|
565
|
+
data = transmuxedData.data;
|
|
571
566
|
} else if (hasClosedCaptions) {
|
|
572
567
|
if (!this.textEngine_) {
|
|
573
|
-
this.reinitText('text/vtt',
|
|
568
|
+
this.reinitText('text/vtt', this.sequenceMode_);
|
|
574
569
|
}
|
|
575
570
|
// If it is the init segment for closed captions, initialize the closed
|
|
576
571
|
// caption parser.
|
|
@@ -585,19 +580,78 @@ shaka.media.MediaSourceEngine = class {
|
|
|
585
580
|
closedCaptions, startTime, endTime, videoOffset);
|
|
586
581
|
}
|
|
587
582
|
}
|
|
583
|
+
}
|
|
588
584
|
|
|
589
|
-
|
|
585
|
+
data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
|
|
586
|
+
|
|
587
|
+
const sourceBuffer = this.sourceBuffers_[contentType];
|
|
588
|
+
const SEQUENCE = shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
|
|
589
|
+
|
|
590
|
+
if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE &&
|
|
591
|
+
startTime != null) {
|
|
592
|
+
// This is the first media segment to be appended to a SourceBuffer in
|
|
593
|
+
// sequence mode. We set the mode late so that we can trick MediaSource
|
|
594
|
+
// into extracting a timestamp for us to align text segments in sequence
|
|
595
|
+
// mode.
|
|
596
|
+
|
|
597
|
+
// Timestamps can only be reliably extracted from video, not audio.
|
|
598
|
+
// Packed audio formats do not have internal timestamps at all.
|
|
599
|
+
// Prefer video for this when available.
|
|
600
|
+
const isBestSourceBufferForTimestamps =
|
|
601
|
+
contentType == ContentType.VIDEO ||
|
|
602
|
+
!(ContentType.VIDEO in this.sourceBuffers_);
|
|
603
|
+
if (isBestSourceBufferForTimestamps) {
|
|
604
|
+
// Append the segment in segments mode first, with offset of 0 and an
|
|
605
|
+
// open append window.
|
|
606
|
+
const originalRange =
|
|
607
|
+
[sourceBuffer.appendWindowStart, sourceBuffer.appendWindowEnd];
|
|
608
|
+
sourceBuffer.appendWindowStart = 0;
|
|
609
|
+
sourceBuffer.appendWindowEnd = Infinity;
|
|
610
|
+
|
|
611
|
+
const originalOffset = sourceBuffer.timestampOffset;
|
|
612
|
+
sourceBuffer.timestampOffset = 0;
|
|
613
|
+
|
|
614
|
+
await this.enqueueOperation_(
|
|
615
|
+
contentType, () => this.append_(contentType, data));
|
|
616
|
+
|
|
617
|
+
// Reset the offset and append window.
|
|
618
|
+
sourceBuffer.timestampOffset = originalOffset;
|
|
619
|
+
sourceBuffer.appendWindowStart = originalRange[0];
|
|
620
|
+
sourceBuffer.appendWindowEnd = originalRange[1];
|
|
621
|
+
|
|
622
|
+
// Now get the timestamp of the segment and compute the offset for text
|
|
623
|
+
// segments.
|
|
624
|
+
const mediaStartTime = shaka.media.TimeRangesUtils.bufferStart(
|
|
625
|
+
this.getBuffered_(contentType));
|
|
626
|
+
const textOffset = (startTime || 0) - (mediaStartTime || 0);
|
|
627
|
+
this.textSequenceModeOffset_.resolve(textOffset);
|
|
628
|
+
|
|
629
|
+
// Finally, clear the buffer.
|
|
630
|
+
await this.enqueueOperation_(
|
|
631
|
+
contentType,
|
|
632
|
+
() => this.remove_(contentType, 0, this.mediaSource_.duration));
|
|
633
|
+
}
|
|
590
634
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
} else {
|
|
595
|
-
data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
|
|
635
|
+
// Now switch to sequence mode and fall through to our normal operations.
|
|
636
|
+
sourceBuffer.mode = SEQUENCE;
|
|
637
|
+
}
|
|
596
638
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
639
|
+
if (startTime != null && this.sequenceMode_ &&
|
|
640
|
+
contentType != ContentType.TEXT) {
|
|
641
|
+
// In sequence mode, for non-text streams, if we just cleared the buffer
|
|
642
|
+
// and are performing an unbuffered seek, we need to set a new
|
|
643
|
+
// timestampOffset on the sourceBuffer.
|
|
644
|
+
if (seeked) {
|
|
645
|
+
const timestampOffset = /** @type {number} */ (startTime);
|
|
646
|
+
this.enqueueOperation_(
|
|
647
|
+
contentType,
|
|
648
|
+
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
649
|
+
}
|
|
600
650
|
}
|
|
651
|
+
|
|
652
|
+
await this.enqueueOperation_(
|
|
653
|
+
contentType,
|
|
654
|
+
() => this.append_(contentType, data));
|
|
601
655
|
}
|
|
602
656
|
|
|
603
657
|
/**
|
package/lib/media/playhead.js
CHANGED
|
@@ -41,6 +41,20 @@ shaka.media.Playhead = class {
|
|
|
41
41
|
*/
|
|
42
42
|
setStartTime(startTime) {}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Get the number of playback stalls detected by the StallDetector.
|
|
46
|
+
*
|
|
47
|
+
* @return {number}
|
|
48
|
+
*/
|
|
49
|
+
getStallsDetected() {}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the number of playback gaps jumped by the GapJumpingController.
|
|
53
|
+
*
|
|
54
|
+
* @return {number}
|
|
55
|
+
*/
|
|
56
|
+
getGapsJumped() {}
|
|
57
|
+
|
|
44
58
|
/**
|
|
45
59
|
* Get the current playhead position. The position will be restricted to valid
|
|
46
60
|
* time ranges.
|
|
@@ -133,6 +147,16 @@ shaka.media.SrcEqualsPlayhead = class {
|
|
|
133
147
|
return time || 0;
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
/** @override */
|
|
151
|
+
getStallsDetected() {
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** @override */
|
|
156
|
+
getGapsJumped() {
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
136
160
|
/** @override */
|
|
137
161
|
notifyOfBufferingChange() {}
|
|
138
162
|
};
|
|
@@ -160,8 +184,10 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
160
184
|
* @param {function()} onSeek
|
|
161
185
|
* Called when the user agent seeks to a time within the presentation
|
|
162
186
|
* timeline.
|
|
187
|
+
* @param {function(!Event)} onEvent
|
|
188
|
+
* Called when an event is raised to be sent to the application.
|
|
163
189
|
*/
|
|
164
|
-
constructor(mediaElement, manifest, config, startTime, onSeek) {
|
|
190
|
+
constructor(mediaElement, manifest, config, startTime, onSeek, onEvent) {
|
|
165
191
|
/**
|
|
166
192
|
* The seek range must be at least this number of seconds long. If it is
|
|
167
193
|
* smaller than this, change it to be this big so we don't repeatedly seek
|
|
@@ -192,12 +218,17 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
192
218
|
/** @private {?number} */
|
|
193
219
|
this.lastCorrectiveSeek_ = null;
|
|
194
220
|
|
|
221
|
+
/** @private {shaka.media.StallDetector} */
|
|
222
|
+
this.stallDetector_ =
|
|
223
|
+
this.createStallDetector_(mediaElement, config, onEvent);
|
|
224
|
+
|
|
195
225
|
/** @private {shaka.media.GapJumpingController} */
|
|
196
226
|
this.gapController_ = new shaka.media.GapJumpingController(
|
|
197
227
|
mediaElement,
|
|
198
228
|
manifest.presentationTimeline,
|
|
199
229
|
config,
|
|
200
|
-
this.
|
|
230
|
+
this.stallDetector_,
|
|
231
|
+
onEvent);
|
|
201
232
|
|
|
202
233
|
/** @private {shaka.media.VideoWrapper} */
|
|
203
234
|
this.videoWrapper_ = new shaka.media.VideoWrapper(
|
|
@@ -261,6 +292,16 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
261
292
|
return time;
|
|
262
293
|
}
|
|
263
294
|
|
|
295
|
+
/** @override */
|
|
296
|
+
getStallsDetected() {
|
|
297
|
+
return this.stallDetector_.getStallsDetected();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** @override */
|
|
301
|
+
getGapsJumped() {
|
|
302
|
+
return this.gapController_.getGapsJumped();
|
|
303
|
+
}
|
|
304
|
+
|
|
264
305
|
/**
|
|
265
306
|
* Gets the playhead's initial position in seconds.
|
|
266
307
|
*
|
|
@@ -475,10 +516,12 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
475
516
|
*
|
|
476
517
|
* @param {!HTMLMediaElement} mediaElement
|
|
477
518
|
* @param {shaka.extern.StreamingConfiguration} config
|
|
519
|
+
* @param {function(!Event)} onEvent
|
|
520
|
+
* Called when an event is raised to be sent to the application.
|
|
478
521
|
* @return {shaka.media.StallDetector}
|
|
479
522
|
* @private
|
|
480
523
|
*/
|
|
481
|
-
createStallDetector_(mediaElement, config) {
|
|
524
|
+
createStallDetector_(mediaElement, config, onEvent) {
|
|
482
525
|
if (!config.stallEnabled) {
|
|
483
526
|
return null;
|
|
484
527
|
}
|
|
@@ -492,7 +535,7 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
492
535
|
// playhead forward.
|
|
493
536
|
const detector = new shaka.media.StallDetector(
|
|
494
537
|
new shaka.media.StallDetector.MediaElementImplementation(mediaElement),
|
|
495
|
-
threshold);
|
|
538
|
+
threshold, onEvent);
|
|
496
539
|
|
|
497
540
|
detector.onStall((at, duration) => {
|
|
498
541
|
shaka.log.debug(`Stall detected at ${at} for ${duration} seconds.`);
|
|
@@ -9,6 +9,7 @@ goog.provide('shaka.media.StallDetector.Implementation');
|
|
|
9
9
|
goog.provide('shaka.media.StallDetector.MediaElementImplementation');
|
|
10
10
|
|
|
11
11
|
goog.require('shaka.media.TimeRangesUtils');
|
|
12
|
+
goog.require('shaka.util.FakeEvent');
|
|
12
13
|
goog.require('shaka.util.IReleasable');
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -23,11 +24,14 @@ shaka.media.StallDetector = class {
|
|
|
23
24
|
/**
|
|
24
25
|
* @param {shaka.media.StallDetector.Implementation} implementation
|
|
25
26
|
* @param {number} stallThresholdSeconds
|
|
27
|
+
* @param {function(!Event)} onEvent
|
|
28
|
+
* Called when an event is raised to be sent to the application.
|
|
26
29
|
*/
|
|
27
|
-
constructor(implementation, stallThresholdSeconds) {
|
|
30
|
+
constructor(implementation, stallThresholdSeconds, onEvent) {
|
|
31
|
+
/** @private {?function(!Event)} */
|
|
32
|
+
this.onEvent_ = onEvent;
|
|
28
33
|
/** @private {shaka.media.StallDetector.Implementation} */
|
|
29
34
|
this.implementation_ = implementation;
|
|
30
|
-
|
|
31
35
|
/** @private {boolean} */
|
|
32
36
|
this.wasMakingProgress_ = implementation.shouldBeMakingProgress();
|
|
33
37
|
/** @private {number} */
|
|
@@ -36,6 +40,8 @@ shaka.media.StallDetector = class {
|
|
|
36
40
|
this.lastUpdateSeconds_ = implementation.getWallSeconds();
|
|
37
41
|
/** @private {boolean} */
|
|
38
42
|
this.didJump_ = false;
|
|
43
|
+
/** @private {number} */
|
|
44
|
+
this.stallsDetected_ = 0;
|
|
39
45
|
|
|
40
46
|
/**
|
|
41
47
|
* The amount of time in seconds that we must have the same value of
|
|
@@ -53,6 +59,7 @@ shaka.media.StallDetector = class {
|
|
|
53
59
|
release() {
|
|
54
60
|
// Drop external references to make things easier on the GC.
|
|
55
61
|
this.implementation_ = null;
|
|
62
|
+
this.onEvent_ = null;
|
|
56
63
|
this.onStall_ = () => {};
|
|
57
64
|
}
|
|
58
65
|
|
|
@@ -66,6 +73,13 @@ shaka.media.StallDetector = class {
|
|
|
66
73
|
this.onStall_ = doThis;
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Returns the number of playback stalls detected.
|
|
78
|
+
*/
|
|
79
|
+
getStallsDetected() {
|
|
80
|
+
return this.stallsDetected_;
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
/**
|
|
70
84
|
* Have the detector update itself and fire the "on stall" callback if a stall
|
|
71
85
|
* was detected.
|
|
@@ -100,6 +114,9 @@ shaka.media.StallDetector = class {
|
|
|
100
114
|
// If the onStall_ method updated the current time, update our stored
|
|
101
115
|
// value so we don't think that was an update.
|
|
102
116
|
this.value_ = impl.getPresentationSeconds();
|
|
117
|
+
this.stallsDetected_++;
|
|
118
|
+
this.onEvent_(new shaka.util.FakeEvent(
|
|
119
|
+
shaka.util.FakeEvent.EventName.StallDetected));
|
|
103
120
|
}
|
|
104
121
|
|
|
105
122
|
return triggerCallback;
|
|
@@ -1215,6 +1215,13 @@ shaka.media.StreamingEngine = class {
|
|
|
1215
1215
|
mediaState.performingUpdate = true;
|
|
1216
1216
|
|
|
1217
1217
|
try {
|
|
1218
|
+
if (reference.getStatus() ==
|
|
1219
|
+
shaka.media.SegmentReference.Status.MISSING) {
|
|
1220
|
+
throw new shaka.util.Error(
|
|
1221
|
+
shaka.util.Error.Severity.RECOVERABLE,
|
|
1222
|
+
shaka.util.Error.Category.NETWORK,
|
|
1223
|
+
shaka.util.Error.Code.SEGMENT_MISSING);
|
|
1224
|
+
}
|
|
1218
1225
|
await this.initSourceBuffer_(mediaState, reference);
|
|
1219
1226
|
this.destroyer_.ensureNotDestroyed();
|
|
1220
1227
|
if (this.fatalError_) {
|
|
@@ -1605,8 +1612,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1605
1612
|
reference.syncTime == null ? reference.startTime : reference.syncTime,
|
|
1606
1613
|
reference.endTime,
|
|
1607
1614
|
hasClosedCaptions,
|
|
1608
|
-
seeked
|
|
1609
|
-
this.manifest_.sequenceMode);
|
|
1615
|
+
seeked);
|
|
1610
1616
|
this.destroyer_.ensureNotDestroyed();
|
|
1611
1617
|
shaka.log.v2(logPrefix, 'appended media segment');
|
|
1612
1618
|
}
|
|
@@ -1695,7 +1701,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1695
1701
|
};
|
|
1696
1702
|
|
|
1697
1703
|
// Dispatch an event to notify the application about the emsg box.
|
|
1698
|
-
const eventName = shaka.
|
|
1704
|
+
const eventName = shaka.util.FakeEvent.EventName.Emsg;
|
|
1699
1705
|
const data = (new Map()).set('detail', emsg);
|
|
1700
1706
|
const event = new shaka.util.FakeEvent(eventName, data);
|
|
1701
1707
|
this.playerInterface_.onEvent(event);
|
|
@@ -368,8 +368,10 @@ shaka.offline.indexeddb.StorageMechanism.SESSION_ID_STORE = 'session-ids';
|
|
|
368
368
|
shaka.offline.StorageMuxer.register(
|
|
369
369
|
'idb',
|
|
370
370
|
() => {
|
|
371
|
-
// Offline storage is not supported on the Chromecast
|
|
372
|
-
|
|
371
|
+
// Offline storage is not supported on the Chromecast or Xbox One
|
|
372
|
+
// platforms.
|
|
373
|
+
if (shaka.util.Platform.isChromecast() ||
|
|
374
|
+
shaka.util.Platform.isXboxOne()) {
|
|
373
375
|
return null;
|
|
374
376
|
}
|
|
375
377
|
// Offline storage requires the IndexedDB API.
|