shaka-player 4.0.2 → 4.1.1
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 +1 -0
- package/CHANGELOG.md +36 -16
- package/CONTRIBUTORS +2 -0
- package/build/test.py +1 -1
- package/demo/common/assets.js +1 -1
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +3 -3
- package/dist/demo.compiled.debug.js +7 -7
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +7 -7
- package/dist/demo.compiled.map +2 -2
- package/dist/deps.js +2 -2
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.js +3 -3
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +3 -3
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +14 -8
- package/dist/shaka-player.compiled.debug.d.ts +14 -8
- package/dist/shaka-player.compiled.debug.externs.js +13 -4
- package/dist/shaka-player.compiled.debug.js +296 -282
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +13 -4
- package/dist/shaka-player.compiled.js +781 -773
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +15 -9
- package/dist/shaka-player.ui.debug.d.ts +15 -9
- package/dist/shaka-player.ui.debug.externs.js +13 -4
- package/dist/shaka-player.ui.debug.js +345 -326
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +13 -4
- package/dist/shaka-player.ui.js +955 -949
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/chromecast.md +3 -1
- package/docs/tutorials/drm-config.md +16 -0
- package/docs/tutorials/ui.md +26 -0
- package/externs/chromecast.js +9 -2
- package/externs/shaka/manifest.js +7 -0
- package/externs/shaka/player.js +25 -4
- package/externs/shaka/text.js +7 -0
- 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/hls/hls_parser.js +189 -69
- package/lib/media/drm_engine.js +17 -8
- package/lib/media/gap_jumping_controller.js +23 -1
- package/lib/media/playhead.js +47 -4
- package/lib/media/segment_index.js +26 -1
- package/lib/media/segment_reference.js +10 -3
- package/lib/media/stall_detector.js +19 -2
- package/lib/media/streaming_engine.js +9 -2
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/player.js +175 -69
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +4 -0
- package/lib/util/error.js +6 -0
- package/lib/util/fake_event.js +39 -0
- package/lib/util/player_configuration.js +2 -0
- package/lib/util/stats.js +28 -1
- package/lib/util/stream_utils.js +8 -0
- package/package.json +2 -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_parser_unit.js +102 -8
- 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/player_integration.js +3 -0
- package/test/player_unit.js +142 -1
- package/test/test/util/manifest_generator.js +2 -0
- package/test/test/util/simple_fakes.js +14 -0
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/text/vtt_text_parser_unit.js +4 -0
- package/test/ui/ui_unit.js +18 -5
- package/test/util/stream_utils_unit.js +108 -0
- package/ui/controls.js +12 -8
- package/ui/externs/ui.js +10 -1
- package/ui/statistics_button.js +10 -0
- package/ui/ui.js +12 -1
package/lib/hls/hls_parser.js
CHANGED
|
@@ -121,13 +121,19 @@ shaka.hls.HlsParser = class {
|
|
|
121
121
|
this.updatePlaylistDelay_ = 0;
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
|
|
124
|
+
* If true, we have already calculated offsets to synchronize streams.
|
|
125
|
+
* Offsets are computed in syncStreams*_().
|
|
126
|
+
* @private {boolean}
|
|
127
|
+
*/
|
|
128
|
+
this.streamsSynced_ = false;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The minimum sequence number for generated segments, when ignoring
|
|
132
|
+
* EXT-X-PROGRAM-DATE-TIME.
|
|
133
|
+
*
|
|
128
134
|
* @private {?number}
|
|
129
135
|
*/
|
|
130
|
-
this.
|
|
136
|
+
this.minSequenceNumber_ = null;
|
|
131
137
|
|
|
132
138
|
/**
|
|
133
139
|
* This timer is used to trigger the start of a manifest update. A manifest
|
|
@@ -361,39 +367,155 @@ shaka.hls.HlsParser = class {
|
|
|
361
367
|
}
|
|
362
368
|
|
|
363
369
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* measured in time since 1970.
|
|
370
|
+
* Align all streams by sequence number by dropping early segments. Then
|
|
371
|
+
* offset all streams to begin at presentation time 0.
|
|
367
372
|
* @private
|
|
368
373
|
*/
|
|
369
|
-
|
|
370
|
-
if (this.
|
|
371
|
-
// The offset was already calculated.
|
|
374
|
+
syncStreamsWithSequenceNumber_() {
|
|
375
|
+
if (this.streamsSynced_) {
|
|
372
376
|
return;
|
|
373
377
|
}
|
|
374
378
|
|
|
375
|
-
|
|
376
|
-
|
|
379
|
+
// Sync using media sequence number. Find the highest starting sequence
|
|
380
|
+
// number among all streams. Later, we will drop any references to
|
|
381
|
+
// earlier segments in other streams, then offset everything back to 0.
|
|
382
|
+
let highestStartingSequenceNumber = -1;
|
|
383
|
+
const firstSequenceNumberMap = new Map();
|
|
384
|
+
|
|
377
385
|
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
378
386
|
const segmentIndex = streamInfo.stream.segmentIndex;
|
|
379
387
|
if (segmentIndex) {
|
|
380
|
-
segmentIndex.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
388
|
+
const segment0 = segmentIndex.earliestReference();
|
|
389
|
+
if (segment0) {
|
|
390
|
+
// This looks inefficient, but iteration order is insertion order.
|
|
391
|
+
// So the very first entry should be the one we want.
|
|
392
|
+
// We assert that this holds true so that we are alerted by debug
|
|
393
|
+
// builds and tests if it changes. We still do a loop, though, so
|
|
394
|
+
// that the code functions correctly in production no matter what.
|
|
395
|
+
if (goog.DEBUG) {
|
|
396
|
+
const firstSequenceStartTime =
|
|
397
|
+
streamInfo.mediaSequenceToStartTime.values().next().value;
|
|
398
|
+
goog.asserts.assert(
|
|
399
|
+
firstSequenceStartTime == segment0.startTime,
|
|
400
|
+
'Sequence number map is not ordered as expected!');
|
|
401
|
+
}
|
|
402
|
+
for (const [sequence, start] of streamInfo.mediaSequenceToStartTime) {
|
|
403
|
+
if (start == segment0.startTime) {
|
|
404
|
+
firstSequenceNumberMap.set(streamInfo, sequence);
|
|
405
|
+
|
|
406
|
+
highestStartingSequenceNumber = Math.max(
|
|
407
|
+
highestStartingSequenceNumber, sequence);
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
384
410
|
}
|
|
385
|
-
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (highestStartingSequenceNumber < 0) {
|
|
416
|
+
// Nothing to sync.
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// From now on, updates will ignore any references before this number.
|
|
421
|
+
this.minSequenceNumber_ = highestStartingSequenceNumber;
|
|
422
|
+
|
|
423
|
+
shaka.log.debug('Syncing HLS streams against base sequence number:',
|
|
424
|
+
this.minSequenceNumber_);
|
|
425
|
+
|
|
426
|
+
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
427
|
+
const segmentIndex = streamInfo.stream.segmentIndex;
|
|
428
|
+
if (segmentIndex) {
|
|
429
|
+
// Drop any earlier references.
|
|
430
|
+
const numSegmentsToDrop = this.minSequenceNumber_ -
|
|
431
|
+
firstSequenceNumberMap.get(streamInfo);
|
|
432
|
+
segmentIndex.dropFirstReferences(numSegmentsToDrop);
|
|
433
|
+
|
|
434
|
+
// Now adjust timestamps back to begin at 0.
|
|
435
|
+
const segmentN = segmentIndex.earliestReference();
|
|
436
|
+
if (segmentN) {
|
|
437
|
+
this.offsetStream_(streamInfo, -segmentN.startTime);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.streamsSynced_ = true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Synchronize streams by the EXT-X-PROGRAM-DATE-TIME tags attached to their
|
|
447
|
+
* segments. Also normalizes segment times so that the earliest segment in
|
|
448
|
+
* any stream is at time 0.
|
|
449
|
+
* @private
|
|
450
|
+
*/
|
|
451
|
+
syncStreamsWithProgramDateTime_() {
|
|
452
|
+
if (this.streamsSynced_) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let lowestSyncTime = Infinity;
|
|
457
|
+
|
|
458
|
+
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
459
|
+
const segmentIndex = streamInfo.stream.segmentIndex;
|
|
460
|
+
if (segmentIndex) {
|
|
461
|
+
const segment0 = segmentIndex.earliestReference();
|
|
462
|
+
if (segment0 != null && segment0.syncTime != null) {
|
|
463
|
+
lowestSyncTime = Math.min(lowestSyncTime, segment0.syncTime);
|
|
464
|
+
}
|
|
386
465
|
}
|
|
387
466
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
467
|
+
|
|
468
|
+
if (lowestSyncTime == Infinity) {
|
|
469
|
+
// Nothing to sync.
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
shaka.log.debug('Syncing HLS streams against base time:', lowestSyncTime);
|
|
474
|
+
|
|
475
|
+
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
476
|
+
const segmentIndex = streamInfo.stream.segmentIndex;
|
|
477
|
+
if (segmentIndex != null) {
|
|
478
|
+
const segment0 = segmentIndex.earliestReference();
|
|
479
|
+
if (segment0.syncTime == null) {
|
|
480
|
+
shaka.log.alwaysError('Missing EXT-X-PROGRAM-DATE-TIME for stream',
|
|
481
|
+
streamInfo.verbatimMediaPlaylistUri,
|
|
482
|
+
'Expect AV sync issues!');
|
|
483
|
+
} else {
|
|
484
|
+
// The first segment's target startTime should be based entirely on
|
|
485
|
+
// its syncTime. The rest of the stream will be based on that
|
|
486
|
+
// starting point. The earliest segment sync time from any stream
|
|
487
|
+
// will become presentation time 0. If two streams start e.g. 6
|
|
488
|
+
// seconds apart in syncTime, then their first segments will also
|
|
489
|
+
// start 6 seconds apart in presentation time.
|
|
490
|
+
const segment0TargetTime = segment0.syncTime - lowestSyncTime;
|
|
491
|
+
const streamOffset = segment0TargetTime - segment0.startTime;
|
|
492
|
+
|
|
493
|
+
this.offsetStream_(streamInfo, streamOffset);
|
|
394
494
|
}
|
|
395
495
|
}
|
|
396
496
|
}
|
|
497
|
+
|
|
498
|
+
this.streamsSynced_ = true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* @param {!shaka.hls.HlsParser.StreamInfo} streamInfo
|
|
503
|
+
* @param {number} offset
|
|
504
|
+
* @private
|
|
505
|
+
*/
|
|
506
|
+
offsetStream_(streamInfo, offset) {
|
|
507
|
+
streamInfo.stream.segmentIndex.offset(offset);
|
|
508
|
+
|
|
509
|
+
streamInfo.maxTimestamp += offset;
|
|
510
|
+
goog.asserts.assert(streamInfo.maxTimestamp >= 0,
|
|
511
|
+
'Negative maxTimestamp after adjustment!');
|
|
512
|
+
|
|
513
|
+
for (const [key, value] of streamInfo.mediaSequenceToStartTime) {
|
|
514
|
+
streamInfo.mediaSequenceToStartTime.set(key, value + offset);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
shaka.log.debug('Offset', offset, 'applied to',
|
|
518
|
+
streamInfo.verbatimMediaPlaylistUri);
|
|
397
519
|
}
|
|
398
520
|
|
|
399
521
|
/**
|
|
@@ -460,6 +582,7 @@ shaka.hls.HlsParser = class {
|
|
|
460
582
|
variants.push({
|
|
461
583
|
id: 0,
|
|
462
584
|
language: 'und',
|
|
585
|
+
disabledUntilTime: 0,
|
|
463
586
|
primary: true,
|
|
464
587
|
audio: type == 'audio' ? streamInfo.stream : null,
|
|
465
588
|
video: type == 'video' ? streamInfo.stream : null,
|
|
@@ -524,7 +647,11 @@ shaka.hls.HlsParser = class {
|
|
|
524
647
|
|
|
525
648
|
// Now that we have generated all streams, we can determine the offset to
|
|
526
649
|
// apply to sync times.
|
|
527
|
-
this.
|
|
650
|
+
if (this.config_.hls.ignoreManifestProgramDateTime) {
|
|
651
|
+
this.syncStreamsWithSequenceNumber_();
|
|
652
|
+
} else {
|
|
653
|
+
this.syncStreamsWithProgramDateTime_();
|
|
654
|
+
}
|
|
528
655
|
|
|
529
656
|
if (this.aesEncrypted_ && variants.length == 0) {
|
|
530
657
|
// We do not support AES-128 encryption with HLS yet. Variants is null
|
|
@@ -539,15 +666,12 @@ shaka.hls.HlsParser = class {
|
|
|
539
666
|
|
|
540
667
|
// Find the min and max timestamp of the earliest segment in all streams.
|
|
541
668
|
// Find the minimum duration of all streams as well.
|
|
542
|
-
let minFirstTimestamp = Infinity;
|
|
543
669
|
let minDuration = Infinity;
|
|
544
|
-
|
|
545
670
|
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
546
|
-
minFirstTimestamp =
|
|
547
|
-
Math.min(minFirstTimestamp, streamInfo.minTimestamp);
|
|
548
671
|
if (streamInfo.stream.type != 'text') {
|
|
549
|
-
|
|
550
|
-
|
|
672
|
+
// Since everything is already offset to 0 (either by sync or by being
|
|
673
|
+
// VOD), only maxTimestamp is necessary to compute the duration.
|
|
674
|
+
minDuration = Math.min(minDuration, streamInfo.maxTimestamp);
|
|
551
675
|
}
|
|
552
676
|
}
|
|
553
677
|
|
|
@@ -591,18 +715,11 @@ shaka.hls.HlsParser = class {
|
|
|
591
715
|
segmentAvailabilityDuration);
|
|
592
716
|
}
|
|
593
717
|
} else {
|
|
594
|
-
// For VOD/EVENT content, offset everything back to 0.
|
|
595
|
-
// Use the minimum timestamp as the offset for all streams.
|
|
596
718
|
// Use the minimum duration as the presentation duration.
|
|
597
719
|
this.presentationTimeline_.setDuration(minDuration);
|
|
598
|
-
// Use a negative offset to adjust towards 0.
|
|
599
|
-
this.presentationTimeline_.offset(-minFirstTimestamp);
|
|
600
720
|
|
|
601
721
|
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
602
|
-
//
|
|
603
|
-
// presentation-aligned times, so offset them all now.
|
|
604
|
-
streamInfo.stream.segmentIndex.offset(-minFirstTimestamp);
|
|
605
|
-
// Finally, fit the segments to the playlist duration.
|
|
722
|
+
// Fit the segments to the playlist duration.
|
|
606
723
|
streamInfo.stream.segmentIndex.fit(/* periodStart= */ 0, minDuration);
|
|
607
724
|
}
|
|
608
725
|
}
|
|
@@ -810,12 +927,7 @@ shaka.hls.HlsParser = class {
|
|
|
810
927
|
});
|
|
811
928
|
|
|
812
929
|
// Create stream info for each audio / video media tag.
|
|
813
|
-
|
|
814
|
-
// and can be reused.
|
|
815
|
-
if (mediaTags.length) {
|
|
816
|
-
await this.createStreamInfoFromMediaTag_(mediaTags[0]);
|
|
817
|
-
}
|
|
818
|
-
const promises = mediaTags.slice(1).map((tag) => {
|
|
930
|
+
const promises = mediaTags.map((tag) => {
|
|
819
931
|
return this.createStreamInfoFromMediaTag_(tag);
|
|
820
932
|
});
|
|
821
933
|
await Promise.all(promises);
|
|
@@ -1583,7 +1695,6 @@ shaka.hls.HlsParser = class {
|
|
|
1583
1695
|
throw error;
|
|
1584
1696
|
}
|
|
1585
1697
|
|
|
1586
|
-
const minTimestamp = segments[0].startTime;
|
|
1587
1698
|
const lastEndTime = segments[segments.length - 1].endTime;
|
|
1588
1699
|
/** @type {!shaka.media.SegmentIndex} */
|
|
1589
1700
|
const segmentIndex = new shaka.media.SegmentIndex(segments);
|
|
@@ -1641,7 +1752,6 @@ shaka.hls.HlsParser = class {
|
|
|
1641
1752
|
stream,
|
|
1642
1753
|
verbatimMediaPlaylistUri,
|
|
1643
1754
|
absoluteMediaPlaylistUri,
|
|
1644
|
-
minTimestamp,
|
|
1645
1755
|
maxTimestamp: lastEndTime,
|
|
1646
1756
|
mediaSequenceToStartTime,
|
|
1647
1757
|
canSkipSegments,
|
|
@@ -1853,15 +1963,9 @@ shaka.hls.HlsParser = class {
|
|
|
1853
1963
|
const dateTimeTag =
|
|
1854
1964
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
|
|
1855
1965
|
if (dateTimeTag && dateTimeTag.value) {
|
|
1856
|
-
|
|
1857
|
-
goog.asserts.assert(
|
|
1966
|
+
syncTime = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
|
|
1967
|
+
goog.asserts.assert(syncTime != null,
|
|
1858
1968
|
'EXT-X-PROGRAM-DATE-TIME format not valid');
|
|
1859
|
-
// Sync time offset is null on the first go-through. This indicates that
|
|
1860
|
-
// we have not yet seen every stream, and thus do not yet have enough
|
|
1861
|
-
// information to determine how to normalize the sync times.
|
|
1862
|
-
// For that first go-through, the sync time will be applied after the
|
|
1863
|
-
// references are all created. Until then, just offset by 0.
|
|
1864
|
-
syncTime = time + (this.syncTimeOffset_ || 0);
|
|
1865
1969
|
}
|
|
1866
1970
|
}
|
|
1867
1971
|
|
|
@@ -1926,6 +2030,11 @@ shaka.hls.HlsParser = class {
|
|
|
1926
2030
|
const pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
1927
2031
|
absoluteMediaPlaylistUri, pUri);
|
|
1928
2032
|
|
|
2033
|
+
let partialStatus = shaka.media.SegmentReference.Status.AVAILABLE;
|
|
2034
|
+
if (item.getAttributeValue('GAP') == 'YES') {
|
|
2035
|
+
partialStatus = shaka.media.SegmentReference.Status.MISSING;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
1929
2038
|
const partial = new shaka.media.SegmentReference(
|
|
1930
2039
|
pStartTime,
|
|
1931
2040
|
pEndTime,
|
|
@@ -1935,7 +2044,12 @@ shaka.hls.HlsParser = class {
|
|
|
1935
2044
|
initSegmentReference,
|
|
1936
2045
|
/* timestampOffset= */ 0, // This value is ignored in sequence mode.
|
|
1937
2046
|
/* appendWindowStart= */ 0,
|
|
1938
|
-
/* appendWindowEnd= */ Infinity
|
|
2047
|
+
/* appendWindowEnd= */ Infinity,
|
|
2048
|
+
/* partialReferences= */ [],
|
|
2049
|
+
/* tilesLayout= */ '',
|
|
2050
|
+
/* tileDuration= */ null,
|
|
2051
|
+
/* syncTime= */ null,
|
|
2052
|
+
partialStatus);
|
|
1939
2053
|
partialSegmentRefs.push(partial);
|
|
1940
2054
|
} // for-loop of hlsSegment.partialSegments
|
|
1941
2055
|
}
|
|
@@ -2085,16 +2199,13 @@ shaka.hls.HlsParser = class {
|
|
|
2085
2199
|
firstStartTime = mediaSequenceToStartTime.get(position);
|
|
2086
2200
|
}
|
|
2087
2201
|
|
|
2088
|
-
const firstSegmentUri = hlsSegments[0].absoluteUri;
|
|
2089
|
-
shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
|
|
2090
|
-
'starts at', firstStartTime);
|
|
2091
|
-
|
|
2092
2202
|
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
2093
2203
|
const references = [];
|
|
2094
2204
|
|
|
2205
|
+
let previousReference = null;
|
|
2206
|
+
|
|
2095
2207
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2096
2208
|
const item = hlsSegments[i];
|
|
2097
|
-
const previousReference = references[references.length - 1];
|
|
2098
2209
|
const startTime =
|
|
2099
2210
|
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2100
2211
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
@@ -2124,9 +2235,17 @@ shaka.hls.HlsParser = class {
|
|
|
2124
2235
|
variables,
|
|
2125
2236
|
playlist.absoluteUri,
|
|
2126
2237
|
type);
|
|
2238
|
+
previousReference = reference;
|
|
2127
2239
|
|
|
2128
2240
|
if (reference) {
|
|
2129
|
-
|
|
2241
|
+
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
|
2242
|
+
this.minSequenceNumber_ != null &&
|
|
2243
|
+
position < this.minSequenceNumber_) {
|
|
2244
|
+
// This segment is ignored as part of our fallback synchronization
|
|
2245
|
+
// method.
|
|
2246
|
+
} else {
|
|
2247
|
+
references.push(reference);
|
|
2248
|
+
}
|
|
2130
2249
|
}
|
|
2131
2250
|
}
|
|
2132
2251
|
|
|
@@ -2256,12 +2375,16 @@ shaka.hls.HlsParser = class {
|
|
|
2256
2375
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2257
2376
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2258
2377
|
|
|
2378
|
+
// If you wait long enough, requesting the first segment can fail
|
|
2379
|
+
// because it has fallen off the left edge of DVR, so to be safer,
|
|
2380
|
+
// let's request the middle segment.
|
|
2259
2381
|
goog.asserts.assert(playlist.segments.length,
|
|
2260
2382
|
'Playlist should have segments!');
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2383
|
+
const middleSegmentIdx = Math.trunc((playlist.segments.length - 1) / 2);
|
|
2384
|
+
const middleSegmentUri = this.variableSubstitution_(
|
|
2385
|
+
playlist.segments[middleSegmentIdx].absoluteUri, variables);
|
|
2263
2386
|
|
|
2264
|
-
const parsedUri = new goog.Uri(
|
|
2387
|
+
const parsedUri = new goog.Uri(middleSegmentUri);
|
|
2265
2388
|
const extension = parsedUri.getPath().split('.').pop();
|
|
2266
2389
|
const map = HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_[contentType];
|
|
2267
2390
|
|
|
@@ -2291,7 +2414,7 @@ shaka.hls.HlsParser = class {
|
|
|
2291
2414
|
// If unable to guess mime type, request a segment and try getting it
|
|
2292
2415
|
// from the response.
|
|
2293
2416
|
const headRequest = shaka.net.NetworkingEngine.makeRequest(
|
|
2294
|
-
[
|
|
2417
|
+
[middleSegmentUri], this.config_.retryParameters);
|
|
2295
2418
|
headRequest.method = 'HEAD';
|
|
2296
2419
|
|
|
2297
2420
|
const response = await this.makeNetworkRequest_(
|
|
@@ -2578,7 +2701,6 @@ shaka.hls.HlsParser = class {
|
|
|
2578
2701
|
* stream: !shaka.extern.Stream,
|
|
2579
2702
|
* verbatimMediaPlaylistUri: string,
|
|
2580
2703
|
* absoluteMediaPlaylistUri: string,
|
|
2581
|
-
* minTimestamp: number,
|
|
2582
2704
|
* maxTimestamp: number,
|
|
2583
2705
|
* mediaSequenceToStartTime: !Map.<number, number>,
|
|
2584
2706
|
* canSkipSegments: boolean
|
|
@@ -2597,8 +2719,6 @@ shaka.hls.HlsParser = class {
|
|
|
2597
2719
|
* @property {string} absoluteMediaPlaylistUri
|
|
2598
2720
|
* The absolute media playlist URI, resolved relative to the master playlist
|
|
2599
2721
|
* and updated to reflect any redirects.
|
|
2600
|
-
* @property {number} minTimestamp
|
|
2601
|
-
* The minimum timestamp found in the stream.
|
|
2602
2722
|
* @property {number} maxTimestamp
|
|
2603
2723
|
* The maximum timestamp found in the stream.
|
|
2604
2724
|
* @property {!Map.<number, number>} mediaSequenceToStartTime
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -355,7 +355,8 @@ shaka.media.DrmEngine = class {
|
|
|
355
355
|
shaka.media.DrmEngine.fillInDrmInfoDefaults_(
|
|
356
356
|
info,
|
|
357
357
|
shaka.util.MapUtils.asMap(this.config_.servers),
|
|
358
|
-
shaka.util.MapUtils.asMap(this.config_.advanced || {})
|
|
358
|
+
shaka.util.MapUtils.asMap(this.config_.advanced || {}),
|
|
359
|
+
this.config_.keySystemsMapping);
|
|
359
360
|
}
|
|
360
361
|
}
|
|
361
362
|
|
|
@@ -2224,16 +2225,20 @@ shaka.media.DrmEngine = class {
|
|
|
2224
2225
|
* that the parser left blank. Before working with any drmInfo, it should be
|
|
2225
2226
|
* passed through here as it is uncommon for drmInfo to be complete when
|
|
2226
2227
|
* fetched from a manifest because most manifest formats do not have the
|
|
2227
|
-
* required information.
|
|
2228
|
+
* required information. Also applies the key systems mapping.
|
|
2228
2229
|
*
|
|
2229
2230
|
* @param {shaka.extern.DrmInfo} drmInfo
|
|
2230
2231
|
* @param {!Map.<string, string>} servers
|
|
2231
2232
|
* @param {!Map.<string, shaka.extern.AdvancedDrmConfiguration>}
|
|
2232
2233
|
* advancedConfigs
|
|
2234
|
+
* @param {!Object.<string, string>} keySystemsMapping
|
|
2233
2235
|
* @private
|
|
2234
2236
|
*/
|
|
2235
|
-
static fillInDrmInfoDefaults_(drmInfo, servers, advancedConfigs
|
|
2236
|
-
|
|
2237
|
+
static fillInDrmInfoDefaults_(drmInfo, servers, advancedConfigs,
|
|
2238
|
+
keySystemsMapping) {
|
|
2239
|
+
const originalKeySystem = drmInfo.keySystem;
|
|
2240
|
+
|
|
2241
|
+
if (!originalKeySystem) {
|
|
2237
2242
|
// This is a placeholder from the manifest parser for an unrecognized key
|
|
2238
2243
|
// system. Skip this entry, to avoid logging nonsensical errors.
|
|
2239
2244
|
return;
|
|
@@ -2254,14 +2259,14 @@ shaka.media.DrmEngine = class {
|
|
|
2254
2259
|
// The only way to get license servers from the manifest is not to specify
|
|
2255
2260
|
// any in your player config.
|
|
2256
2261
|
|
|
2257
|
-
if (
|
|
2262
|
+
if (originalKeySystem == 'org.w3.clearkey' && drmInfo.licenseServerUri) {
|
|
2258
2263
|
// Preference 1: Clear Key with pre-configured keys will have a data URI
|
|
2259
2264
|
// assigned as its license server. Don't change anything.
|
|
2260
2265
|
return;
|
|
2261
2266
|
} else if (servers.size) {
|
|
2262
2267
|
// Preference 2: If anything is configured at the application level,
|
|
2263
2268
|
// override whatever was in the manifest.
|
|
2264
|
-
const server = servers.get(
|
|
2269
|
+
const server = servers.get(originalKeySystem) || '';
|
|
2265
2270
|
|
|
2266
2271
|
drmInfo.licenseServerUri = server;
|
|
2267
2272
|
} else {
|
|
@@ -2273,7 +2278,7 @@ shaka.media.DrmEngine = class {
|
|
|
2273
2278
|
drmInfo.keyIds = new Set();
|
|
2274
2279
|
}
|
|
2275
2280
|
|
|
2276
|
-
const advancedConfig = advancedConfigs.get(
|
|
2281
|
+
const advancedConfig = advancedConfigs.get(originalKeySystem);
|
|
2277
2282
|
|
|
2278
2283
|
if (advancedConfig) {
|
|
2279
2284
|
if (!drmInfo.distinctiveIdentifierRequired) {
|
|
@@ -2307,6 +2312,10 @@ shaka.media.DrmEngine = class {
|
|
|
2307
2312
|
}
|
|
2308
2313
|
}
|
|
2309
2314
|
|
|
2315
|
+
if (keySystemsMapping[originalKeySystem]) {
|
|
2316
|
+
drmInfo.keySystem = keySystemsMapping[originalKeySystem];
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2310
2319
|
// Chromecast has a variant of PlayReady that uses a different key
|
|
2311
2320
|
// system ID. Since manifest parsers convert the standard PlayReady
|
|
2312
2321
|
// UUID to the standard PlayReady key system ID, here we will switch
|
|
@@ -2315,7 +2324,7 @@ shaka.media.DrmEngine = class {
|
|
|
2315
2324
|
// player config uses the standard PlayReady ID for license server
|
|
2316
2325
|
// configuration.
|
|
2317
2326
|
if (window.cast && window.cast.__platform__) {
|
|
2318
|
-
if (
|
|
2327
|
+
if (originalKeySystem == 'com.microsoft.playready') {
|
|
2319
2328
|
drmInfo.keySystem = 'com.chromecast.playready';
|
|
2320
2329
|
}
|
|
2321
2330
|
}
|
|
@@ -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
|
|
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.`);
|