shaka-player 4.7.13 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +1 -0
- package/CONTRIBUTORS +1 -0
- package/LICENSE +54 -0
- package/README.md +22 -1
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +34 -23
- package/dist/locales.js +44 -34
- package/dist/shaka-player.compiled.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.externs.js +474 -337
- package/dist/shaka-player.compiled.debug.js +949 -832
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +474 -337
- package/dist/shaka-player.compiled.js +1237 -1171
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +257 -47
- package/dist/shaka-player.ui.debug.d.ts +257 -47
- package/dist/shaka-player.ui.debug.externs.js +591 -376
- package/dist/shaka-player.ui.debug.js +1165 -965
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +591 -376
- package/dist/shaka-player.ui.js +1521 -1404
- package/dist/shaka-player.ui.map +4 -4
- package/externs/device_sensor_event.js +18 -0
- package/externs/hisense.js +16 -0
- package/externs/playstation.js +31 -0
- package/externs/shaka/abr_manager.js +3 -1
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/codecs.js +13 -0
- package/externs/shaka/manifest.js +58 -6
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/player.js +184 -13
- package/externs/shaka/text.js +19 -0
- package/externs/tizen.js +41 -0
- package/externs/webos.js +19 -0
- package/externs/xbox.js +43 -0
- package/lib/abr/simple_abr_manager.js +9 -1
- package/lib/ads/ad_manager.js +19 -2
- package/lib/ads/client_side_ad_manager.js +5 -3
- package/lib/ads/media_tailor_ad_manager.js +9 -0
- package/lib/cast/cast_receiver.js +0 -14
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/mp4_cea_parser.js +6 -1
- package/lib/dash/content_protection.js +97 -53
- package/lib/dash/dash_parser.js +230 -176
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +27 -21
- package/lib/dash/segment_list.js +18 -16
- package/lib/dash/segment_template.js +26 -21
- package/lib/hls/hls_parser.js +358 -164
- package/lib/media/buffering_observer.js +25 -1
- package/lib/media/drm_engine.js +91 -71
- package/lib/media/manifest_filterer.js +220 -0
- package/lib/media/media_source_engine.js +34 -5
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +40 -30
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +31 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +319 -63
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +74 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/storage.js +3 -3
- package/lib/player.js +882 -673
- package/lib/polyfill/media_capabilities.js +16 -1
- package/lib/text/simple_text_displayer.js +9 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/text_engine.js +16 -3
- package/lib/text/ttml_text_parser.js +116 -114
- package/lib/text/ui_text_displayer.js +28 -11
- package/lib/text/vtt_text_parser.js +54 -115
- package/lib/transmuxer/opus.js +91 -0
- package/lib/transmuxer/ts_transmuxer.js +115 -0
- package/lib/util/cmcd_manager.js +152 -3
- package/lib/util/dom_utils.js +11 -0
- package/lib/util/error.js +5 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/manifest_parser_utils.js +97 -3
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +19 -0
- package/lib/util/platform.js +161 -9
- package/lib/util/player_configuration.js +61 -3
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +16 -6
- package/lib/util/string_utils.js +36 -0
- package/lib/util/tXml.js +814 -0
- package/lib/util/ts_parser.js +50 -0
- package/lib/util/xml_utils.js +10 -338
- package/package.json +2 -2
- package/shaka-player.uncompiled.js +8 -0
- package/ui/controls.js +187 -5
- package/ui/enums.js +2 -0
- package/ui/externs/ui.js +30 -3
- package/ui/gl_matrix/matrix_4x4.js +568 -0
- package/ui/gl_matrix/matrix_quaternion.js +45 -0
- package/ui/less/containers.less +11 -0
- package/ui/locales/en-GB.json +2 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +2 -0
- package/ui/locales/source.json +8 -0
- package/ui/recenter_vr.js +126 -0
- package/ui/resolution_selection.js +25 -5
- package/ui/statistics_button.js +12 -0
- package/ui/text_selection.js +1 -1
- package/ui/toggle_stereoscopic.js +127 -0
- package/ui/ui.js +74 -38
- package/ui/vr_manager.js +557 -0
- package/ui/vr_utils.js +232 -0
- package/ui/vr_webgl.js +697 -0
package/lib/hls/hls_parser.js
CHANGED
|
@@ -37,9 +37,9 @@ goog.require('shaka.util.OperationManager');
|
|
|
37
37
|
goog.require('shaka.util.Pssh');
|
|
38
38
|
goog.require('shaka.media.SegmentUtils');
|
|
39
39
|
goog.require('shaka.util.Timer');
|
|
40
|
+
goog.require('shaka.util.TXml');
|
|
40
41
|
goog.require('shaka.util.Platform');
|
|
41
42
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
42
|
-
goog.require('shaka.util.XmlUtils');
|
|
43
43
|
goog.requireType('shaka.hls.Segment');
|
|
44
44
|
|
|
45
45
|
|
|
@@ -217,6 +217,18 @@ shaka.hls.HlsParser = class {
|
|
|
217
217
|
* {!Map.<string, !shaka.media.InitSegmentReference>} */
|
|
218
218
|
this.mapTagToInitSegmentRefMap_ = new Map();
|
|
219
219
|
|
|
220
|
+
/** @private {Map.<string, !shaka.extern.aesKey>} */
|
|
221
|
+
this.aesKeyInfoMap_ = new Map();
|
|
222
|
+
|
|
223
|
+
/** @private {Map.<string, !Promise.<shaka.extern.Response>>} */
|
|
224
|
+
this.aesKeyMap_ = new Map();
|
|
225
|
+
|
|
226
|
+
/** @private {Map.<string, !Promise.<shaka.extern.Response>>} */
|
|
227
|
+
this.identityKeyMap_ = new Map();
|
|
228
|
+
|
|
229
|
+
/** @private {Map.<!shaka.media.InitSegmentReference, ?string>} */
|
|
230
|
+
this.identityKidMap_ = new Map();
|
|
231
|
+
|
|
220
232
|
/** @private {boolean} */
|
|
221
233
|
this.lowLatencyMode_ = false;
|
|
222
234
|
|
|
@@ -297,6 +309,10 @@ shaka.hls.HlsParser = class {
|
|
|
297
309
|
this.groupIdToCodecsMap_.clear();
|
|
298
310
|
this.globalVariables_.clear();
|
|
299
311
|
this.mapTagToInitSegmentRefMap_.clear();
|
|
312
|
+
this.aesKeyInfoMap_.clear();
|
|
313
|
+
this.aesKeyMap_.clear();
|
|
314
|
+
this.identityKeyMap_.clear();
|
|
315
|
+
this.identityKidMap_.clear();
|
|
300
316
|
|
|
301
317
|
if (this.contentSteeringManager_) {
|
|
302
318
|
this.contentSteeringManager_.destroy();
|
|
@@ -434,7 +450,8 @@ shaka.hls.HlsParser = class {
|
|
|
434
450
|
|
|
435
451
|
const mediaSequenceToStartTime =
|
|
436
452
|
this.getMediaSequenceToStartTimeFor_(streamInfo);
|
|
437
|
-
const {keyIds, drmInfos} = this.parseDrmInfo_(
|
|
453
|
+
const {keyIds, drmInfos} = await this.parseDrmInfo_(
|
|
454
|
+
playlist, stream.mimeType, streamInfo.getUris, mediaVariables);
|
|
438
455
|
|
|
439
456
|
const keysAreEqual =
|
|
440
457
|
(a, b) => a.size === b.size && [...a].every((value) => b.has(value));
|
|
@@ -447,7 +464,7 @@ shaka.hls.HlsParser = class {
|
|
|
447
464
|
|
|
448
465
|
const {segments, bandwidth} = this.createSegments_(
|
|
449
466
|
playlist, stream, mediaSequenceToStartTime, mediaVariables,
|
|
450
|
-
streamInfo.getUris);
|
|
467
|
+
streamInfo.getUris, streamInfo.type);
|
|
451
468
|
if (bandwidth) {
|
|
452
469
|
stream.bandwidth = bandwidth;
|
|
453
470
|
}
|
|
@@ -581,6 +598,9 @@ shaka.hls.HlsParser = class {
|
|
|
581
598
|
this.minSequenceNumber_);
|
|
582
599
|
|
|
583
600
|
for (const streamInfo of streamInfos) {
|
|
601
|
+
if (!this.ignoreManifestProgramDateTimeFor_(streamInfo.type)) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
584
604
|
const segmentIndex = streamInfo.stream.segmentIndex;
|
|
585
605
|
if (segmentIndex) {
|
|
586
606
|
// Drop any earlier references.
|
|
@@ -637,6 +657,9 @@ shaka.hls.HlsParser = class {
|
|
|
637
657
|
shaka.log.debug('Syncing HLS streams against base time:', lowestSyncTime);
|
|
638
658
|
|
|
639
659
|
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
660
|
+
if (this.ignoreManifestProgramDateTimeFor_(streamInfo.type)) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
640
663
|
const segmentIndex = streamInfo.stream.segmentIndex;
|
|
641
664
|
if (segmentIndex != null) {
|
|
642
665
|
// A segment's startTime should be based on its syncTime vs the lowest
|
|
@@ -718,12 +741,12 @@ shaka.hls.HlsParser = class {
|
|
|
718
741
|
/** @type {!Array.<!shaka.extern.Stream>} */
|
|
719
742
|
let imageStreams = [];
|
|
720
743
|
|
|
744
|
+
const getUris = () => {
|
|
745
|
+
return [uri];
|
|
746
|
+
};
|
|
747
|
+
|
|
721
748
|
// Parsing a media playlist results in a single-variant stream.
|
|
722
749
|
if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
|
|
723
|
-
const getUris = () => {
|
|
724
|
-
return [uri];
|
|
725
|
-
};
|
|
726
|
-
|
|
727
750
|
// Get necessary info for this stream. These are things we would normally
|
|
728
751
|
// find from the master playlist (e.g. from values on EXT-X-MEDIA tags).
|
|
729
752
|
const basicInfo =
|
|
@@ -737,6 +760,7 @@ shaka.hls.HlsParser = class {
|
|
|
737
760
|
const channelsCount = basicInfo.channelCount;
|
|
738
761
|
const sampleRate = basicInfo.sampleRate;
|
|
739
762
|
const closedCaptions = basicInfo.closedCaptions;
|
|
763
|
+
const videoRange = basicInfo.videoRange;
|
|
740
764
|
|
|
741
765
|
// Some values we cannot figure out, and aren't important enough to ask
|
|
742
766
|
// the user to provide through config values. A lot of these are only
|
|
@@ -757,8 +781,7 @@ shaka.hls.HlsParser = class {
|
|
|
757
781
|
|
|
758
782
|
if (type == 'video') {
|
|
759
783
|
this.addVideoAttributes_(streamInfo.stream, width, height,
|
|
760
|
-
/* frameRate= */ null, /*
|
|
761
|
-
/* videoLayout= */ null);
|
|
784
|
+
/* frameRate= */ null, videoRange, /* videoLayout= */ null);
|
|
762
785
|
}
|
|
763
786
|
|
|
764
787
|
// Wrap the stream from that stream info with a variant.
|
|
@@ -805,8 +828,9 @@ shaka.hls.HlsParser = class {
|
|
|
805
828
|
this.parseCodecs_(variantTags);
|
|
806
829
|
|
|
807
830
|
this.parseClosedCaptions_(mediaTags);
|
|
808
|
-
variants =
|
|
809
|
-
|
|
831
|
+
variants = await this.createVariantsForTags_(
|
|
832
|
+
variantTags, sessionKeyTags, mediaTags, getUris,
|
|
833
|
+
this.globalVariables_);
|
|
810
834
|
textStreams = this.parseTexts_(mediaTags);
|
|
811
835
|
imageStreams = await this.parseImages_(imageTags, iFrameTags);
|
|
812
836
|
}
|
|
@@ -876,6 +900,19 @@ shaka.hls.HlsParser = class {
|
|
|
876
900
|
}
|
|
877
901
|
|
|
878
902
|
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
|
|
903
|
+
|
|
904
|
+
if (variants.length == 1) {
|
|
905
|
+
const createSegmentIndexPromises = [];
|
|
906
|
+
const variant = variants[0];
|
|
907
|
+
for (const stream of [variant.video, variant.audio]) {
|
|
908
|
+
if (stream && !stream.segmentIndex) {
|
|
909
|
+
createSegmentIndexPromises.push(stream.createSegmentIndex());
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (createSegmentIndexPromises.length > 0) {
|
|
913
|
+
await Promise.all(createSegmentIndexPromises);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
879
916
|
}
|
|
880
917
|
|
|
881
918
|
/**
|
|
@@ -986,27 +1023,27 @@ shaka.hls.HlsParser = class {
|
|
|
986
1023
|
this.presentationTimeline_.setSegmentAvailabilityDuration(
|
|
987
1024
|
segmentAvailabilityDuration);
|
|
988
1025
|
}
|
|
989
|
-
|
|
990
|
-
if (!this.presentationTimeline_.isStartTimeLocked()) {
|
|
991
|
-
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
992
|
-
if (!streamInfo.stream.segmentIndex) {
|
|
993
|
-
continue; // Not active.
|
|
994
|
-
}
|
|
995
|
-
if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
|
|
996
|
-
continue;
|
|
997
|
-
}
|
|
998
|
-
const firstReference = streamInfo.stream.segmentIndex.get(0);
|
|
999
|
-
if (firstReference && firstReference.syncTime) {
|
|
1000
|
-
const syncTime = firstReference.syncTime;
|
|
1001
|
-
this.presentationTimeline_.setInitialProgramDateTime(syncTime);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
1026
|
} else {
|
|
1006
1027
|
// Use the minimum duration as the presentation duration.
|
|
1007
1028
|
this.presentationTimeline_.setDuration(this.getMinDuration_());
|
|
1008
1029
|
}
|
|
1009
1030
|
|
|
1031
|
+
if (!this.presentationTimeline_.isStartTimeLocked()) {
|
|
1032
|
+
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
1033
|
+
if (!streamInfo.stream.segmentIndex) {
|
|
1034
|
+
continue; // Not active.
|
|
1035
|
+
}
|
|
1036
|
+
if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
const firstReference = streamInfo.stream.segmentIndex.get(0);
|
|
1040
|
+
if (firstReference && firstReference.syncTime) {
|
|
1041
|
+
const syncTime = firstReference.syncTime;
|
|
1042
|
+
this.presentationTimeline_.setInitialProgramDateTime(syncTime);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1010
1047
|
// This is the first point where we have a meaningful presentation start
|
|
1011
1048
|
// time, and we need to tell PresentationTimeline that so that it can
|
|
1012
1049
|
// maintain consistency from here on.
|
|
@@ -1323,37 +1360,48 @@ shaka.hls.HlsParser = class {
|
|
|
1323
1360
|
* from the playlist.
|
|
1324
1361
|
* @param {!Array.<!shaka.hls.Tag>} mediaTags EXT-X-MEDIA tags from the
|
|
1325
1362
|
* playlist.
|
|
1326
|
-
* @
|
|
1363
|
+
* @param {function():!Array.<string>} getUris
|
|
1364
|
+
* @param {?Map.<string, string>=} variables
|
|
1365
|
+
* @return {!Promise.<!Array.<!shaka.extern.Variant>>}
|
|
1327
1366
|
* @private
|
|
1328
1367
|
*/
|
|
1329
|
-
createVariantsForTags_(tags, sessionKeyTags, mediaTags
|
|
1368
|
+
async createVariantsForTags_(tags, sessionKeyTags, mediaTags, getUris,
|
|
1369
|
+
variables) {
|
|
1330
1370
|
// EXT-X-SESSION-KEY processing
|
|
1331
1371
|
const drmInfos = [];
|
|
1332
1372
|
const keyIds = new Set();
|
|
1333
1373
|
if (sessionKeyTags.length > 0) {
|
|
1334
1374
|
for (const drmTag of sessionKeyTags) {
|
|
1335
1375
|
const method = drmTag.getRequiredAttrValue('METHOD');
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1376
|
+
// According to the HLS spec, KEYFORMAT is optional and implicitly
|
|
1377
|
+
// defaults to "identity".
|
|
1378
|
+
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
|
|
1379
|
+
const keyFormat =
|
|
1380
|
+
drmTag.getAttributeValue('KEYFORMAT') || 'identity';
|
|
1381
|
+
let drmInfo = null;
|
|
1382
|
+
if (method == 'NONE' || this.isAesMethod_(method)) {
|
|
1383
|
+
continue;
|
|
1384
|
+
} else if (keyFormat == 'identity') {
|
|
1385
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1386
|
+
drmInfo = await this.identityDrmParser_(
|
|
1387
|
+
drmTag, /* mimeType= */ '', getUris,
|
|
1388
|
+
/* initSegmentRef= */ null, variables);
|
|
1389
|
+
} else {
|
|
1342
1390
|
const drmParser =
|
|
1343
1391
|
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
|
1344
1392
|
|
|
1345
|
-
|
|
1393
|
+
drmInfo = drmParser ?
|
|
1346
1394
|
drmParser(drmTag, /* mimeType= */ '') : null;
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1395
|
+
}
|
|
1396
|
+
if (drmInfo) {
|
|
1397
|
+
if (drmInfo.keyIds) {
|
|
1398
|
+
for (const keyId of drmInfo.keyIds) {
|
|
1399
|
+
keyIds.add(keyId);
|
|
1352
1400
|
}
|
|
1353
|
-
drmInfos.push(drmInfo);
|
|
1354
|
-
} else {
|
|
1355
|
-
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1356
1401
|
}
|
|
1402
|
+
drmInfos.push(drmInfo);
|
|
1403
|
+
} else {
|
|
1404
|
+
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1357
1405
|
}
|
|
1358
1406
|
}
|
|
1359
1407
|
}
|
|
@@ -1587,6 +1635,9 @@ shaka.hls.HlsParser = class {
|
|
|
1587
1635
|
getCodecsForVariantTag_(tag) {
|
|
1588
1636
|
let codecsString = tag.getAttributeValue('CODECS') || '';
|
|
1589
1637
|
|
|
1638
|
+
const supplementalCodecsString =
|
|
1639
|
+
tag.getAttributeValue('SUPPLEMENTAL-CODECS');
|
|
1640
|
+
|
|
1590
1641
|
this.codecInfoInManifest_ = codecsString.length > 0;
|
|
1591
1642
|
|
|
1592
1643
|
if (!this.codecInfoInManifest_ && !this.config_.hls.disableCodecGuessing) {
|
|
@@ -1607,34 +1658,15 @@ shaka.hls.HlsParser = class {
|
|
|
1607
1658
|
/** @type {!Array.<string>} */
|
|
1608
1659
|
const codecs = codecsString.split(/\s*,\s*/);
|
|
1609
1660
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
* @return {!Array.<string>} codecs
|
|
1617
|
-
* @private
|
|
1618
|
-
*/
|
|
1619
|
-
filterDuplicateCodecs_(codecs) {
|
|
1620
|
-
// Filter out duplicate codecs.
|
|
1621
|
-
const seen = new Set();
|
|
1622
|
-
const ret = [];
|
|
1623
|
-
for (const codec of codecs) {
|
|
1624
|
-
// HLS says the CODECS field needs to include all codecs that appear in
|
|
1625
|
-
// the content. This means that if the content changes profiles, it should
|
|
1626
|
-
// include both. Since all known browsers support changing profiles
|
|
1627
|
-
// without any other work, just ignore them. See also:
|
|
1628
|
-
// https://github.com/shaka-project/shaka-player/issues/1817
|
|
1629
|
-
const shortCodec = shaka.util.MimeUtils.getCodecBase(codec);
|
|
1630
|
-
if (!seen.has(shortCodec)) {
|
|
1631
|
-
ret.push(codec);
|
|
1632
|
-
seen.add(shortCodec);
|
|
1633
|
-
} else {
|
|
1634
|
-
shaka.log.debug('Ignoring duplicate codec');
|
|
1635
|
-
}
|
|
1661
|
+
if (supplementalCodecsString) {
|
|
1662
|
+
const supplementalCodecs = supplementalCodecsString.split(/\s*,\s*/)
|
|
1663
|
+
.map((codec) => {
|
|
1664
|
+
return codec.split('/')[0];
|
|
1665
|
+
});
|
|
1666
|
+
codecs.push(...supplementalCodecs);
|
|
1636
1667
|
}
|
|
1637
|
-
|
|
1668
|
+
|
|
1669
|
+
return shaka.media.SegmentUtils.codecsFiltering(codecs);
|
|
1638
1670
|
}
|
|
1639
1671
|
|
|
1640
1672
|
/**
|
|
@@ -2444,7 +2476,23 @@ shaka.hls.HlsParser = class {
|
|
|
2444
2476
|
this.syncStreamsWithSequenceNumber_(streamInfos);
|
|
2445
2477
|
} else {
|
|
2446
2478
|
this.syncStreamsWithProgramDateTime_(streamInfos);
|
|
2479
|
+
if (this.config_.hls.ignoreManifestProgramDateTimeForTypes.length > 0) {
|
|
2480
|
+
this.syncStreamsWithSequenceNumber_(streamInfos);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
/**
|
|
2486
|
+
* @param {string} type
|
|
2487
|
+
* @return {boolean}
|
|
2488
|
+
* @private
|
|
2489
|
+
*/
|
|
2490
|
+
ignoreManifestProgramDateTimeFor_(type) {
|
|
2491
|
+
if (this.config_.hls.ignoreManifestProgramDateTime) {
|
|
2492
|
+
return true;
|
|
2447
2493
|
}
|
|
2494
|
+
const forTypes = this.config_.hls.ignoreManifestProgramDateTimeForTypes;
|
|
2495
|
+
return forTypes.includes(type);
|
|
2448
2496
|
}
|
|
2449
2497
|
|
|
2450
2498
|
/**
|
|
@@ -2534,7 +2582,7 @@ shaka.hls.HlsParser = class {
|
|
|
2534
2582
|
}
|
|
2535
2583
|
|
|
2536
2584
|
const {drmInfos, keyIds, encrypted, aesEncrypted} =
|
|
2537
|
-
|
|
2585
|
+
await this.parseDrmInfo_(playlist, mimeType, getUris, mediaVariables);
|
|
2538
2586
|
|
|
2539
2587
|
if (encrypted && !drmInfos.length && !aesEncrypted) {
|
|
2540
2588
|
throw new shaka.util.Error(
|
|
@@ -2556,7 +2604,8 @@ shaka.hls.HlsParser = class {
|
|
|
2556
2604
|
this.mediaSequenceToStartTimeByType_.get(type) : new Map();
|
|
2557
2605
|
|
|
2558
2606
|
const {segments, bandwidth} = this.createSegments_(
|
|
2559
|
-
playlist, stream, mediaSequenceToStartTime, mediaVariables, getUris
|
|
2607
|
+
playlist, stream, mediaSequenceToStartTime, mediaVariables, getUris,
|
|
2608
|
+
type);
|
|
2560
2609
|
if (bandwidth) {
|
|
2561
2610
|
stream.bandwidth = bandwidth;
|
|
2562
2611
|
}
|
|
@@ -2753,22 +2802,31 @@ shaka.hls.HlsParser = class {
|
|
|
2753
2802
|
/**
|
|
2754
2803
|
* @param {!shaka.hls.Playlist} playlist
|
|
2755
2804
|
* @param {string} mimeType
|
|
2756
|
-
* @
|
|
2805
|
+
* @param {function():!Array.<string>} getUris
|
|
2806
|
+
* @param {?Map.<string, string>=} variables
|
|
2807
|
+
* @return {Promise.<{
|
|
2757
2808
|
* drmInfos: !Array.<shaka.extern.DrmInfo>,
|
|
2758
2809
|
* keyIds: !Set.<string>,
|
|
2759
2810
|
* encrypted: boolean,
|
|
2760
2811
|
* aesEncrypted: boolean
|
|
2761
|
-
* }}
|
|
2812
|
+
* }>}
|
|
2762
2813
|
* @private
|
|
2763
2814
|
*/
|
|
2764
|
-
parseDrmInfo_(playlist, mimeType) {
|
|
2765
|
-
/** @type {!
|
|
2766
|
-
const
|
|
2815
|
+
async parseDrmInfo_(playlist, mimeType, getUris, variables) {
|
|
2816
|
+
/** @type {!Map<!shaka.hls.Tag, ?shaka.media.InitSegmentReference>} */
|
|
2817
|
+
const drmTagsMap = new Map();
|
|
2767
2818
|
if (playlist.segments) {
|
|
2768
2819
|
for (const segment of playlist.segments) {
|
|
2769
2820
|
const segmentKeyTags = shaka.hls.Utils.filterTagsByName(segment.tags,
|
|
2770
2821
|
'EXT-X-KEY');
|
|
2771
|
-
|
|
2822
|
+
let initSegmentRef = null;
|
|
2823
|
+
if (segmentKeyTags.length) {
|
|
2824
|
+
initSegmentRef = this.getInitSegmentReference_(playlist,
|
|
2825
|
+
segment.tags, getUris, variables);
|
|
2826
|
+
for (const segmentKeyTag of segmentKeyTags) {
|
|
2827
|
+
drmTagsMap.set(segmentKeyTag, initSegmentRef);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2772
2830
|
}
|
|
2773
2831
|
}
|
|
2774
2832
|
|
|
@@ -2779,34 +2837,43 @@ shaka.hls.HlsParser = class {
|
|
|
2779
2837
|
const drmInfos = [];
|
|
2780
2838
|
const keyIds = new Set();
|
|
2781
2839
|
|
|
2782
|
-
for (const
|
|
2840
|
+
for (const [key, value] of drmTagsMap) {
|
|
2841
|
+
const drmTag = /** @type {!shaka.hls.Tag} */ (key);
|
|
2842
|
+
const initSegmentRef =
|
|
2843
|
+
/** @type {?shaka.media.InitSegmentReference} */ (value);
|
|
2783
2844
|
const method = drmTag.getRequiredAttrValue('METHOD');
|
|
2784
2845
|
if (method != 'NONE') {
|
|
2785
2846
|
encrypted = true;
|
|
2786
2847
|
|
|
2787
|
-
|
|
2848
|
+
// According to the HLS spec, KEYFORMAT is optional and implicitly
|
|
2849
|
+
// defaults to "identity".
|
|
2850
|
+
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
|
|
2851
|
+
const keyFormat =
|
|
2852
|
+
drmTag.getAttributeValue('KEYFORMAT') || 'identity';
|
|
2853
|
+
let drmInfo = null;
|
|
2854
|
+
|
|
2855
|
+
if (this.isAesMethod_(method)) {
|
|
2788
2856
|
// These keys are handled separately.
|
|
2789
2857
|
aesEncrypted = true;
|
|
2858
|
+
continue;
|
|
2859
|
+
} else if (keyFormat == 'identity') {
|
|
2860
|
+
// eslint-disable-next-line no-await-in-loop
|
|
2861
|
+
drmInfo = await this.identityDrmParser_(
|
|
2862
|
+
drmTag, mimeType, getUris, initSegmentRef, variables);
|
|
2790
2863
|
} else {
|
|
2791
|
-
// According to the HLS spec, KEYFORMAT is optional and implicitly
|
|
2792
|
-
// defaults to "identity".
|
|
2793
|
-
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
|
|
2794
|
-
const keyFormat =
|
|
2795
|
-
drmTag.getAttributeValue('KEYFORMAT') || 'identity';
|
|
2796
2864
|
const drmParser =
|
|
2797
2865
|
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
}
|
|
2866
|
+
drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
|
|
2867
|
+
}
|
|
2868
|
+
if (drmInfo) {
|
|
2869
|
+
if (drmInfo.keyIds) {
|
|
2870
|
+
for (const keyId of drmInfo.keyIds) {
|
|
2871
|
+
keyIds.add(keyId);
|
|
2805
2872
|
}
|
|
2806
|
-
drmInfos.push(drmInfo);
|
|
2807
|
-
} else {
|
|
2808
|
-
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
2809
2873
|
}
|
|
2874
|
+
drmInfos.push(drmInfo);
|
|
2875
|
+
} else {
|
|
2876
|
+
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
2810
2877
|
}
|
|
2811
2878
|
}
|
|
2812
2879
|
}
|
|
@@ -2819,14 +2886,14 @@ shaka.hls.HlsParser = class {
|
|
|
2819
2886
|
* @param {!shaka.hls.Playlist} playlist
|
|
2820
2887
|
* @param {function():!Array.<string>} getUris
|
|
2821
2888
|
* @param {?Map.<string, string>=} variables
|
|
2822
|
-
* @return {!shaka.extern.
|
|
2889
|
+
* @return {!shaka.extern.aesKey}
|
|
2823
2890
|
* @private
|
|
2824
2891
|
*/
|
|
2825
|
-
|
|
2892
|
+
parseAESDrmTag_(drmTag, playlist, getUris, variables) {
|
|
2826
2893
|
// Check if the Web Crypto API is available.
|
|
2827
2894
|
if (!window.crypto || !window.crypto.subtle) {
|
|
2828
2895
|
shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
|
|
2829
|
-
'AES
|
|
2896
|
+
'AES. (Web Crypto only exists in secure origins like https)');
|
|
2830
2897
|
throw new shaka.util.Error(
|
|
2831
2898
|
shaka.util.Error.Severity.CRITICAL,
|
|
2832
2899
|
shaka.util.Error.Category.MANIFEST,
|
|
@@ -2857,36 +2924,63 @@ shaka.hls.HlsParser = class {
|
|
|
2857
2924
|
}
|
|
2858
2925
|
}
|
|
2859
2926
|
|
|
2860
|
-
const
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
2870
|
-
keyUris, this.config_.retryParameters);
|
|
2871
|
-
const keyResponse = await this.makeNetworkRequest_(request, requestType);
|
|
2927
|
+
const aesKeyInfoKey = `${drmTag.toString()}-${firstMediaSequenceNumber}`;
|
|
2928
|
+
if (!this.aesKeyInfoMap_.has(aesKeyInfoKey)) {
|
|
2929
|
+
// Default AES-128
|
|
2930
|
+
const keyInfo = {
|
|
2931
|
+
bitsKey: 128,
|
|
2932
|
+
blockCipherMode: 'CBC',
|
|
2933
|
+
iv,
|
|
2934
|
+
firstMediaSequenceNumber,
|
|
2935
|
+
};
|
|
2872
2936
|
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2937
|
+
const method = drmTag.getRequiredAttrValue('METHOD');
|
|
2938
|
+
switch (method) {
|
|
2939
|
+
case 'AES-256':
|
|
2940
|
+
keyInfo.bitsKey = 256;
|
|
2941
|
+
break;
|
|
2942
|
+
case 'AES-256-CTR':
|
|
2943
|
+
keyInfo.bitsKey = 256;
|
|
2944
|
+
keyInfo.blockCipherMode = 'CTR';
|
|
2945
|
+
break;
|
|
2879
2946
|
}
|
|
2880
2947
|
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2948
|
+
// Don't download the key object until the segment is parsed, to avoid a
|
|
2949
|
+
// startup delay for long manifests with lots of keys.
|
|
2950
|
+
keyInfo.fetchKey = async () => {
|
|
2951
|
+
const keyUris = shaka.hls.Utils.constructSegmentUris(
|
|
2952
|
+
getUris(), drmTag.getRequiredAttrValue('URI'), variables);
|
|
2953
|
+
|
|
2954
|
+
const keyMapKey = keyUris.sort().join('');
|
|
2955
|
+
if (!this.aesKeyMap_.has(keyMapKey)) {
|
|
2956
|
+
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
|
|
2957
|
+
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
2958
|
+
keyUris, this.config_.retryParameters);
|
|
2959
|
+
const keyResponse = this.makeNetworkRequest_(request, requestType);
|
|
2960
|
+
this.aesKeyMap_.set(keyMapKey, keyResponse);
|
|
2961
|
+
}
|
|
2962
|
+
const keyResponse = await this.aesKeyMap_.get(keyMapKey);
|
|
2963
|
+
|
|
2964
|
+
// keyResponse.status is undefined when URI is "data:text/plain;base64,"
|
|
2965
|
+
if (!keyResponse.data ||
|
|
2966
|
+
keyResponse.data.byteLength != (keyInfo.bitsKey / 8)) {
|
|
2967
|
+
throw new shaka.util.Error(
|
|
2968
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2969
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2970
|
+
shaka.util.Error.Code.AES_128_INVALID_KEY_LENGTH);
|
|
2971
|
+
}
|
|
2888
2972
|
|
|
2889
|
-
|
|
2973
|
+
const algorithm = {
|
|
2974
|
+
name: keyInfo.blockCipherMode == 'CTR' ? 'AES-CTR' : 'AES-CBC',
|
|
2975
|
+
length: keyInfo.bitsKey,
|
|
2976
|
+
};
|
|
2977
|
+
keyInfo.cryptoKey = await window.crypto.subtle.importKey(
|
|
2978
|
+
'raw', keyResponse.data, algorithm, true, ['decrypt']);
|
|
2979
|
+
keyInfo.fetchKey = undefined; // No longer needed.
|
|
2980
|
+
};
|
|
2981
|
+
this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo);
|
|
2982
|
+
}
|
|
2983
|
+
return this.aesKeyInfoMap_.get(aesKeyInfoKey);
|
|
2890
2984
|
}
|
|
2891
2985
|
|
|
2892
2986
|
|
|
@@ -3066,22 +3160,22 @@ shaka.hls.HlsParser = class {
|
|
|
3066
3160
|
mapTag.getAttributeValue('BYTERANGE', ''),
|
|
3067
3161
|
].join('-');
|
|
3068
3162
|
if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
|
|
3069
|
-
/** @type {shaka.extern.
|
|
3070
|
-
let
|
|
3163
|
+
/** @type {shaka.extern.aesKey|undefined} */
|
|
3164
|
+
let aesKey = undefined;
|
|
3071
3165
|
let byteRangeTag = null;
|
|
3072
3166
|
for (const tag of tags) {
|
|
3073
3167
|
if (tag.name == 'EXT-X-KEY') {
|
|
3074
|
-
if (tag.getRequiredAttrValue('METHOD')
|
|
3168
|
+
if (this.isAesMethod_(tag.getRequiredAttrValue('METHOD')) &&
|
|
3075
3169
|
tag.id < mapTag.id) {
|
|
3076
|
-
|
|
3077
|
-
this.
|
|
3170
|
+
aesKey =
|
|
3171
|
+
this.parseAESDrmTag_(tag, playlist, getUris, variables);
|
|
3078
3172
|
}
|
|
3079
3173
|
} else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) {
|
|
3080
3174
|
byteRangeTag = tag;
|
|
3081
3175
|
}
|
|
3082
3176
|
}
|
|
3083
3177
|
const initSegmentRef = this.createInitSegmentReference_(
|
|
3084
|
-
absoluteInitSegmentUris, mapTag, byteRangeTag,
|
|
3178
|
+
absoluteInitSegmentUris, mapTag, byteRangeTag, aesKey);
|
|
3085
3179
|
this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
|
|
3086
3180
|
}
|
|
3087
3181
|
return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
|
|
@@ -3093,12 +3187,12 @@ shaka.hls.HlsParser = class {
|
|
|
3093
3187
|
* @param {!Array.<string>} absoluteInitSegmentUris
|
|
3094
3188
|
* @param {!shaka.hls.Tag} mapTag EXT-X-MAP
|
|
3095
3189
|
* @param {shaka.hls.Tag=} byteRangeTag EXT-X-BYTERANGE
|
|
3096
|
-
* @param {shaka.extern.
|
|
3190
|
+
* @param {shaka.extern.aesKey=} aesKey
|
|
3097
3191
|
* @return {!shaka.media.InitSegmentReference}
|
|
3098
3192
|
* @private
|
|
3099
3193
|
*/
|
|
3100
3194
|
createInitSegmentReference_(absoluteInitSegmentUris, mapTag, byteRangeTag,
|
|
3101
|
-
|
|
3195
|
+
aesKey) {
|
|
3102
3196
|
let startByte = 0;
|
|
3103
3197
|
let endByte = null;
|
|
3104
3198
|
let byterange = mapTag.getAttributeValue('BYTERANGE');
|
|
@@ -3113,8 +3207,8 @@ shaka.hls.HlsParser = class {
|
|
|
3113
3207
|
startByte = Number(blocks[1]);
|
|
3114
3208
|
endByte = startByte + byteLength - 1;
|
|
3115
3209
|
|
|
3116
|
-
if (
|
|
3117
|
-
// MAP segment encrypted with method
|
|
3210
|
+
if (aesKey) {
|
|
3211
|
+
// MAP segment encrypted with method AES, when served with
|
|
3118
3212
|
// HTTP Range, has the unencrypted size specified in the range.
|
|
3119
3213
|
// See: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
|
3120
3214
|
const length = (endByte + 1) - startByte;
|
|
@@ -3131,7 +3225,7 @@ shaka.hls.HlsParser = class {
|
|
|
3131
3225
|
/* mediaQuality= */ null,
|
|
3132
3226
|
/* timescale= */ null,
|
|
3133
3227
|
/* segmentData= */ null,
|
|
3134
|
-
|
|
3228
|
+
aesKey);
|
|
3135
3229
|
return initSegmentRef;
|
|
3136
3230
|
}
|
|
3137
3231
|
|
|
@@ -3146,13 +3240,13 @@ shaka.hls.HlsParser = class {
|
|
|
3146
3240
|
* @param {!shaka.hls.Playlist} playlist
|
|
3147
3241
|
* @param {shaka.extern.Stream} stream
|
|
3148
3242
|
* @param {function():!Array.<string>} getUris
|
|
3149
|
-
* @param {shaka.extern.
|
|
3243
|
+
* @param {shaka.extern.aesKey=} aesKey
|
|
3150
3244
|
* @return {shaka.media.SegmentReference}
|
|
3151
3245
|
* @private
|
|
3152
3246
|
*/
|
|
3153
3247
|
createSegmentReference_(
|
|
3154
3248
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
3155
|
-
variables, playlist, stream, getUris,
|
|
3249
|
+
variables, playlist, stream, getUris, aesKey) {
|
|
3156
3250
|
const tags = hlsSegment.tags;
|
|
3157
3251
|
const extinfTag =
|
|
3158
3252
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXTINF');
|
|
@@ -3173,7 +3267,7 @@ shaka.hls.HlsParser = class {
|
|
|
3173
3267
|
const dateTimeTag =
|
|
3174
3268
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
|
|
3175
3269
|
if (dateTimeTag && dateTimeTag.value) {
|
|
3176
|
-
syncTime = shaka.util.
|
|
3270
|
+
syncTime = shaka.util.TXml.parseDate(dateTimeTag.value);
|
|
3177
3271
|
goog.asserts.assert(syncTime != null,
|
|
3178
3272
|
'EXT-X-PROGRAM-DATE-TIME format not valid');
|
|
3179
3273
|
}
|
|
@@ -3298,7 +3392,7 @@ shaka.hls.HlsParser = class {
|
|
|
3298
3392
|
/* tileDuration= */ null,
|
|
3299
3393
|
partialSyncTime,
|
|
3300
3394
|
partialStatus,
|
|
3301
|
-
|
|
3395
|
+
aesKey);
|
|
3302
3396
|
if (item.name == 'EXT-X-PRELOAD-HINT') {
|
|
3303
3397
|
partial.markAsPreload();
|
|
3304
3398
|
isPreloadSegment = true;
|
|
@@ -3413,7 +3507,7 @@ shaka.hls.HlsParser = class {
|
|
|
3413
3507
|
tileDuration,
|
|
3414
3508
|
syncTime,
|
|
3415
3509
|
status,
|
|
3416
|
-
|
|
3510
|
+
aesKey,
|
|
3417
3511
|
allPartialSegments,
|
|
3418
3512
|
);
|
|
3419
3513
|
|
|
@@ -3467,12 +3561,13 @@ shaka.hls.HlsParser = class {
|
|
|
3467
3561
|
* @param {!Map.<number, number>} mediaSequenceToStartTime
|
|
3468
3562
|
* @param {!Map.<string, string>} variables
|
|
3469
3563
|
* @param {function():!Array.<string>} getUris
|
|
3564
|
+
* @param {string} type
|
|
3470
3565
|
* @return {{segments: !Array.<!shaka.media.SegmentReference>,
|
|
3471
3566
|
* bandwidth: (number|undefined)}}
|
|
3472
3567
|
* @private
|
|
3473
3568
|
*/
|
|
3474
3569
|
createSegments_(playlist, stream, mediaSequenceToStartTime, variables,
|
|
3475
|
-
getUris) {
|
|
3570
|
+
getUris, type) {
|
|
3476
3571
|
/** @type {Array.<!shaka.hls.Segment>} */
|
|
3477
3572
|
const hlsSegments = playlist.segments;
|
|
3478
3573
|
goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
|
|
@@ -3480,8 +3575,8 @@ shaka.hls.HlsParser = class {
|
|
|
3480
3575
|
/** @type {shaka.media.InitSegmentReference} */
|
|
3481
3576
|
let initSegmentRef;
|
|
3482
3577
|
|
|
3483
|
-
/** @type {shaka.extern.
|
|
3484
|
-
let
|
|
3578
|
+
/** @type {shaka.extern.aesKey|undefined} */
|
|
3579
|
+
let aesKey = undefined;
|
|
3485
3580
|
|
|
3486
3581
|
let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
3487
3582
|
playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
|
|
@@ -3527,14 +3622,14 @@ shaka.hls.HlsParser = class {
|
|
|
3527
3622
|
discontinuitySequence++;
|
|
3528
3623
|
}
|
|
3529
3624
|
|
|
3530
|
-
// Apply new AES
|
|
3625
|
+
// Apply new AES tags as you see them, keeping a running total.
|
|
3531
3626
|
for (const drmTag of item.tags) {
|
|
3532
3627
|
if (drmTag.name == 'EXT-X-KEY') {
|
|
3533
|
-
if (drmTag.getRequiredAttrValue('METHOD')
|
|
3534
|
-
|
|
3535
|
-
this.
|
|
3628
|
+
if (this.isAesMethod_(drmTag.getRequiredAttrValue('METHOD'))) {
|
|
3629
|
+
aesKey =
|
|
3630
|
+
this.parseAESDrmTag_(drmTag, playlist, getUris, variables);
|
|
3536
3631
|
} else {
|
|
3537
|
-
|
|
3632
|
+
aesKey = undefined;
|
|
3538
3633
|
}
|
|
3539
3634
|
}
|
|
3540
3635
|
}
|
|
@@ -3565,7 +3660,7 @@ shaka.hls.HlsParser = class {
|
|
|
3565
3660
|
playlist,
|
|
3566
3661
|
stream,
|
|
3567
3662
|
getUris,
|
|
3568
|
-
|
|
3663
|
+
aesKey);
|
|
3569
3664
|
|
|
3570
3665
|
if (reference) {
|
|
3571
3666
|
const bitrate = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
@@ -3586,7 +3681,7 @@ shaka.hls.HlsParser = class {
|
|
|
3586
3681
|
previousReference = reference;
|
|
3587
3682
|
reference.discontinuitySequence = discontinuitySequence;
|
|
3588
3683
|
|
|
3589
|
-
if (this.
|
|
3684
|
+
if (this.ignoreManifestProgramDateTimeFor_(type) &&
|
|
3590
3685
|
this.minSequenceNumber_ != null &&
|
|
3591
3686
|
position < this.minSequenceNumber_) {
|
|
3592
3687
|
// This segment is ignored as part of our fallback synchronization
|
|
@@ -3687,8 +3782,10 @@ shaka.hls.HlsParser = class {
|
|
|
3687
3782
|
// skip this step, and wait until we have sync time across stream types.
|
|
3688
3783
|
const lowestSyncTime = this.lowestSyncTime_;
|
|
3689
3784
|
if (someSyncTime && lowestSyncTime != Infinity) {
|
|
3690
|
-
|
|
3691
|
-
reference
|
|
3785
|
+
if (!this.ignoreManifestProgramDateTimeFor_(type)) {
|
|
3786
|
+
for (const reference of references) {
|
|
3787
|
+
reference.syncAgainst(lowestSyncTime);
|
|
3788
|
+
}
|
|
3692
3789
|
}
|
|
3693
3790
|
}
|
|
3694
3791
|
|
|
@@ -4037,6 +4134,17 @@ shaka.hls.HlsParser = class {
|
|
|
4037
4134
|
return op.promise;
|
|
4038
4135
|
}
|
|
4039
4136
|
|
|
4137
|
+
/**
|
|
4138
|
+
* @param {string} method
|
|
4139
|
+
* @return {boolean}
|
|
4140
|
+
* @private
|
|
4141
|
+
*/
|
|
4142
|
+
isAesMethod_(method) {
|
|
4143
|
+
return method == 'AES-128' ||
|
|
4144
|
+
method == 'AES-256' ||
|
|
4145
|
+
method == 'AES-256-CTR';
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4040
4148
|
/**
|
|
4041
4149
|
* @param {!shaka.hls.Tag} drmTag
|
|
4042
4150
|
* @param {string} mimeType
|
|
@@ -4059,13 +4167,28 @@ shaka.hls.HlsParser = class {
|
|
|
4059
4167
|
.HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
|
|
4060
4168
|
}
|
|
4061
4169
|
|
|
4170
|
+
const method = drmTag.getRequiredAttrValue('METHOD');
|
|
4171
|
+
const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR'];
|
|
4172
|
+
if (!VALID_METHODS.includes(method)) {
|
|
4173
|
+
shaka.log.error('FairPlay in HLS is only supported with [',
|
|
4174
|
+
VALID_METHODS.join(', '), '], not', method);
|
|
4175
|
+
return null;
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
let encryptionScheme = 'cenc';
|
|
4179
|
+
if (method == 'SAMPLE-AES') {
|
|
4180
|
+
// It should be 'cbcs-1-9' but Safari doesn't support it.
|
|
4181
|
+
// See: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/Modules/encryptedmedia/MediaKeyEncryptionScheme.idl
|
|
4182
|
+
encryptionScheme = 'cbcs';
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4062
4185
|
/*
|
|
4063
4186
|
* Even if we're not able to construct initData through the HLS tag, adding
|
|
4064
4187
|
* a DRMInfo will allow DRM Engine to request a media key system access
|
|
4065
4188
|
* with the correct keySystem and initDataType
|
|
4066
4189
|
*/
|
|
4067
4190
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
4068
|
-
'com.apple.fps', [
|
|
4191
|
+
'com.apple.fps', encryptionScheme, [
|
|
4069
4192
|
{initDataType: 'sinf', initData: new Uint8Array(0), keyId: null},
|
|
4070
4193
|
]);
|
|
4071
4194
|
|
|
@@ -4086,13 +4209,18 @@ shaka.hls.HlsParser = class {
|
|
|
4086
4209
|
return null;
|
|
4087
4210
|
}
|
|
4088
4211
|
|
|
4212
|
+
let encryptionScheme = 'cenc';
|
|
4213
|
+
if (method == 'SAMPLE-AES') {
|
|
4214
|
+
encryptionScheme = 'cbcs';
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4089
4217
|
const uri = drmTag.getRequiredAttrValue('URI');
|
|
4090
4218
|
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);
|
|
4091
4219
|
|
|
4092
4220
|
// The data encoded in the URI is a PSSH box to be used as init data.
|
|
4093
4221
|
const pssh = shaka.util.BufferUtils.toUint8(parsedData.data);
|
|
4094
4222
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
4095
|
-
'com.widevine.alpha', [
|
|
4223
|
+
'com.widevine.alpha', encryptionScheme, [
|
|
4096
4224
|
{initDataType: 'cenc', initData: pssh},
|
|
4097
4225
|
]);
|
|
4098
4226
|
|
|
@@ -4124,6 +4252,11 @@ shaka.hls.HlsParser = class {
|
|
|
4124
4252
|
return null;
|
|
4125
4253
|
}
|
|
4126
4254
|
|
|
4255
|
+
let encryptionScheme = 'cenc';
|
|
4256
|
+
if (method == 'SAMPLE-AES') {
|
|
4257
|
+
encryptionScheme = 'cbcs';
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4127
4260
|
const uri = drmTag.getRequiredAttrValue('URI');
|
|
4128
4261
|
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);
|
|
4129
4262
|
|
|
@@ -4139,7 +4272,7 @@ shaka.hls.HlsParser = class {
|
|
|
4139
4272
|
const pssh =
|
|
4140
4273
|
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
|
|
4141
4274
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
4142
|
-
'com.microsoft.playready', [
|
|
4275
|
+
'com.microsoft.playready', encryptionScheme, [
|
|
4143
4276
|
{initDataType: 'cenc', initData: pssh},
|
|
4144
4277
|
]);
|
|
4145
4278
|
|
|
@@ -4150,10 +4283,15 @@ shaka.hls.HlsParser = class {
|
|
|
4150
4283
|
* See: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-5.1
|
|
4151
4284
|
*
|
|
4152
4285
|
* @param {!shaka.hls.Tag} drmTag
|
|
4153
|
-
* @
|
|
4286
|
+
* @param {string} mimeType
|
|
4287
|
+
* @param {function():!Array.<string>} getUris
|
|
4288
|
+
* @param {?shaka.media.InitSegmentReference} initSegmentRef
|
|
4289
|
+
* @param {?Map.<string, string>=} variables
|
|
4290
|
+
* @return {!Promise.<?shaka.extern.DrmInfo>}
|
|
4154
4291
|
* @private
|
|
4155
4292
|
*/
|
|
4156
|
-
|
|
4293
|
+
async identityDrmParser_(drmTag, mimeType, getUris, initSegmentRef,
|
|
4294
|
+
variables) {
|
|
4157
4295
|
if (mimeType == 'video/mp2t') {
|
|
4158
4296
|
throw new shaka.util.Error(
|
|
4159
4297
|
shaka.util.Error.Severity.CRITICAL,
|
|
@@ -4177,15 +4315,73 @@ shaka.hls.HlsParser = class {
|
|
|
4177
4315
|
return null;
|
|
4178
4316
|
}
|
|
4179
4317
|
|
|
4318
|
+
const keyUris = shaka.hls.Utils.constructSegmentUris(
|
|
4319
|
+
getUris(), drmTag.getRequiredAttrValue('URI'), variables);
|
|
4320
|
+
|
|
4321
|
+
let key;
|
|
4322
|
+
if (keyUris[0].startsWith('data:text/plain;base64,')) {
|
|
4323
|
+
key = shaka.util.Uint8ArrayUtils.toHex(
|
|
4324
|
+
shaka.util.Uint8ArrayUtils.fromBase64(
|
|
4325
|
+
keyUris[0].split('data:text/plain;base64,').pop()));
|
|
4326
|
+
} else {
|
|
4327
|
+
const keyMapKey = keyUris.sort().join('');
|
|
4328
|
+
if (!this.identityKeyMap_.has(keyMapKey)) {
|
|
4329
|
+
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
|
|
4330
|
+
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
4331
|
+
keyUris, this.config_.retryParameters);
|
|
4332
|
+
const keyResponse = this.makeNetworkRequest_(request, requestType);
|
|
4333
|
+
this.identityKeyMap_.set(keyMapKey, keyResponse);
|
|
4334
|
+
}
|
|
4335
|
+
const keyResponse = await this.identityKeyMap_.get(keyMapKey);
|
|
4336
|
+
key = shaka.util.Uint8ArrayUtils.toHex(keyResponse.data);
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4180
4339
|
// NOTE: The ClearKey CDM requires a key-id to key mapping. HLS doesn't
|
|
4181
4340
|
// provide a key ID anywhere. So although we could use the 'URI' attribute
|
|
4182
4341
|
// to fetch the actual 16-byte key, without a key ID, we can't provide this
|
|
4183
|
-
// automatically to the ClearKey CDM.
|
|
4184
|
-
//
|
|
4342
|
+
// automatically to the ClearKey CDM. By default we assume that keyId is 0,
|
|
4343
|
+
// but we will try to get key ID from Init Segment.
|
|
4344
|
+
// If the application want override this behavior will have to use
|
|
4345
|
+
// player.configure('drm.clearKeys', { ... }) to provide the key IDs
|
|
4185
4346
|
// and keys or player.configure('drm.servers.org\.w3\.clearkey', ...) to
|
|
4186
4347
|
// provide a ClearKey license server URI.
|
|
4187
|
-
|
|
4188
|
-
|
|
4348
|
+
let keyId = '00000000000000000000000000000000';
|
|
4349
|
+
|
|
4350
|
+
if (initSegmentRef) {
|
|
4351
|
+
let defaultKID;
|
|
4352
|
+
if (this.identityKidMap_.has(initSegmentRef)) {
|
|
4353
|
+
defaultKID = this.identityKidMap_.get(initSegmentRef);
|
|
4354
|
+
} else {
|
|
4355
|
+
const initSegmentRequest = shaka.util.Networking.createSegmentRequest(
|
|
4356
|
+
initSegmentRef.getUris(),
|
|
4357
|
+
initSegmentRef.getStartByte(),
|
|
4358
|
+
initSegmentRef.getEndByte(),
|
|
4359
|
+
this.config_.retryParameters);
|
|
4360
|
+
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
4361
|
+
const initType =
|
|
4362
|
+
shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT;
|
|
4363
|
+
const initResponse = await this.makeNetworkRequest_(
|
|
4364
|
+
initSegmentRequest, requestType, {type: initType});
|
|
4365
|
+
|
|
4366
|
+
defaultKID = shaka.media.SegmentUtils.getDefaultKID(
|
|
4367
|
+
initResponse.data);
|
|
4368
|
+
this.identityKidMap_.set(initSegmentRef, defaultKID);
|
|
4369
|
+
}
|
|
4370
|
+
if (defaultKID) {
|
|
4371
|
+
keyId = defaultKID;
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
const clearkeys = new Map();
|
|
4376
|
+
clearkeys.set(keyId, key);
|
|
4377
|
+
|
|
4378
|
+
let encryptionScheme = 'cenc';
|
|
4379
|
+
if (method == 'SAMPLE-AES') {
|
|
4380
|
+
encryptionScheme = 'cbcs';
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
return shaka.util.ManifestParserUtils.createDrmInfoFromClearKeys(
|
|
4384
|
+
clearkeys, encryptionScheme);
|
|
4189
4385
|
}
|
|
4190
4386
|
};
|
|
4191
4387
|
|
|
@@ -4366,8 +4562,6 @@ shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
|
|
|
4366
4562
|
shaka.hls.HlsParser.widevineDrmParser_,
|
|
4367
4563
|
'com.microsoft.playready':
|
|
4368
4564
|
shaka.hls.HlsParser.playreadyDrmParser_,
|
|
4369
|
-
'identity':
|
|
4370
|
-
shaka.hls.HlsParser.identityDrmParser_,
|
|
4371
4565
|
};
|
|
4372
4566
|
|
|
4373
4567
|
|