shaka-player 3.0.11 → 3.0.15
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/AUTHORS +5 -0
- package/CHANGELOG.md +132 -0
- package/CONTRIBUTORS +5 -0
- package/README.md +3 -0
- package/build/apps.py +2 -2
- package/build/check.py +2 -2
- package/build/conformance.textproto +0 -9
- package/build/generateExterns.js +1 -1
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/demo/common/assets.js +1 -1
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +2 -1
- package/demo/demo.less +4 -0
- package/demo/demo_utils.js +1 -0
- package/demo/input_container.js +2 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +63 -60
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +63 -60
- package/dist/demo.compiled.map +3 -3
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +9 -9
- package/dist/locales.js +35 -33
- package/dist/receiver.compiled.debug.js +59 -56
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +59 -56
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +131 -5
- package/dist/shaka-player.compiled.debug.d.ts +131 -5
- package/dist/shaka-player.compiled.debug.externs.js +142 -8
- package/dist/shaka-player.compiled.debug.js +330 -312
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +142 -8
- package/dist/shaka-player.compiled.js +707 -702
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +131 -5
- package/dist/shaka-player.ui.debug.d.ts +131 -5
- package/dist/shaka-player.ui.debug.externs.js +144 -10
- package/dist/shaka-player.ui.debug.js +354 -333
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +144 -10
- package/dist/shaka-player.ui.js +860 -855
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +25 -0
- package/externs/aria.js +18 -0
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +2 -0
- package/karma.conf.js +1 -0
- package/lib/ads/ad_manager.js +29 -11
- package/lib/ads/client_side_ad_manager.js +76 -86
- package/lib/ads/server_side_ad_manager.js +26 -11
- package/lib/cast/cast_proxy.js +3 -2
- package/lib/cast/cast_receiver.js +6 -2
- package/lib/cast/cast_sender.js +2 -2
- package/lib/dash/dash_parser.js +63 -1
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +41 -14
- package/lib/hls/hls_parser.js +41 -11
- package/lib/media/content_workarounds.js +18 -0
- package/lib/media/drm_engine.js +8 -0
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +7 -0
- package/lib/media/playhead.js +1 -1
- package/lib/media/presentation_timeline.js +3 -2
- package/lib/media/segment_index.js +41 -11
- package/lib/media/streaming_engine.js +34 -21
- package/lib/net/networking_engine.js +2 -1
- package/lib/offline/download_manager.js +1 -2
- package/lib/player.js +99 -44
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/mediasource.js +2 -0
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/ttml_text_parser.js +5 -0
- package/lib/text/ui_text_displayer.js +37 -3
- package/lib/util/error.js +1 -4
- package/lib/util/fake_event.js +53 -7
- package/lib/util/mime_utils.js +33 -0
- package/lib/util/periods.js +25 -3
- package/lib/util/stream_utils.js +11 -0
- package/package.json +3 -3
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/dash/dash_parser_manifest_unit.js +73 -0
- package/test/demo/demo_unit.js +0 -1
- package/test/hls/hls_parser_unit.js +45 -1
- package/test/media/drm_engine_unit.js +56 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/presentation_timeline_unit.js +19 -0
- package/test/media/segment_index_unit.js +79 -6
- package/test/media/streaming_engine_unit.js +3 -3
- package/test/player_integration.js +38 -0
- package/test/player_unit.js +19 -1
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/util/fake_ad_manager.js +4 -1
- package/test/test/util/fake_media_source_engine.js +4 -0
- package/test/test/util/simple_fakes.js +5 -3
- package/test/test/util/waiter.js +36 -2
- package/test/text/ttml_text_parser_unit.js +9 -0
- package/test/text/ui_text_displayer_unit.js +19 -3
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/ui_unit.js +4 -0
- package/ui/big_play_button.js +10 -0
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +9 -7
- package/ui/less/containers.less +3 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/ru.json +6 -6
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +28 -51
- package/ui/pip_button.js +2 -3
- package/ui/play_button.js +10 -10
- package/ui/small_play_button.js +12 -0
package/lib/dash/dash_parser.js
CHANGED
|
@@ -65,6 +65,12 @@ shaka.dash.DashParser = class {
|
|
|
65
65
|
*/
|
|
66
66
|
this.segmentIndexMap_ = {};
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A map of period ids to their durations
|
|
70
|
+
* @private {!Object.<string, number>}
|
|
71
|
+
*/
|
|
72
|
+
this.periodDurations_ = {};
|
|
73
|
+
|
|
68
74
|
/** @private {shaka.util.PeriodCombiner} */
|
|
69
75
|
this.periodCombiner_ = new shaka.util.PeriodCombiner();
|
|
70
76
|
|
|
@@ -88,6 +94,18 @@ shaka.dash.DashParser = class {
|
|
|
88
94
|
|
|
89
95
|
/** @private {!shaka.util.OperationManager} */
|
|
90
96
|
this.operationManager_ = new shaka.util.OperationManager();
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Largest period start time seen.
|
|
100
|
+
* @private {?number}
|
|
101
|
+
*/
|
|
102
|
+
this.largestPeriodStartTime_ = null;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Period IDs seen in previous manifest.
|
|
106
|
+
* @private {!Array.<number>}
|
|
107
|
+
*/
|
|
108
|
+
this.lastManifestUpdatePeriodIds_ = [];
|
|
91
109
|
}
|
|
92
110
|
|
|
93
111
|
/**
|
|
@@ -468,6 +486,7 @@ shaka.dash.DashParser = class {
|
|
|
468
486
|
for (const {i, item: elem, next} of enumerate(periodNodes)) {
|
|
469
487
|
const start = /** @type {number} */ (
|
|
470
488
|
XmlUtils.parseAttr(elem, 'start', XmlUtils.parseDuration, prevEnd));
|
|
489
|
+
const periodId = elem.id;
|
|
471
490
|
const givenDuration =
|
|
472
491
|
XmlUtils.parseAttr(elem, 'duration', XmlUtils.parseDuration);
|
|
473
492
|
|
|
@@ -501,6 +520,42 @@ shaka.dash.DashParser = class {
|
|
|
501
520
|
periodDuration = givenDuration;
|
|
502
521
|
}
|
|
503
522
|
|
|
523
|
+
/**
|
|
524
|
+
* This is to improve robustness when the player observes manifest with
|
|
525
|
+
* past periods that are inconsistent to previous ones.
|
|
526
|
+
*
|
|
527
|
+
* This may happen when a CDN or proxy server switches its upstream from
|
|
528
|
+
* one encoder to another redundant encoder.
|
|
529
|
+
*
|
|
530
|
+
* Skip periods that match all of the following criteria:
|
|
531
|
+
* - Start time is earlier than latest period start time ever seen
|
|
532
|
+
* - Period ID is never seen in the previous manifest
|
|
533
|
+
* - Not the last period in the manifest
|
|
534
|
+
*
|
|
535
|
+
* Periods that meet the aforementioned criteria are considered invalid
|
|
536
|
+
* and should be safe to discard.
|
|
537
|
+
*/
|
|
538
|
+
|
|
539
|
+
if (this.largestPeriodStartTime_ !== null &&
|
|
540
|
+
periodId !== null && start !== null &&
|
|
541
|
+
start < this.largestPeriodStartTime_ &&
|
|
542
|
+
!this.lastManifestUpdatePeriodIds_.includes(periodId) &&
|
|
543
|
+
i + 1 != periodNodes.length) {
|
|
544
|
+
shaka.log.debug(
|
|
545
|
+
`Skipping Period with ID ${periodId} as its start time is smaller` +
|
|
546
|
+
' than the largest period start time that has been seen, and ID ' +
|
|
547
|
+
'is unseen before');
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
// Save maximum period start time if it is the last period
|
|
553
|
+
if (start !== null &&
|
|
554
|
+
(this.largestPeriodStartTime_ === null ||
|
|
555
|
+
start > this.largestPeriodStartTime_)) {
|
|
556
|
+
this.largestPeriodStartTime_ = start;
|
|
557
|
+
}
|
|
558
|
+
|
|
504
559
|
// Parse child nodes.
|
|
505
560
|
const info = {
|
|
506
561
|
start: start,
|
|
@@ -511,6 +566,10 @@ shaka.dash.DashParser = class {
|
|
|
511
566
|
const period = this.parsePeriod_(context, baseUris, info);
|
|
512
567
|
periods.push(period);
|
|
513
568
|
|
|
569
|
+
if (context.period.id && periodDuration) {
|
|
570
|
+
this.periodDurations_[context.period.id] = periodDuration;
|
|
571
|
+
}
|
|
572
|
+
|
|
514
573
|
if (periodDuration == null) {
|
|
515
574
|
if (next) {
|
|
516
575
|
// If the duration is still null and we aren't at the end, then we
|
|
@@ -528,6 +587,9 @@ shaka.dash.DashParser = class {
|
|
|
528
587
|
prevEnd = start + periodDuration;
|
|
529
588
|
} // end of period parsing loop
|
|
530
589
|
|
|
590
|
+
// Replace previous seen periods with the current one.
|
|
591
|
+
this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
|
|
592
|
+
|
|
531
593
|
if (presentationDuration != null) {
|
|
532
594
|
if (prevEnd != presentationDuration) {
|
|
533
595
|
shaka.log.warning(
|
|
@@ -958,7 +1020,7 @@ shaka.dash.DashParser = class {
|
|
|
958
1020
|
const hasManifest = !!this.manifest_;
|
|
959
1021
|
streamInfo = shaka.dash.SegmentTemplate.createStreamInfo(
|
|
960
1022
|
context, requestInitSegment, this.segmentIndexMap_, hasManifest,
|
|
961
|
-
this.config_.dash.initialSegmentLimit);
|
|
1023
|
+
this.config_.dash.initialSegmentLimit, this.periodDurations_);
|
|
962
1024
|
} else {
|
|
963
1025
|
goog.asserts.assert(isText,
|
|
964
1026
|
'Must have Segment* with non-text streams.');
|
package/lib/dash/segment_list.js
CHANGED
|
@@ -63,12 +63,12 @@ shaka.dash.SegmentList = class {
|
|
|
63
63
|
const start = context.presentationTimeline.getSegmentAvailabilityStart();
|
|
64
64
|
segmentIndex.mergeAndEvict(references, start);
|
|
65
65
|
} else {
|
|
66
|
-
context.presentationTimeline.notifySegments(references);
|
|
67
66
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
68
67
|
if (id && context.dynamic) {
|
|
69
68
|
segmentIndexMap[id] = segmentIndex;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
71
|
+
context.presentationTimeline.notifySegments(references);
|
|
72
72
|
|
|
73
73
|
if (!context.dynamic || !context.periodInfo.isLastPeriod) {
|
|
74
74
|
const periodStart = context.periodInfo.start;
|
|
@@ -35,11 +35,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
35
35
|
* @param {boolean} isUpdate True if the manifest is being updated.
|
|
36
36
|
* @param {number} segmentLimit The maximum number of segments to generate for
|
|
37
37
|
* a SegmentTemplate with fixed duration.
|
|
38
|
+
* @param {!Object.<string, number>} periodDurationMap
|
|
38
39
|
* @return {shaka.dash.DashParser.StreamInfo}
|
|
39
40
|
*/
|
|
40
41
|
static createStreamInfo(
|
|
41
42
|
context, requestInitSegment, segmentIndexMap, isUpdate,
|
|
42
|
-
segmentLimit) {
|
|
43
|
+
segmentLimit, periodDurationMap) {
|
|
43
44
|
goog.asserts.assert(context.representation.segmentTemplate,
|
|
44
45
|
'Should only be called with SegmentTemplate');
|
|
45
46
|
const SegmentTemplate = shaka.dash.SegmentTemplate;
|
|
@@ -77,7 +78,8 @@ shaka.dash.SegmentTemplate = class {
|
|
|
77
78
|
return {
|
|
78
79
|
generateSegmentIndex: () => {
|
|
79
80
|
return SegmentTemplate.generateSegmentIndexFromDuration_(
|
|
80
|
-
shallowCopyOfContext, info, segmentLimit, initSegmentReference
|
|
81
|
+
shallowCopyOfContext, info, segmentLimit, initSegmentReference,
|
|
82
|
+
periodDurationMap);
|
|
81
83
|
},
|
|
82
84
|
};
|
|
83
85
|
} else {
|
|
@@ -114,12 +116,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
114
116
|
segmentIndex.mergeAndEvict(references,
|
|
115
117
|
context.presentationTimeline.getSegmentAvailabilityStart());
|
|
116
118
|
} else {
|
|
117
|
-
context.presentationTimeline.notifySegments(references);
|
|
118
119
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
119
120
|
if (id && context.dynamic) {
|
|
120
121
|
segmentIndexMap[id] = segmentIndex;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
124
|
+
context.presentationTimeline.notifySegments(references);
|
|
123
125
|
|
|
124
126
|
if (shouldFit) {
|
|
125
127
|
segmentIndex.fit(periodStart, periodEnd);
|
|
@@ -259,11 +261,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
259
261
|
* @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info
|
|
260
262
|
* @param {number} segmentLimit The maximum number of segments to generate.
|
|
261
263
|
* @param {shaka.media.InitSegmentReference} initSegmentReference
|
|
264
|
+
* @param {!Object.<string, number>} periodDurationMap
|
|
262
265
|
* @return {!Promise.<shaka.media.SegmentIndex>}
|
|
263
266
|
* @private
|
|
264
267
|
*/
|
|
265
268
|
static generateSegmentIndexFromDuration_(
|
|
266
|
-
context, info, segmentLimit, initSegmentReference) {
|
|
269
|
+
context, info, segmentLimit, initSegmentReference, periodDurationMap) {
|
|
267
270
|
goog.asserts.assert(info.mediaTemplate,
|
|
268
271
|
'There should be a media template with duration');
|
|
269
272
|
|
|
@@ -275,9 +278,20 @@ shaka.dash.SegmentTemplate = class {
|
|
|
275
278
|
// Capture values that could change as the parsing context moves on to
|
|
276
279
|
// other parts of the manifest.
|
|
277
280
|
const periodStart = context.periodInfo.start;
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
+
const periodId = context.period.id;
|
|
282
|
+
const initialPeriodDuration = context.periodInfo.duration;
|
|
283
|
+
|
|
284
|
+
// For multi-period live streams the period duration may not be known until
|
|
285
|
+
// the following period appears in an updated manifest. periodDurationMap
|
|
286
|
+
// provides the updated period duration.
|
|
287
|
+
const getPeriodEnd = () => {
|
|
288
|
+
const periodDuration =
|
|
289
|
+
(periodId != null && periodDurationMap[periodId]) ||
|
|
290
|
+
initialPeriodDuration;
|
|
291
|
+
const periodEnd = periodDuration ?
|
|
292
|
+
(periodStart + periodDuration) : Infinity;
|
|
293
|
+
return periodEnd;
|
|
294
|
+
};
|
|
281
295
|
|
|
282
296
|
const segmentDuration = info.segmentDuration;
|
|
283
297
|
goog.asserts.assert(
|
|
@@ -304,7 +318,7 @@ shaka.dash.SegmentTemplate = class {
|
|
|
304
318
|
|
|
305
319
|
Math.min(
|
|
306
320
|
presentationTimeline.getSegmentAvailabilityEnd(),
|
|
307
|
-
|
|
321
|
+
getPeriodEnd()),
|
|
308
322
|
];
|
|
309
323
|
};
|
|
310
324
|
|
|
@@ -378,7 +392,8 @@ shaka.dash.SegmentTemplate = class {
|
|
|
378
392
|
const segmentStart = segmentPeriodTime + periodStart;
|
|
379
393
|
// Cap the segment end at the period end so that references from the
|
|
380
394
|
// next period will fit neatly after it.
|
|
381
|
-
const segmentEnd = Math.min(segmentStart + segmentDuration,
|
|
395
|
+
const segmentEnd = Math.min(segmentStart + segmentDuration,
|
|
396
|
+
getPeriodEnd());
|
|
382
397
|
|
|
383
398
|
// This condition will be true unless the segmentStart was >= periodEnd.
|
|
384
399
|
// If we've done the position calculations correctly, this won't happen.
|
|
@@ -394,7 +409,7 @@ shaka.dash.SegmentTemplate = class {
|
|
|
394
409
|
initSegmentReference,
|
|
395
410
|
timestampOffset,
|
|
396
411
|
/* appendWindowStart= */ periodStart,
|
|
397
|
-
/* appendWindowEnd= */
|
|
412
|
+
/* appendWindowEnd= */ getPeriodEnd());
|
|
398
413
|
};
|
|
399
414
|
|
|
400
415
|
for (let position = minPosition; position <= maxPosition; ++position) {
|
|
@@ -407,7 +422,15 @@ shaka.dash.SegmentTemplate = class {
|
|
|
407
422
|
|
|
408
423
|
// If the availability timeline currently ends before the period, we will
|
|
409
424
|
// need to add references over time.
|
|
410
|
-
|
|
425
|
+
const willNeedToAddReferences =
|
|
426
|
+
presentationTimeline.getSegmentAvailabilityEnd() < getPeriodEnd();
|
|
427
|
+
|
|
428
|
+
// When we start a live stream with a period that ends within the
|
|
429
|
+
// availability window we will not need to add more references, but we will
|
|
430
|
+
// need to evict old references.
|
|
431
|
+
const willNeedToEvictReferences = presentationTimeline.isLive();
|
|
432
|
+
|
|
433
|
+
if (willNeedToAddReferences || willNeedToEvictReferences) {
|
|
411
434
|
// The period continues to get longer over time, so check for new
|
|
412
435
|
// references once every |segmentDuration| seconds.
|
|
413
436
|
// We clamp to |minPosition| in case the initial range was reversed and no
|
|
@@ -416,7 +439,9 @@ shaka.dash.SegmentTemplate = class {
|
|
|
416
439
|
let nextPosition = Math.max(minPosition, maxPosition + 1);
|
|
417
440
|
segmentIndex.updateEvery(segmentDuration, () => {
|
|
418
441
|
// Evict any references outside the window.
|
|
419
|
-
|
|
442
|
+
const availabilityStartTime =
|
|
443
|
+
presentationTimeline.getSegmentAvailabilityStart();
|
|
444
|
+
segmentIndex.evict(availabilityStartTime);
|
|
420
445
|
|
|
421
446
|
// Compute any new references that need to be added.
|
|
422
447
|
const [_, maxPosition] = computeAvailablePositionRange();
|
|
@@ -426,8 +451,10 @@ shaka.dash.SegmentTemplate = class {
|
|
|
426
451
|
references.push(reference);
|
|
427
452
|
nextPosition++;
|
|
428
453
|
}
|
|
429
|
-
|
|
430
|
-
|
|
454
|
+
|
|
455
|
+
// The timer must continue firing until the entire period is
|
|
456
|
+
// unavailable, so that all references will be evicted.
|
|
457
|
+
if (availabilityStartTime > getPeriodEnd() && !references.length) {
|
|
431
458
|
// Signal stop.
|
|
432
459
|
return null;
|
|
433
460
|
}
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -475,6 +475,30 @@ shaka.hls.HlsParser = class {
|
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
+
// Now that the content has been fit, notify segments.
|
|
479
|
+
this.segmentsToNotifyByStream_ = [];
|
|
480
|
+
const streamsToNotify = [];
|
|
481
|
+
for (const variant of variants) {
|
|
482
|
+
for (const stream of [variant.video, variant.audio]) {
|
|
483
|
+
if (stream) {
|
|
484
|
+
streamsToNotify.push(stream);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
await Promise.all(streamsToNotify.map(async (stream) => {
|
|
489
|
+
await stream.createSegmentIndex();
|
|
490
|
+
}));
|
|
491
|
+
for (const stream of streamsToNotify) {
|
|
492
|
+
this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
|
|
493
|
+
}
|
|
494
|
+
this.notifySegments_();
|
|
495
|
+
|
|
496
|
+
// This asserts that the live edge is being calculated from segment times.
|
|
497
|
+
// For VOD and event streams, this check should still pass.
|
|
498
|
+
goog.asserts.assert(
|
|
499
|
+
!this.presentationTimeline_.usingPresentationStartTime(),
|
|
500
|
+
'We should not be using the presentation start time in HLS!');
|
|
501
|
+
|
|
478
502
|
this.manifest_ = {
|
|
479
503
|
presentationTimeline: this.presentationTimeline_,
|
|
480
504
|
variants,
|
|
@@ -642,7 +666,8 @@ shaka.hls.HlsParser = class {
|
|
|
642
666
|
// Create variants for each variant tag.
|
|
643
667
|
const variantsPromises = tags.map(async (tag) => {
|
|
644
668
|
const frameRate = tag.getAttributeValue('FRAME-RATE');
|
|
645
|
-
const bandwidth = Number(tag.
|
|
669
|
+
const bandwidth = Number(tag.getAttributeValue('AVERAGE-BANDWIDTH')) ||
|
|
670
|
+
Number(tag.getRequiredAttrValue('BANDWIDTH'));
|
|
646
671
|
|
|
647
672
|
const resolution = tag.getAttributeValue('RESOLUTION');
|
|
648
673
|
const [width, height] = resolution ? resolution.split('x') : [null, null];
|
|
@@ -1063,7 +1088,7 @@ shaka.hls.HlsParser = class {
|
|
|
1063
1088
|
// "AUTOSELECT=YES". A value of "AUTOSELECT=NO" would imply that it may
|
|
1064
1089
|
// only be selected explicitly by the user, and we don't have a way to
|
|
1065
1090
|
// represent that in our model.
|
|
1066
|
-
const defaultAttrValue = tag.
|
|
1091
|
+
const defaultAttrValue = tag.getAttributeValue('DEFAULT');
|
|
1067
1092
|
const primary = defaultAttrValue == 'YES';
|
|
1068
1093
|
|
|
1069
1094
|
const channelsCount = type == 'audio' ? this.getChannelsCount_(tag) : null;
|
|
@@ -1396,12 +1421,6 @@ shaka.hls.HlsParser = class {
|
|
|
1396
1421
|
}
|
|
1397
1422
|
|
|
1398
1423
|
this.notifySegments_();
|
|
1399
|
-
|
|
1400
|
-
// This asserts that the live edge is being calculated from segment times.
|
|
1401
|
-
// For VOD and event streams, this check should still pass.
|
|
1402
|
-
goog.asserts.assert(
|
|
1403
|
-
!this.presentationTimeline_.usingPresentationStartTime(),
|
|
1404
|
-
'We should not be using the presentation start time in HLS!');
|
|
1405
1424
|
}
|
|
1406
1425
|
|
|
1407
1426
|
/**
|
|
@@ -1637,9 +1656,6 @@ shaka.hls.HlsParser = class {
|
|
|
1637
1656
|
references.push(reference);
|
|
1638
1657
|
}
|
|
1639
1658
|
|
|
1640
|
-
this.segmentsToNotifyByStream_.push(references);
|
|
1641
|
-
this.notifySegments_();
|
|
1642
|
-
|
|
1643
1659
|
return references;
|
|
1644
1660
|
}
|
|
1645
1661
|
|
|
@@ -1976,6 +1992,12 @@ shaka.hls.HlsParser = class {
|
|
|
1976
1992
|
reader.rewind(1);
|
|
1977
1993
|
};
|
|
1978
1994
|
|
|
1995
|
+
// We will look a few packet-lengths forward to find the first sync byte.
|
|
1996
|
+
// Note that we are using this method on what is already a subset of the
|
|
1997
|
+
// file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
|
|
1998
|
+
// we can't look too far ahead to begin with.
|
|
1999
|
+
let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
|
|
2000
|
+
|
|
1979
2001
|
// TODO: refactor this while loop for better readability.
|
|
1980
2002
|
// eslint-disable-next-line no-constant-condition
|
|
1981
2003
|
while (true) {
|
|
@@ -1984,8 +2006,16 @@ shaka.hls.HlsParser = class {
|
|
|
1984
2006
|
|
|
1985
2007
|
syncByte = reader.readUint8();
|
|
1986
2008
|
if (syncByte != 0x47) {
|
|
2009
|
+
if (syncByteScanLength > 0) {
|
|
2010
|
+
// This file could have started with a cut-off TS packet. Scan forward
|
|
2011
|
+
// until we find a sync byte.
|
|
2012
|
+
syncByteScanLength -= 1;
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
1987
2015
|
fail();
|
|
1988
2016
|
}
|
|
2017
|
+
// If we've found a sync byte, stop scanning forward for future packets.
|
|
2018
|
+
syncByteScanLength = 0;
|
|
1989
2019
|
|
|
1990
2020
|
const flagsAndPacketId = reader.readUint16();
|
|
1991
2021
|
const packetId = flagsAndPacketId & 0x1fff;
|
|
@@ -75,6 +75,24 @@ shaka.media.ContentWorkarounds = class {
|
|
|
75
75
|
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
76
|
});
|
|
77
77
|
})
|
|
78
|
+
.fullBox('avc3', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
84
|
+
.fullBox('ac-3', (box) => {
|
|
85
|
+
boxesToModify.push({
|
|
86
|
+
box,
|
|
87
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
.fullBox('ec-3', (box) => {
|
|
91
|
+
boxesToModify.push({
|
|
92
|
+
box,
|
|
93
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
94
|
+
});
|
|
95
|
+
})
|
|
78
96
|
.fullBox('mp4a', (box) => {
|
|
79
97
|
boxesToModify.push({
|
|
80
98
|
box,
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -496,6 +496,7 @@ shaka.media.DrmEngine = class {
|
|
|
496
496
|
tasks.push(session.remove());
|
|
497
497
|
|
|
498
498
|
await Promise.all(tasks);
|
|
499
|
+
this.activeSessions_.delete(session);
|
|
499
500
|
}
|
|
500
501
|
|
|
501
502
|
/**
|
|
@@ -1750,9 +1751,16 @@ shaka.media.DrmEngine = class {
|
|
|
1750
1751
|
// audio adaptations, so we shouldn't have to worry about checking
|
|
1751
1752
|
// robustness.
|
|
1752
1753
|
if (drm1.keySystem == drm2.keySystem) {
|
|
1754
|
+
/** @type {Array<shaka.extern.InitDataOverride>} */
|
|
1753
1755
|
let initData = [];
|
|
1754
1756
|
initData = initData.concat(drm1.initData || []);
|
|
1755
1757
|
initData = initData.concat(drm2.initData || []);
|
|
1758
|
+
initData = initData.filter((d, i) => {
|
|
1759
|
+
return d.keyId === undefined || i === initData.findIndex((d2) => {
|
|
1760
|
+
return d2.keyId === d.keyId;
|
|
1761
|
+
});
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1756
1764
|
const keyIds = drm1.keyIds && drm2.keyIds ?
|
|
1757
1765
|
new Set([...drm1.keyIds, ...drm2.keyIds]) :
|
|
1758
1766
|
drm1.keyIds || drm2.keyIds;
|
|
@@ -154,8 +154,10 @@ shaka.media.GapJumpingController = class {
|
|
|
154
154
|
}
|
|
155
155
|
// Don't gap jump while paused, so that you don't constantly jump ahead
|
|
156
156
|
// while paused on a livestream. We make an exception for time 0, since we
|
|
157
|
-
// may be _required_ to seek on startup before play can begin
|
|
158
|
-
|
|
157
|
+
// may be _required_ to seek on startup before play can begin, but only if
|
|
158
|
+
// autoplay is enabled.
|
|
159
|
+
if (this.video_.paused && (this.video_.currentTime != 0 ||
|
|
160
|
+
(!this.video_.autoplay && this.video_.currentTime == 0))) {
|
|
159
161
|
return;
|
|
160
162
|
}
|
|
161
163
|
|
|
@@ -214,8 +216,10 @@ shaka.media.GapJumpingController = class {
|
|
|
214
216
|
this.didFireLargeGap_ = true;
|
|
215
217
|
|
|
216
218
|
// Event firing is synchronous.
|
|
217
|
-
const
|
|
218
|
-
'
|
|
219
|
+
const data = new Map()
|
|
220
|
+
.set('currentTime', currentTime)
|
|
221
|
+
.set('gapSize', jumpSize);
|
|
222
|
+
const event = new shaka.util.FakeEvent('largegap', data);
|
|
219
223
|
event.cancelable = true;
|
|
220
224
|
this.onEvent_(event);
|
|
221
225
|
|
|
@@ -577,6 +577,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
577
577
|
this.textEngine_.setSelectedClosedCaptionId(id, videoBufferEndTime);
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
+
/** Disable embedded closed captions. */
|
|
581
|
+
clearSelectedClosedCaptionId() {
|
|
582
|
+
if (this.textEngine_) {
|
|
583
|
+
this.textEngine_.setSelectedClosedCaptionId('', 0);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
580
587
|
/**
|
|
581
588
|
* Enqueue an operation to remove data from the SourceBuffer.
|
|
582
589
|
*
|
package/lib/media/playhead.js
CHANGED
|
@@ -80,7 +80,7 @@ shaka.media.SrcEqualsPlayhead = class {
|
|
|
80
80
|
// We listen for the loaded-data-event so that we know when we can
|
|
81
81
|
// interact with |currentTime|.
|
|
82
82
|
const onLoaded = () => {
|
|
83
|
-
if (this.startTime_ == null) {
|
|
83
|
+
if (this.startTime_ == null || this.startTime_ == 0) {
|
|
84
84
|
this.started_ = true;
|
|
85
85
|
} else {
|
|
86
86
|
// Startup is complete only when the video element acknowledges the
|
|
@@ -351,9 +351,10 @@ shaka.media.PresentationTimeline = class {
|
|
|
351
351
|
*/
|
|
352
352
|
getSegmentAvailabilityEnd() {
|
|
353
353
|
if (!this.isLive() && !this.isInProgress()) {
|
|
354
|
-
|
|
354
|
+
// It's a static manifest (can also be a dynamic->static conversion)
|
|
355
|
+
return this.maxSegmentEndTime_ || this.duration_;
|
|
355
356
|
}
|
|
356
|
-
|
|
357
|
+
// Can be either live or "in-progress recording" (live with known duration)
|
|
357
358
|
return Math.min(this.getLiveEdge_(), this.duration_);
|
|
358
359
|
}
|
|
359
360
|
|
|
@@ -181,7 +181,9 @@ shaka.media.SegmentIndex = class {
|
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
183
|
* Merges the given SegmentReferences. Supports extending the original
|
|
184
|
-
* references only. Will
|
|
184
|
+
* references only. Will replace old references with equivalent new ones, and
|
|
185
|
+
* keep any unique old ones.
|
|
186
|
+
*
|
|
185
187
|
* Used, for example, by the DASH and HLS parser, where manifests may not list
|
|
186
188
|
* all available references, so we must keep available references in memory to
|
|
187
189
|
* fill the availability window.
|
|
@@ -189,6 +191,8 @@ shaka.media.SegmentIndex = class {
|
|
|
189
191
|
* @param {!Array.<!shaka.media.SegmentReference>} references The list of
|
|
190
192
|
* SegmentReferences, which must be sorted first by their start times
|
|
191
193
|
* (ascending) and second by their end times (ascending).
|
|
194
|
+
* @deprecated Not used directly by our own parsers, so will become private in
|
|
195
|
+
* v4. Use mergeAndEvict() instead.
|
|
192
196
|
* @export
|
|
193
197
|
*/
|
|
194
198
|
merge(references) {
|
|
@@ -199,13 +203,13 @@ shaka.media.SegmentIndex = class {
|
|
|
199
203
|
return;
|
|
200
204
|
}
|
|
201
205
|
|
|
202
|
-
// Partial segments are used for live edge, and should be removed when they
|
|
203
|
-
// get older. Remove the old SegmentReferences after the first new
|
|
204
|
-
// reference's start time.
|
|
205
206
|
if (!references.length) {
|
|
206
207
|
return;
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
// Partial segments are used for live edge, and should be removed when they
|
|
211
|
+
// get older. Remove the old SegmentReferences after the first new
|
|
212
|
+
// reference's start time.
|
|
209
213
|
this.references = this.references.filter((r) => {
|
|
210
214
|
return r.startTime < references[0].startTime;
|
|
211
215
|
});
|
|
@@ -233,13 +237,27 @@ shaka.media.SegmentIndex = class {
|
|
|
233
237
|
* @export
|
|
234
238
|
*/
|
|
235
239
|
mergeAndEvict(references, windowStart) {
|
|
236
|
-
//
|
|
240
|
+
// Filter out the references that are no longer available to avoid
|
|
237
241
|
// repeatedly evicting them and messing up eviction count.
|
|
238
242
|
references = references.filter((r) => {
|
|
239
|
-
return r.endTime > windowStart
|
|
243
|
+
return r.endTime > windowStart &&
|
|
244
|
+
(this.references.length == 0 ||
|
|
245
|
+
r.endTime > this.references[0].startTime);
|
|
240
246
|
});
|
|
241
247
|
|
|
248
|
+
const oldFirstRef = this.references[0];
|
|
242
249
|
this.merge(references);
|
|
250
|
+
const newFirstRef = this.references[0];
|
|
251
|
+
|
|
252
|
+
if (oldFirstRef) {
|
|
253
|
+
// We don't compare the actual ref, since the object could legitimately be
|
|
254
|
+
// replaced with an equivalent. Even the URIs could change due to
|
|
255
|
+
// load-balancing actions taken by the server. However, if the time
|
|
256
|
+
// changes, its not an equivalent reference.
|
|
257
|
+
goog.asserts.assert(oldFirstRef.startTime == newFirstRef.startTime,
|
|
258
|
+
'SegmentIndex.merge should not change the first reference time!');
|
|
259
|
+
}
|
|
260
|
+
|
|
243
261
|
this.evict(windowStart);
|
|
244
262
|
}
|
|
245
263
|
|
|
@@ -363,20 +381,28 @@ shaka.media.SegmentIndex = class {
|
|
|
363
381
|
|
|
364
382
|
/** @return {!shaka.media.SegmentIterator} */
|
|
365
383
|
[Symbol.iterator]() {
|
|
366
|
-
|
|
384
|
+
const iter = this.getIteratorForTime(0);
|
|
385
|
+
goog.asserts.assert(iter != null, 'Iterator for 0 should never be null!');
|
|
386
|
+
return iter;
|
|
367
387
|
}
|
|
368
388
|
|
|
369
389
|
/**
|
|
370
390
|
* Returns a new iterator that initially points to the segment that contains
|
|
371
391
|
* the given time. Like the normal iterator, next() must be called first to
|
|
372
|
-
* get to the first element.
|
|
392
|
+
* get to the first element. Returns null if we do not find a segment at the
|
|
393
|
+
* requested time.
|
|
373
394
|
*
|
|
374
395
|
* @param {number} time
|
|
375
|
-
* @return {
|
|
396
|
+
* @return {?shaka.media.SegmentIterator}
|
|
376
397
|
* @export
|
|
377
398
|
*/
|
|
378
399
|
getIteratorForTime(time) {
|
|
379
|
-
|
|
400
|
+
const index = this.find(time);
|
|
401
|
+
if (index == null) {
|
|
402
|
+
return null;
|
|
403
|
+
} else {
|
|
404
|
+
return new shaka.media.SegmentIterator(this, index);
|
|
405
|
+
}
|
|
380
406
|
}
|
|
381
407
|
|
|
382
408
|
|
|
@@ -463,7 +489,11 @@ shaka.media.SegmentIterator = class {
|
|
|
463
489
|
*/
|
|
464
490
|
seek(time) {
|
|
465
491
|
const iter = this.segmentIndex_.getIteratorForTime(time);
|
|
466
|
-
|
|
492
|
+
if (iter) {
|
|
493
|
+
this.nextPosition_ = iter.nextPosition_;
|
|
494
|
+
} else {
|
|
495
|
+
this.nextPosition_ = Number.MAX_VALUE;
|
|
496
|
+
}
|
|
467
497
|
return this.next().value;
|
|
468
498
|
}
|
|
469
499
|
|