shaka-player 4.0.0 → 4.0.3
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/AUTHORS +1 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +4 -4
- package/build/misspellings.txt +1 -0
- package/build/test.py +1 -1
- package/demo/common/assets.js +1 -1
- package/demo/demo.less +0 -1
- package/demo/main.js +3 -3
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +3 -3
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +3 -3
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +1 -1
- package/dist/locales.js +33 -33
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.debug.map +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/receiver.compiled.map +1 -1
- package/dist/shaka-player.compiled.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.externs.js +4 -2
- package/dist/shaka-player.compiled.debug.js +278 -267
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +4 -2
- package/dist/shaka-player.compiled.js +815 -806
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +2 -1
- package/dist/shaka-player.ui.debug.d.ts +2 -1
- package/dist/shaka-player.ui.debug.externs.js +4 -2
- package/dist/shaka-player.ui.debug.js +305 -293
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +4 -2
- package/dist/shaka-player.ui.js +966 -959
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/upgrade.md +4 -2
- package/externs/shaka/player.js +15 -2
- package/karma.conf.js +4 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- package/lib/dash/dash_parser.js +2 -2
- package/lib/hls/hls_parser.js +211 -82
- package/lib/media/drm_engine.js +19 -0
- package/lib/media/media_source_engine.js +91 -37
- package/lib/media/segment_index.js +26 -1
- package/lib/media/segment_reference.js +10 -3
- package/lib/media/streaming_engine.js +2 -3
- package/lib/offline/indexeddb/storage_mechanism.js +4 -2
- package/lib/player.js +165 -107
- package/lib/text/vtt_text_parser.js +7 -7
- package/lib/util/config_utils.js +25 -0
- package/lib/util/error.js +7 -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 +14 -14
- package/lib/util/stream_utils.js +5 -2
- package/package.json +6 -3
- package/test/hls/hls_live_unit.js +79 -7
- package/test/hls/hls_parser_unit.js +102 -8
- package/test/hls/manifest_text_parser_unit.js +19 -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 +6 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +98 -1
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -0
- package/test/test/util/simple_fakes.js +31 -2
- package/test/text/vtt_text_parser_unit.js +64 -5
- package/test/util/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -0
- 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/.github/pull_request_template.md +0 -10
- package/docs/tutorials/gsoc-ideas-list.md +0 -3
|
@@ -42,10 +42,12 @@ application:
|
|
|
42
42
|
`manifest.defaultPresentationDelay` (deprecated in v3.0.0)
|
|
43
43
|
- Configuration of factories should be plain factory functions, not
|
|
44
44
|
constructors; these will not be invoked with `new` (deprecated in v3.1.0)
|
|
45
|
-
- `drm.initDataTransform`
|
|
46
|
-
minimum supported version of iOS is now 13)
|
|
45
|
+
- `drm.initDataTransform` now defaults to a no-op
|
|
47
46
|
- `streaming.smallGapLimit` and `streaming.jumpLargeGaps` have been removed;
|
|
48
47
|
all gaps will now be jumped
|
|
48
|
+
- `manifest.hls.useFullSegmentsForStartTime` has been removed; this setting
|
|
49
|
+
is no longer necessary, as we no longer fetch segments for start times in
|
|
50
|
+
the HLS parser
|
|
49
51
|
|
|
50
52
|
- Player API changes:
|
|
51
53
|
- `shaka.Player.prototype.addTextTrack()` has been replaced by
|
package/externs/shaka/player.js
CHANGED
|
@@ -618,6 +618,9 @@ shaka.extern.AdvancedDrmConfiguration;
|
|
|
618
618
|
* clearKeys: !Object.<string, string>,
|
|
619
619
|
* delayLicenseRequestUntilPlayed: boolean,
|
|
620
620
|
* advanced: Object.<string, shaka.extern.AdvancedDrmConfiguration>,
|
|
621
|
+
* initDataTransform:
|
|
622
|
+
* ((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
|
|
623
|
+
* undefined),
|
|
621
624
|
* logLicenseExchange: boolean,
|
|
622
625
|
* updateExpirationTime: number,
|
|
623
626
|
* preferredKeySystems: !Array.<string>
|
|
@@ -641,6 +644,14 @@ shaka.extern.AdvancedDrmConfiguration;
|
|
|
641
644
|
* <i>Optional.</i> <br>
|
|
642
645
|
* A dictionary which maps key system IDs to advanced DRM configuration for
|
|
643
646
|
* those key systems.
|
|
647
|
+
* @property
|
|
648
|
+
* {((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
|
|
649
|
+
* undefined)}
|
|
650
|
+
* initDataTransform
|
|
651
|
+
* <i>Optional.</i><br>
|
|
652
|
+
* If given, this function is called with the init data from the
|
|
653
|
+
* manifest/media and should return the (possibly transformed) init data to
|
|
654
|
+
* pass to the browser.
|
|
644
655
|
* @property {boolean} logLicenseExchange
|
|
645
656
|
* <i>Optional.</i><br>
|
|
646
657
|
* If set to <code>true</code>, prints logs containing the license exchange.
|
|
@@ -755,8 +766,10 @@ shaka.extern.DashManifestConfiguration;
|
|
|
755
766
|
* <i>Defaults to <code>'avc1.42E01E'</code>.</i>
|
|
756
767
|
* @property {boolean} ignoreManifestProgramDateTime
|
|
757
768
|
* If <code>true</code>, the HLS parser will ignore the
|
|
758
|
-
* <code>EXT-X-PROGRAM-DATE-TIME</code> tags in the manifest
|
|
759
|
-
*
|
|
769
|
+
* <code>EXT-X-PROGRAM-DATE-TIME</code> tags in the manifest and use media
|
|
770
|
+
* sequence numbers instead.
|
|
771
|
+
* Meant for streams where <code>EXT-X-PROGRAM-DATE-TIME</code> is incorrect
|
|
772
|
+
* or malformed.
|
|
760
773
|
* <i>Defaults to <code>false</code>.</i>
|
|
761
774
|
* @property {string} mediaPlaylistFullMimeType
|
|
762
775
|
* A string containing a full mime type, including both the basic mime type
|
package/karma.conf.js
CHANGED
|
@@ -363,6 +363,8 @@ module.exports = (config) => {
|
|
|
363
363
|
// If testing custom assets, we don't serve other unit or integration tests.
|
|
364
364
|
// External asset tests are the basis for custom asset testing, so this file
|
|
365
365
|
// is automatically included.
|
|
366
|
+
clientArgs.testFiles.push('demo/common/asset.js');
|
|
367
|
+
clientArgs.testFiles.push('demo/common/assets.js');
|
|
366
368
|
clientArgs.testFiles.push('test/player_external.js');
|
|
367
369
|
} else {
|
|
368
370
|
// In a normal test run, we serve unit tests.
|
|
@@ -374,6 +376,8 @@ module.exports = (config) => {
|
|
|
374
376
|
}
|
|
375
377
|
if (settings.external) {
|
|
376
378
|
// If --external is present, we serve external asset tests.
|
|
379
|
+
clientArgs.testFiles.push('demo/common/asset.js');
|
|
380
|
+
clientArgs.testFiles.push('demo/common/assets.js');
|
|
377
381
|
clientArgs.testFiles.push('test/**/*_external.js');
|
|
378
382
|
}
|
|
379
383
|
}
|
|
@@ -219,8 +219,9 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
219
219
|
* @export
|
|
220
220
|
*/
|
|
221
221
|
getBandwidthEstimate() {
|
|
222
|
+
const defaultBandwidthEstimate = this.getDefaultBandwidth_();
|
|
222
223
|
return this.bandwidthEstimator_.getBandwidthEstimate(
|
|
223
|
-
|
|
224
|
+
defaultBandwidthEstimate);
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -765,8 +765,8 @@ shaka.dash.DashParser = class {
|
|
|
765
765
|
// currently support that. Just choose one.
|
|
766
766
|
// TODO: https://github.com/shaka-project/shaka-player/issues/1528
|
|
767
767
|
stream.trickModeVideo = trickModeSet.streams.find((trickStream) =>
|
|
768
|
-
shaka.util.MimeUtils.
|
|
769
|
-
shaka.util.MimeUtils.
|
|
768
|
+
shaka.util.MimeUtils.getNormalizedCodec(stream.codecs) ==
|
|
769
|
+
shaka.util.MimeUtils.getNormalizedCodec(trickStream.codecs));
|
|
770
770
|
}
|
|
771
771
|
}
|
|
772
772
|
}
|
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!');
|
|
384
401
|
}
|
|
385
|
-
|
|
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
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
386
412
|
}
|
|
387
413
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
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
|
/**
|
|
@@ -524,7 +646,11 @@ shaka.hls.HlsParser = class {
|
|
|
524
646
|
|
|
525
647
|
// Now that we have generated all streams, we can determine the offset to
|
|
526
648
|
// apply to sync times.
|
|
527
|
-
this.
|
|
649
|
+
if (this.config_.hls.ignoreManifestProgramDateTime) {
|
|
650
|
+
this.syncStreamsWithSequenceNumber_();
|
|
651
|
+
} else {
|
|
652
|
+
this.syncStreamsWithProgramDateTime_();
|
|
653
|
+
}
|
|
528
654
|
|
|
529
655
|
if (this.aesEncrypted_ && variants.length == 0) {
|
|
530
656
|
// We do not support AES-128 encryption with HLS yet. Variants is null
|
|
@@ -539,15 +665,12 @@ shaka.hls.HlsParser = class {
|
|
|
539
665
|
|
|
540
666
|
// Find the min and max timestamp of the earliest segment in all streams.
|
|
541
667
|
// Find the minimum duration of all streams as well.
|
|
542
|
-
let minFirstTimestamp = Infinity;
|
|
543
668
|
let minDuration = Infinity;
|
|
544
|
-
|
|
545
669
|
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
546
|
-
minFirstTimestamp =
|
|
547
|
-
Math.min(minFirstTimestamp, streamInfo.minTimestamp);
|
|
548
670
|
if (streamInfo.stream.type != 'text') {
|
|
549
|
-
|
|
550
|
-
|
|
671
|
+
// Since everything is already offset to 0 (either by sync or by being
|
|
672
|
+
// VOD), only maxTimestamp is necessary to compute the duration.
|
|
673
|
+
minDuration = Math.min(minDuration, streamInfo.maxTimestamp);
|
|
551
674
|
}
|
|
552
675
|
}
|
|
553
676
|
|
|
@@ -591,18 +714,11 @@ shaka.hls.HlsParser = class {
|
|
|
591
714
|
segmentAvailabilityDuration);
|
|
592
715
|
}
|
|
593
716
|
} else {
|
|
594
|
-
// For VOD/EVENT content, offset everything back to 0.
|
|
595
|
-
// Use the minimum timestamp as the offset for all streams.
|
|
596
717
|
// Use the minimum duration as the presentation duration.
|
|
597
718
|
this.presentationTimeline_.setDuration(minDuration);
|
|
598
|
-
// Use a negative offset to adjust towards 0.
|
|
599
|
-
this.presentationTimeline_.offset(-minFirstTimestamp);
|
|
600
719
|
|
|
601
720
|
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.
|
|
721
|
+
// Fit the segments to the playlist duration.
|
|
606
722
|
streamInfo.stream.segmentIndex.fit(/* periodStart= */ 0, minDuration);
|
|
607
723
|
}
|
|
608
724
|
}
|
|
@@ -810,12 +926,7 @@ shaka.hls.HlsParser = class {
|
|
|
810
926
|
});
|
|
811
927
|
|
|
812
928
|
// 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) => {
|
|
929
|
+
const promises = mediaTags.map((tag) => {
|
|
819
930
|
return this.createStreamInfoFromMediaTag_(tag);
|
|
820
931
|
});
|
|
821
932
|
await Promise.all(promises);
|
|
@@ -1583,7 +1694,6 @@ shaka.hls.HlsParser = class {
|
|
|
1583
1694
|
throw error;
|
|
1584
1695
|
}
|
|
1585
1696
|
|
|
1586
|
-
const minTimestamp = segments[0].startTime;
|
|
1587
1697
|
const lastEndTime = segments[segments.length - 1].endTime;
|
|
1588
1698
|
/** @type {!shaka.media.SegmentIndex} */
|
|
1589
1699
|
const segmentIndex = new shaka.media.SegmentIndex(segments);
|
|
@@ -1641,7 +1751,6 @@ shaka.hls.HlsParser = class {
|
|
|
1641
1751
|
stream,
|
|
1642
1752
|
verbatimMediaPlaylistUri,
|
|
1643
1753
|
absoluteMediaPlaylistUri,
|
|
1644
|
-
minTimestamp,
|
|
1645
1754
|
maxTimestamp: lastEndTime,
|
|
1646
1755
|
mediaSequenceToStartTime,
|
|
1647
1756
|
canSkipSegments,
|
|
@@ -1825,7 +1934,7 @@ shaka.hls.HlsParser = class {
|
|
|
1825
1934
|
* @param {!Map.<string, string>} variables
|
|
1826
1935
|
* @param {string} absoluteMediaPlaylistUri
|
|
1827
1936
|
* @param {string} type
|
|
1828
|
-
* @return {
|
|
1937
|
+
* @return {shaka.media.SegmentReference}
|
|
1829
1938
|
* @private
|
|
1830
1939
|
*/
|
|
1831
1940
|
createSegmentReference_(
|
|
@@ -1853,15 +1962,9 @@ shaka.hls.HlsParser = class {
|
|
|
1853
1962
|
const dateTimeTag =
|
|
1854
1963
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
|
|
1855
1964
|
if (dateTimeTag && dateTimeTag.value) {
|
|
1856
|
-
|
|
1857
|
-
goog.asserts.assert(
|
|
1965
|
+
syncTime = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
|
|
1966
|
+
goog.asserts.assert(syncTime != null,
|
|
1858
1967
|
'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
1968
|
}
|
|
1866
1969
|
}
|
|
1867
1970
|
|
|
@@ -1870,17 +1973,42 @@ shaka.hls.HlsParser = class {
|
|
|
1870
1973
|
status = shaka.media.SegmentReference.Status.MISSING;
|
|
1871
1974
|
}
|
|
1872
1975
|
|
|
1976
|
+
if (!extinfTag) {
|
|
1977
|
+
if (hlsSegment.partialSegments.length == 0) {
|
|
1978
|
+
// EXTINF tag must be available if the segment has no partial segments.
|
|
1979
|
+
throw new shaka.util.Error(
|
|
1980
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1981
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1982
|
+
shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
|
|
1983
|
+
} else if (!this.lowLatencyMode_) {
|
|
1984
|
+
// Without EXTINF and without low-latency mode, partial segments get
|
|
1985
|
+
// ignored.
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1873
1990
|
// Create SegmentReferences for the partial segments.
|
|
1874
1991
|
const partialSegmentRefs = [];
|
|
1875
|
-
if (this.lowLatencyMode_
|
|
1992
|
+
if (this.lowLatencyMode_) {
|
|
1876
1993
|
for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
|
|
1877
1994
|
const item = hlsSegment.partialSegments[i];
|
|
1878
1995
|
const pPreviousReference = i == 0 ?
|
|
1879
1996
|
previousReference : partialSegmentRefs[partialSegmentRefs.length - 1];
|
|
1880
1997
|
const pStartTime = (i == 0) ? startTime : pPreviousReference.endTime;
|
|
1881
|
-
|
|
1882
|
-
//
|
|
1883
|
-
//
|
|
1998
|
+
|
|
1999
|
+
// If DURATION is missing from this partial segment, use the target
|
|
2000
|
+
// partial duration from the top of the playlist, which is a required
|
|
2001
|
+
// attribute for content with partial segments.
|
|
2002
|
+
const pDuration = Number(item.getAttributeValue('DURATION')) ||
|
|
2003
|
+
this.partialTargetDuration_;
|
|
2004
|
+
|
|
2005
|
+
// If for some reason we have neither an explicit duration, nor a target
|
|
2006
|
+
// partial duration, we should SKIP this partial segment to avoid
|
|
2007
|
+
// duplicating content in the presentation timeline.
|
|
2008
|
+
if (!pDuration) {
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1884
2012
|
const pEndTime = pStartTime + pDuration;
|
|
1885
2013
|
|
|
1886
2014
|
let pStartByte = 0;
|
|
@@ -1913,14 +2041,6 @@ shaka.hls.HlsParser = class {
|
|
|
1913
2041
|
/* appendWindowEnd= */ Infinity);
|
|
1914
2042
|
partialSegmentRefs.push(partial);
|
|
1915
2043
|
} // for-loop of hlsSegment.partialSegments
|
|
1916
|
-
} else {
|
|
1917
|
-
// EXTINF tag must be available if the segment has no partial segments.
|
|
1918
|
-
if (!extinfTag) {
|
|
1919
|
-
throw new shaka.util.Error(
|
|
1920
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
1921
|
-
shaka.util.Error.Category.MANIFEST,
|
|
1922
|
-
shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
|
|
1923
|
-
}
|
|
1924
2044
|
}
|
|
1925
2045
|
|
|
1926
2046
|
// If the segment has EXTINF tag, set the segment's end time, start byte
|
|
@@ -1939,6 +2059,7 @@ shaka.hls.HlsParser = class {
|
|
|
1939
2059
|
} else {
|
|
1940
2060
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
1941
2061
|
}
|
|
2062
|
+
|
|
1942
2063
|
// If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
|
|
1943
2064
|
// base on the byterange information. If segment has no EXT-X-BYTERANGE tag
|
|
1944
2065
|
// and has partial segments, set the start byte and end byte base on the
|
|
@@ -2067,16 +2188,13 @@ shaka.hls.HlsParser = class {
|
|
|
2067
2188
|
firstStartTime = mediaSequenceToStartTime.get(position);
|
|
2068
2189
|
}
|
|
2069
2190
|
|
|
2070
|
-
const firstSegmentUri = hlsSegments[0].absoluteUri;
|
|
2071
|
-
shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
|
|
2072
|
-
'starts at', firstStartTime);
|
|
2073
|
-
|
|
2074
2191
|
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
2075
2192
|
const references = [];
|
|
2076
2193
|
|
|
2194
|
+
let previousReference = null;
|
|
2195
|
+
|
|
2077
2196
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2078
2197
|
const item = hlsSegments[i];
|
|
2079
|
-
const previousReference = references[references.length - 1];
|
|
2080
2198
|
const startTime =
|
|
2081
2199
|
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2082
2200
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
@@ -2106,8 +2224,18 @@ shaka.hls.HlsParser = class {
|
|
|
2106
2224
|
variables,
|
|
2107
2225
|
playlist.absoluteUri,
|
|
2108
2226
|
type);
|
|
2109
|
-
|
|
2110
|
-
|
|
2227
|
+
previousReference = reference;
|
|
2228
|
+
|
|
2229
|
+
if (reference) {
|
|
2230
|
+
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
|
2231
|
+
this.minSequenceNumber_ != null &&
|
|
2232
|
+
position < this.minSequenceNumber_) {
|
|
2233
|
+
// This segment is ignored as part of our fallback synchronization
|
|
2234
|
+
// method.
|
|
2235
|
+
} else {
|
|
2236
|
+
references.push(reference);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2111
2239
|
}
|
|
2112
2240
|
|
|
2113
2241
|
// If some segments have sync times, but not all, extrapolate the sync
|
|
@@ -2236,12 +2364,16 @@ shaka.hls.HlsParser = class {
|
|
|
2236
2364
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2237
2365
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2238
2366
|
|
|
2367
|
+
// If you wait long enough, requesting the first segment can fail
|
|
2368
|
+
// because it has fallen off the left edge of DVR, so to be safer,
|
|
2369
|
+
// let's request the middle segment.
|
|
2239
2370
|
goog.asserts.assert(playlist.segments.length,
|
|
2240
2371
|
'Playlist should have segments!');
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2372
|
+
const middleSegmentIdx = Math.trunc((playlist.segments.length - 1) / 2);
|
|
2373
|
+
const middleSegmentUri = this.variableSubstitution_(
|
|
2374
|
+
playlist.segments[middleSegmentIdx].absoluteUri, variables);
|
|
2243
2375
|
|
|
2244
|
-
const parsedUri = new goog.Uri(
|
|
2376
|
+
const parsedUri = new goog.Uri(middleSegmentUri);
|
|
2245
2377
|
const extension = parsedUri.getPath().split('.').pop();
|
|
2246
2378
|
const map = HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_[contentType];
|
|
2247
2379
|
|
|
@@ -2271,7 +2403,7 @@ shaka.hls.HlsParser = class {
|
|
|
2271
2403
|
// If unable to guess mime type, request a segment and try getting it
|
|
2272
2404
|
// from the response.
|
|
2273
2405
|
const headRequest = shaka.net.NetworkingEngine.makeRequest(
|
|
2274
|
-
[
|
|
2406
|
+
[middleSegmentUri], this.config_.retryParameters);
|
|
2275
2407
|
headRequest.method = 'HEAD';
|
|
2276
2408
|
|
|
2277
2409
|
const response = await this.makeNetworkRequest_(
|
|
@@ -2558,7 +2690,6 @@ shaka.hls.HlsParser = class {
|
|
|
2558
2690
|
* stream: !shaka.extern.Stream,
|
|
2559
2691
|
* verbatimMediaPlaylistUri: string,
|
|
2560
2692
|
* absoluteMediaPlaylistUri: string,
|
|
2561
|
-
* minTimestamp: number,
|
|
2562
2693
|
* maxTimestamp: number,
|
|
2563
2694
|
* mediaSequenceToStartTime: !Map.<number, number>,
|
|
2564
2695
|
* canSkipSegments: boolean
|
|
@@ -2577,8 +2708,6 @@ shaka.hls.HlsParser = class {
|
|
|
2577
2708
|
* @property {string} absoluteMediaPlaylistUri
|
|
2578
2709
|
* The absolute media playlist URI, resolved relative to the master playlist
|
|
2579
2710
|
* and updated to reflect any redirects.
|
|
2580
|
-
* @property {number} minTimestamp
|
|
2581
|
-
* The minimum timestamp found in the stream.
|
|
2582
2711
|
* @property {number} maxTimestamp
|
|
2583
2712
|
* The maximum timestamp found in the stream.
|
|
2584
2713
|
* @property {!Map.<number, number>} mediaSequenceToStartTime
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -1253,6 +1253,22 @@ shaka.media.DrmEngine = class {
|
|
|
1253
1253
|
};
|
|
1254
1254
|
this.activeSessions_.set(session, metadata);
|
|
1255
1255
|
|
|
1256
|
+
try {
|
|
1257
|
+
initData = this.config_.initDataTransform(
|
|
1258
|
+
initData, initDataType, this.currentDrmInfo_);
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
let shakaError = error;
|
|
1261
|
+
if (!(error instanceof shaka.util.Error)) {
|
|
1262
|
+
shakaError = new shaka.util.Error(
|
|
1263
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1264
|
+
shaka.util.Error.Category.DRM,
|
|
1265
|
+
shaka.util.Error.Code.INIT_DATA_TRANSFORM_ERROR,
|
|
1266
|
+
error);
|
|
1267
|
+
}
|
|
1268
|
+
this.onError_(shakaError);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1256
1272
|
if (this.config_.logLicenseExchange) {
|
|
1257
1273
|
const str = shaka.util.Uint8ArrayUtils.toBase64(initData);
|
|
1258
1274
|
shaka.log.info('EME init data: type=', initDataType, 'data=', str);
|
|
@@ -1408,6 +1424,9 @@ shaka.media.DrmEngine = class {
|
|
|
1408
1424
|
}
|
|
1409
1425
|
return;
|
|
1410
1426
|
}
|
|
1427
|
+
if (this.destroyer_.destroyed()) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1411
1430
|
|
|
1412
1431
|
const updateEvent = new shaka.util.FakeEvent('drmsessionupdate');
|
|
1413
1432
|
this.playerInterface_.onEvent(updateEvent);
|