shaka-player 4.7.12 → 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 +36 -25
- package/dist/locales.js +40 -30
- package/dist/shaka-player.compiled.d.ts +196 -46
- package/dist/shaka-player.compiled.debug.d.ts +196 -46
- package/dist/shaka-player.compiled.debug.externs.js +474 -337
- package/dist/shaka-player.compiled.debug.js +992 -871
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +474 -337
- package/dist/shaka-player.compiled.js +1248 -1174
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +259 -49
- package/dist/shaka-player.ui.debug.d.ts +259 -49
- package/dist/shaka-player.ui.debug.externs.js +591 -376
- package/dist/shaka-player.ui.debug.js +1208 -1003
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +591 -376
- package/dist/shaka-player.ui.js +1533 -1408
- 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 +69 -7
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/offline.js +7 -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 +240 -179
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +35 -22
- package/lib/dash/segment_list.js +39 -31
- package/lib/dash/segment_template.js +47 -23
- package/lib/hls/hls_parser.js +376 -165
- 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 +108 -29
- package/lib/media/playhead.js +3 -3
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +60 -49
- 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 +321 -77
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +78 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
- package/lib/offline/manifest_converter.js +10 -3
- package/lib/offline/storage.js +5 -3
- package/lib/player.js +896 -674
- 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/mime_utils.js +10 -5
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +24 -37
- 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 +350 -277
- 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.
|
|
@@ -1224,6 +1261,7 @@ shaka.hls.HlsParser = class {
|
|
|
1224
1261
|
textStreamInfo.stream.mimeType =
|
|
1225
1262
|
this.guessMimeTypeBeforeLoading_(type, codecs) ||
|
|
1226
1263
|
this.guessMimeTypeFallback_(type);
|
|
1264
|
+
this.setFullTypeForStream_(textStreamInfo.stream);
|
|
1227
1265
|
}
|
|
1228
1266
|
}
|
|
1229
1267
|
}
|
|
@@ -1233,6 +1271,15 @@ shaka.hls.HlsParser = class {
|
|
|
1233
1271
|
return textStreams.filter((s) => s);
|
|
1234
1272
|
}
|
|
1235
1273
|
|
|
1274
|
+
/**
|
|
1275
|
+
* @param {!shaka.extern.Stream} stream
|
|
1276
|
+
* @private
|
|
1277
|
+
*/
|
|
1278
|
+
setFullTypeForStream_(stream) {
|
|
1279
|
+
stream.fullMimeTypes = new Set([shaka.util.MimeUtils.getFullType(
|
|
1280
|
+
stream.mimeType, stream.codecs)]);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1236
1283
|
/**
|
|
1237
1284
|
* @param {!Array.<!shaka.hls.Tag>} imageTags from the playlist.
|
|
1238
1285
|
* @param {!Array.<!shaka.hls.Tag>} iFrameTags from the playlist.
|
|
@@ -1313,37 +1360,48 @@ shaka.hls.HlsParser = class {
|
|
|
1313
1360
|
* from the playlist.
|
|
1314
1361
|
* @param {!Array.<!shaka.hls.Tag>} mediaTags EXT-X-MEDIA tags from the
|
|
1315
1362
|
* playlist.
|
|
1316
|
-
* @
|
|
1363
|
+
* @param {function():!Array.<string>} getUris
|
|
1364
|
+
* @param {?Map.<string, string>=} variables
|
|
1365
|
+
* @return {!Promise.<!Array.<!shaka.extern.Variant>>}
|
|
1317
1366
|
* @private
|
|
1318
1367
|
*/
|
|
1319
|
-
createVariantsForTags_(tags, sessionKeyTags, mediaTags
|
|
1368
|
+
async createVariantsForTags_(tags, sessionKeyTags, mediaTags, getUris,
|
|
1369
|
+
variables) {
|
|
1320
1370
|
// EXT-X-SESSION-KEY processing
|
|
1321
1371
|
const drmInfos = [];
|
|
1322
1372
|
const keyIds = new Set();
|
|
1323
1373
|
if (sessionKeyTags.length > 0) {
|
|
1324
1374
|
for (const drmTag of sessionKeyTags) {
|
|
1325
1375
|
const method = drmTag.getRequiredAttrValue('METHOD');
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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 {
|
|
1332
1390
|
const drmParser =
|
|
1333
1391
|
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
|
1334
1392
|
|
|
1335
|
-
|
|
1393
|
+
drmInfo = drmParser ?
|
|
1336
1394
|
drmParser(drmTag, /* mimeType= */ '') : null;
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1395
|
+
}
|
|
1396
|
+
if (drmInfo) {
|
|
1397
|
+
if (drmInfo.keyIds) {
|
|
1398
|
+
for (const keyId of drmInfo.keyIds) {
|
|
1399
|
+
keyIds.add(keyId);
|
|
1342
1400
|
}
|
|
1343
|
-
drmInfos.push(drmInfo);
|
|
1344
|
-
} else {
|
|
1345
|
-
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1346
1401
|
}
|
|
1402
|
+
drmInfos.push(drmInfo);
|
|
1403
|
+
} else {
|
|
1404
|
+
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1347
1405
|
}
|
|
1348
1406
|
}
|
|
1349
1407
|
}
|
|
@@ -1577,6 +1635,9 @@ shaka.hls.HlsParser = class {
|
|
|
1577
1635
|
getCodecsForVariantTag_(tag) {
|
|
1578
1636
|
let codecsString = tag.getAttributeValue('CODECS') || '';
|
|
1579
1637
|
|
|
1638
|
+
const supplementalCodecsString =
|
|
1639
|
+
tag.getAttributeValue('SUPPLEMENTAL-CODECS');
|
|
1640
|
+
|
|
1580
1641
|
this.codecInfoInManifest_ = codecsString.length > 0;
|
|
1581
1642
|
|
|
1582
1643
|
if (!this.codecInfoInManifest_ && !this.config_.hls.disableCodecGuessing) {
|
|
@@ -1597,34 +1658,15 @@ shaka.hls.HlsParser = class {
|
|
|
1597
1658
|
/** @type {!Array.<string>} */
|
|
1598
1659
|
const codecs = codecsString.split(/\s*,\s*/);
|
|
1599
1660
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
* @return {!Array.<string>} codecs
|
|
1607
|
-
* @private
|
|
1608
|
-
*/
|
|
1609
|
-
filterDuplicateCodecs_(codecs) {
|
|
1610
|
-
// Filter out duplicate codecs.
|
|
1611
|
-
const seen = new Set();
|
|
1612
|
-
const ret = [];
|
|
1613
|
-
for (const codec of codecs) {
|
|
1614
|
-
// HLS says the CODECS field needs to include all codecs that appear in
|
|
1615
|
-
// the content. This means that if the content changes profiles, it should
|
|
1616
|
-
// include both. Since all known browsers support changing profiles
|
|
1617
|
-
// without any other work, just ignore them. See also:
|
|
1618
|
-
// https://github.com/shaka-project/shaka-player/issues/1817
|
|
1619
|
-
const shortCodec = shaka.util.MimeUtils.getCodecBase(codec);
|
|
1620
|
-
if (!seen.has(shortCodec)) {
|
|
1621
|
-
ret.push(codec);
|
|
1622
|
-
seen.add(shortCodec);
|
|
1623
|
-
} else {
|
|
1624
|
-
shaka.log.debug('Ignoring duplicate codec');
|
|
1625
|
-
}
|
|
1661
|
+
if (supplementalCodecsString) {
|
|
1662
|
+
const supplementalCodecs = supplementalCodecsString.split(/\s*,\s*/)
|
|
1663
|
+
.map((codec) => {
|
|
1664
|
+
return codec.split('/')[0];
|
|
1665
|
+
});
|
|
1666
|
+
codecs.push(...supplementalCodecs);
|
|
1626
1667
|
}
|
|
1627
|
-
|
|
1668
|
+
|
|
1669
|
+
return shaka.media.SegmentUtils.codecsFiltering(codecs);
|
|
1628
1670
|
}
|
|
1629
1671
|
|
|
1630
1672
|
/**
|
|
@@ -2266,6 +2308,7 @@ shaka.hls.HlsParser = class {
|
|
|
2266
2308
|
stream.mimeType = realStream.mimeType;
|
|
2267
2309
|
stream.bandwidth = stream.bandwidth || realStream.bandwidth;
|
|
2268
2310
|
stream.codecs = stream.codecs || realStream.codecs;
|
|
2311
|
+
this.setFullTypeForStream_(stream);
|
|
2269
2312
|
|
|
2270
2313
|
// Since we lazy-loaded this content, the player may need to create new
|
|
2271
2314
|
// sessions for the DRM info in this stream.
|
|
@@ -2280,6 +2323,7 @@ shaka.hls.HlsParser = class {
|
|
|
2280
2323
|
// To aid manifest filtering, assume before loading that all video
|
|
2281
2324
|
// renditions have the same MIME type. (And likewise for audio.)
|
|
2282
2325
|
otherStreamInfo.stream.mimeType = realStream.mimeType;
|
|
2326
|
+
this.setFullTypeForStream_(otherStreamInfo.stream);
|
|
2283
2327
|
}
|
|
2284
2328
|
}
|
|
2285
2329
|
}
|
|
@@ -2288,6 +2332,7 @@ shaka.hls.HlsParser = class {
|
|
|
2288
2332
|
const firstSegment = realStream.segmentIndex.get(0);
|
|
2289
2333
|
if (firstSegment && firstSegment.initSegmentReference) {
|
|
2290
2334
|
stream.mimeType = 'application/mp4';
|
|
2335
|
+
this.setFullTypeForStream_(stream);
|
|
2291
2336
|
}
|
|
2292
2337
|
}
|
|
2293
2338
|
|
|
@@ -2431,9 +2476,25 @@ shaka.hls.HlsParser = class {
|
|
|
2431
2476
|
this.syncStreamsWithSequenceNumber_(streamInfos);
|
|
2432
2477
|
} else {
|
|
2433
2478
|
this.syncStreamsWithProgramDateTime_(streamInfos);
|
|
2479
|
+
if (this.config_.hls.ignoreManifestProgramDateTimeForTypes.length > 0) {
|
|
2480
|
+
this.syncStreamsWithSequenceNumber_(streamInfos);
|
|
2481
|
+
}
|
|
2434
2482
|
}
|
|
2435
2483
|
}
|
|
2436
2484
|
|
|
2485
|
+
/**
|
|
2486
|
+
* @param {string} type
|
|
2487
|
+
* @return {boolean}
|
|
2488
|
+
* @private
|
|
2489
|
+
*/
|
|
2490
|
+
ignoreManifestProgramDateTimeFor_(type) {
|
|
2491
|
+
if (this.config_.hls.ignoreManifestProgramDateTime) {
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
const forTypes = this.config_.hls.ignoreManifestProgramDateTimeForTypes;
|
|
2495
|
+
return forTypes.includes(type);
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2437
2498
|
/**
|
|
2438
2499
|
* There are some values on streams that can only be set once we know about
|
|
2439
2500
|
* both the video and audio content, if present.
|
|
@@ -2521,7 +2582,7 @@ shaka.hls.HlsParser = class {
|
|
|
2521
2582
|
}
|
|
2522
2583
|
|
|
2523
2584
|
const {drmInfos, keyIds, encrypted, aesEncrypted} =
|
|
2524
|
-
|
|
2585
|
+
await this.parseDrmInfo_(playlist, mimeType, getUris, mediaVariables);
|
|
2525
2586
|
|
|
2526
2587
|
if (encrypted && !drmInfos.length && !aesEncrypted) {
|
|
2527
2588
|
throw new shaka.util.Error(
|
|
@@ -2537,12 +2598,14 @@ shaka.hls.HlsParser = class {
|
|
|
2537
2598
|
stream.drmInfos = drmInfos;
|
|
2538
2599
|
stream.keyIds = keyIds;
|
|
2539
2600
|
stream.mimeType = mimeType;
|
|
2601
|
+
this.setFullTypeForStream_(stream);
|
|
2540
2602
|
|
|
2541
2603
|
const mediaSequenceToStartTime = this.isLive_() ?
|
|
2542
2604
|
this.mediaSequenceToStartTimeByType_.get(type) : new Map();
|
|
2543
2605
|
|
|
2544
2606
|
const {segments, bandwidth} = this.createSegments_(
|
|
2545
|
-
playlist, stream, mediaSequenceToStartTime, mediaVariables, getUris
|
|
2607
|
+
playlist, stream, mediaSequenceToStartTime, mediaVariables, getUris,
|
|
2608
|
+
type);
|
|
2546
2609
|
if (bandwidth) {
|
|
2547
2610
|
stream.bandwidth = bandwidth;
|
|
2548
2611
|
}
|
|
@@ -2692,7 +2755,7 @@ shaka.hls.HlsParser = class {
|
|
|
2692
2755
|
roles.push(shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE);
|
|
2693
2756
|
}
|
|
2694
2757
|
|
|
2695
|
-
|
|
2758
|
+
const stream = {
|
|
2696
2759
|
id: this.globalId_++,
|
|
2697
2760
|
originalId: name,
|
|
2698
2761
|
groupId: null,
|
|
@@ -2730,28 +2793,40 @@ shaka.hls.HlsParser = class {
|
|
|
2730
2793
|
accessibilityPurpose: accessibilityPurpose,
|
|
2731
2794
|
external: false,
|
|
2732
2795
|
fastSwitching: false,
|
|
2796
|
+
fullMimeTypes: new Set(),
|
|
2733
2797
|
};
|
|
2798
|
+
this.setFullTypeForStream_(stream);
|
|
2799
|
+
return stream;
|
|
2734
2800
|
}
|
|
2735
2801
|
|
|
2736
2802
|
/**
|
|
2737
2803
|
* @param {!shaka.hls.Playlist} playlist
|
|
2738
2804
|
* @param {string} mimeType
|
|
2739
|
-
* @
|
|
2805
|
+
* @param {function():!Array.<string>} getUris
|
|
2806
|
+
* @param {?Map.<string, string>=} variables
|
|
2807
|
+
* @return {Promise.<{
|
|
2740
2808
|
* drmInfos: !Array.<shaka.extern.DrmInfo>,
|
|
2741
2809
|
* keyIds: !Set.<string>,
|
|
2742
2810
|
* encrypted: boolean,
|
|
2743
2811
|
* aesEncrypted: boolean
|
|
2744
|
-
* }}
|
|
2812
|
+
* }>}
|
|
2745
2813
|
* @private
|
|
2746
2814
|
*/
|
|
2747
|
-
parseDrmInfo_(playlist, mimeType) {
|
|
2748
|
-
/** @type {!
|
|
2749
|
-
const
|
|
2815
|
+
async parseDrmInfo_(playlist, mimeType, getUris, variables) {
|
|
2816
|
+
/** @type {!Map<!shaka.hls.Tag, ?shaka.media.InitSegmentReference>} */
|
|
2817
|
+
const drmTagsMap = new Map();
|
|
2750
2818
|
if (playlist.segments) {
|
|
2751
2819
|
for (const segment of playlist.segments) {
|
|
2752
2820
|
const segmentKeyTags = shaka.hls.Utils.filterTagsByName(segment.tags,
|
|
2753
2821
|
'EXT-X-KEY');
|
|
2754
|
-
|
|
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
|
+
}
|
|
2755
2830
|
}
|
|
2756
2831
|
}
|
|
2757
2832
|
|
|
@@ -2762,34 +2837,43 @@ shaka.hls.HlsParser = class {
|
|
|
2762
2837
|
const drmInfos = [];
|
|
2763
2838
|
const keyIds = new Set();
|
|
2764
2839
|
|
|
2765
|
-
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);
|
|
2766
2844
|
const method = drmTag.getRequiredAttrValue('METHOD');
|
|
2767
2845
|
if (method != 'NONE') {
|
|
2768
2846
|
encrypted = true;
|
|
2769
2847
|
|
|
2770
|
-
|
|
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)) {
|
|
2771
2856
|
// These keys are handled separately.
|
|
2772
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);
|
|
2773
2863
|
} else {
|
|
2774
|
-
// According to the HLS spec, KEYFORMAT is optional and implicitly
|
|
2775
|
-
// defaults to "identity".
|
|
2776
|
-
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
|
|
2777
|
-
const keyFormat =
|
|
2778
|
-
drmTag.getAttributeValue('KEYFORMAT') || 'identity';
|
|
2779
2864
|
const drmParser =
|
|
2780
2865
|
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
}
|
|
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);
|
|
2788
2872
|
}
|
|
2789
|
-
drmInfos.push(drmInfo);
|
|
2790
|
-
} else {
|
|
2791
|
-
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
2792
2873
|
}
|
|
2874
|
+
drmInfos.push(drmInfo);
|
|
2875
|
+
} else {
|
|
2876
|
+
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
2793
2877
|
}
|
|
2794
2878
|
}
|
|
2795
2879
|
}
|
|
@@ -2802,14 +2886,14 @@ shaka.hls.HlsParser = class {
|
|
|
2802
2886
|
* @param {!shaka.hls.Playlist} playlist
|
|
2803
2887
|
* @param {function():!Array.<string>} getUris
|
|
2804
2888
|
* @param {?Map.<string, string>=} variables
|
|
2805
|
-
* @return {!shaka.extern.
|
|
2889
|
+
* @return {!shaka.extern.aesKey}
|
|
2806
2890
|
* @private
|
|
2807
2891
|
*/
|
|
2808
|
-
|
|
2892
|
+
parseAESDrmTag_(drmTag, playlist, getUris, variables) {
|
|
2809
2893
|
// Check if the Web Crypto API is available.
|
|
2810
2894
|
if (!window.crypto || !window.crypto.subtle) {
|
|
2811
2895
|
shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
|
|
2812
|
-
'AES
|
|
2896
|
+
'AES. (Web Crypto only exists in secure origins like https)');
|
|
2813
2897
|
throw new shaka.util.Error(
|
|
2814
2898
|
shaka.util.Error.Severity.CRITICAL,
|
|
2815
2899
|
shaka.util.Error.Category.MANIFEST,
|
|
@@ -2840,36 +2924,63 @@ shaka.hls.HlsParser = class {
|
|
|
2840
2924
|
}
|
|
2841
2925
|
}
|
|
2842
2926
|
|
|
2843
|
-
const
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
2853
|
-
keyUris, this.config_.retryParameters);
|
|
2854
|
-
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
|
+
};
|
|
2855
2936
|
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
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;
|
|
2862
2946
|
}
|
|
2863
2947
|
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
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
|
+
}
|
|
2871
2972
|
|
|
2872
|
-
|
|
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);
|
|
2873
2984
|
}
|
|
2874
2985
|
|
|
2875
2986
|
|
|
@@ -3049,22 +3160,22 @@ shaka.hls.HlsParser = class {
|
|
|
3049
3160
|
mapTag.getAttributeValue('BYTERANGE', ''),
|
|
3050
3161
|
].join('-');
|
|
3051
3162
|
if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
|
|
3052
|
-
/** @type {shaka.extern.
|
|
3053
|
-
let
|
|
3163
|
+
/** @type {shaka.extern.aesKey|undefined} */
|
|
3164
|
+
let aesKey = undefined;
|
|
3054
3165
|
let byteRangeTag = null;
|
|
3055
3166
|
for (const tag of tags) {
|
|
3056
3167
|
if (tag.name == 'EXT-X-KEY') {
|
|
3057
|
-
if (tag.getRequiredAttrValue('METHOD')
|
|
3168
|
+
if (this.isAesMethod_(tag.getRequiredAttrValue('METHOD')) &&
|
|
3058
3169
|
tag.id < mapTag.id) {
|
|
3059
|
-
|
|
3060
|
-
this.
|
|
3170
|
+
aesKey =
|
|
3171
|
+
this.parseAESDrmTag_(tag, playlist, getUris, variables);
|
|
3061
3172
|
}
|
|
3062
3173
|
} else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) {
|
|
3063
3174
|
byteRangeTag = tag;
|
|
3064
3175
|
}
|
|
3065
3176
|
}
|
|
3066
3177
|
const initSegmentRef = this.createInitSegmentReference_(
|
|
3067
|
-
absoluteInitSegmentUris, mapTag, byteRangeTag,
|
|
3178
|
+
absoluteInitSegmentUris, mapTag, byteRangeTag, aesKey);
|
|
3068
3179
|
this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
|
|
3069
3180
|
}
|
|
3070
3181
|
return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
|
|
@@ -3076,12 +3187,12 @@ shaka.hls.HlsParser = class {
|
|
|
3076
3187
|
* @param {!Array.<string>} absoluteInitSegmentUris
|
|
3077
3188
|
* @param {!shaka.hls.Tag} mapTag EXT-X-MAP
|
|
3078
3189
|
* @param {shaka.hls.Tag=} byteRangeTag EXT-X-BYTERANGE
|
|
3079
|
-
* @param {shaka.extern.
|
|
3190
|
+
* @param {shaka.extern.aesKey=} aesKey
|
|
3080
3191
|
* @return {!shaka.media.InitSegmentReference}
|
|
3081
3192
|
* @private
|
|
3082
3193
|
*/
|
|
3083
3194
|
createInitSegmentReference_(absoluteInitSegmentUris, mapTag, byteRangeTag,
|
|
3084
|
-
|
|
3195
|
+
aesKey) {
|
|
3085
3196
|
let startByte = 0;
|
|
3086
3197
|
let endByte = null;
|
|
3087
3198
|
let byterange = mapTag.getAttributeValue('BYTERANGE');
|
|
@@ -3096,8 +3207,8 @@ shaka.hls.HlsParser = class {
|
|
|
3096
3207
|
startByte = Number(blocks[1]);
|
|
3097
3208
|
endByte = startByte + byteLength - 1;
|
|
3098
3209
|
|
|
3099
|
-
if (
|
|
3100
|
-
// MAP segment encrypted with method
|
|
3210
|
+
if (aesKey) {
|
|
3211
|
+
// MAP segment encrypted with method AES, when served with
|
|
3101
3212
|
// HTTP Range, has the unencrypted size specified in the range.
|
|
3102
3213
|
// See: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
|
3103
3214
|
const length = (endByte + 1) - startByte;
|
|
@@ -3114,7 +3225,7 @@ shaka.hls.HlsParser = class {
|
|
|
3114
3225
|
/* mediaQuality= */ null,
|
|
3115
3226
|
/* timescale= */ null,
|
|
3116
3227
|
/* segmentData= */ null,
|
|
3117
|
-
|
|
3228
|
+
aesKey);
|
|
3118
3229
|
return initSegmentRef;
|
|
3119
3230
|
}
|
|
3120
3231
|
|
|
@@ -3129,13 +3240,13 @@ shaka.hls.HlsParser = class {
|
|
|
3129
3240
|
* @param {!shaka.hls.Playlist} playlist
|
|
3130
3241
|
* @param {shaka.extern.Stream} stream
|
|
3131
3242
|
* @param {function():!Array.<string>} getUris
|
|
3132
|
-
* @param {shaka.extern.
|
|
3243
|
+
* @param {shaka.extern.aesKey=} aesKey
|
|
3133
3244
|
* @return {shaka.media.SegmentReference}
|
|
3134
3245
|
* @private
|
|
3135
3246
|
*/
|
|
3136
3247
|
createSegmentReference_(
|
|
3137
3248
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
3138
|
-
variables, playlist, stream, getUris,
|
|
3249
|
+
variables, playlist, stream, getUris, aesKey) {
|
|
3139
3250
|
const tags = hlsSegment.tags;
|
|
3140
3251
|
const extinfTag =
|
|
3141
3252
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXTINF');
|
|
@@ -3156,7 +3267,7 @@ shaka.hls.HlsParser = class {
|
|
|
3156
3267
|
const dateTimeTag =
|
|
3157
3268
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
|
|
3158
3269
|
if (dateTimeTag && dateTimeTag.value) {
|
|
3159
|
-
syncTime = shaka.util.
|
|
3270
|
+
syncTime = shaka.util.TXml.parseDate(dateTimeTag.value);
|
|
3160
3271
|
goog.asserts.assert(syncTime != null,
|
|
3161
3272
|
'EXT-X-PROGRAM-DATE-TIME format not valid');
|
|
3162
3273
|
}
|
|
@@ -3281,7 +3392,7 @@ shaka.hls.HlsParser = class {
|
|
|
3281
3392
|
/* tileDuration= */ null,
|
|
3282
3393
|
partialSyncTime,
|
|
3283
3394
|
partialStatus,
|
|
3284
|
-
|
|
3395
|
+
aesKey);
|
|
3285
3396
|
if (item.name == 'EXT-X-PRELOAD-HINT') {
|
|
3286
3397
|
partial.markAsPreload();
|
|
3287
3398
|
isPreloadSegment = true;
|
|
@@ -3396,7 +3507,7 @@ shaka.hls.HlsParser = class {
|
|
|
3396
3507
|
tileDuration,
|
|
3397
3508
|
syncTime,
|
|
3398
3509
|
status,
|
|
3399
|
-
|
|
3510
|
+
aesKey,
|
|
3400
3511
|
allPartialSegments,
|
|
3401
3512
|
);
|
|
3402
3513
|
|
|
@@ -3450,12 +3561,13 @@ shaka.hls.HlsParser = class {
|
|
|
3450
3561
|
* @param {!Map.<number, number>} mediaSequenceToStartTime
|
|
3451
3562
|
* @param {!Map.<string, string>} variables
|
|
3452
3563
|
* @param {function():!Array.<string>} getUris
|
|
3564
|
+
* @param {string} type
|
|
3453
3565
|
* @return {{segments: !Array.<!shaka.media.SegmentReference>,
|
|
3454
3566
|
* bandwidth: (number|undefined)}}
|
|
3455
3567
|
* @private
|
|
3456
3568
|
*/
|
|
3457
3569
|
createSegments_(playlist, stream, mediaSequenceToStartTime, variables,
|
|
3458
|
-
getUris) {
|
|
3570
|
+
getUris, type) {
|
|
3459
3571
|
/** @type {Array.<!shaka.hls.Segment>} */
|
|
3460
3572
|
const hlsSegments = playlist.segments;
|
|
3461
3573
|
goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
|
|
@@ -3463,8 +3575,8 @@ shaka.hls.HlsParser = class {
|
|
|
3463
3575
|
/** @type {shaka.media.InitSegmentReference} */
|
|
3464
3576
|
let initSegmentRef;
|
|
3465
3577
|
|
|
3466
|
-
/** @type {shaka.extern.
|
|
3467
|
-
let
|
|
3578
|
+
/** @type {shaka.extern.aesKey|undefined} */
|
|
3579
|
+
let aesKey = undefined;
|
|
3468
3580
|
|
|
3469
3581
|
let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
3470
3582
|
playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
|
|
@@ -3510,14 +3622,14 @@ shaka.hls.HlsParser = class {
|
|
|
3510
3622
|
discontinuitySequence++;
|
|
3511
3623
|
}
|
|
3512
3624
|
|
|
3513
|
-
// Apply new AES
|
|
3625
|
+
// Apply new AES tags as you see them, keeping a running total.
|
|
3514
3626
|
for (const drmTag of item.tags) {
|
|
3515
3627
|
if (drmTag.name == 'EXT-X-KEY') {
|
|
3516
|
-
if (drmTag.getRequiredAttrValue('METHOD')
|
|
3517
|
-
|
|
3518
|
-
this.
|
|
3628
|
+
if (this.isAesMethod_(drmTag.getRequiredAttrValue('METHOD'))) {
|
|
3629
|
+
aesKey =
|
|
3630
|
+
this.parseAESDrmTag_(drmTag, playlist, getUris, variables);
|
|
3519
3631
|
} else {
|
|
3520
|
-
|
|
3632
|
+
aesKey = undefined;
|
|
3521
3633
|
}
|
|
3522
3634
|
}
|
|
3523
3635
|
}
|
|
@@ -3548,7 +3660,7 @@ shaka.hls.HlsParser = class {
|
|
|
3548
3660
|
playlist,
|
|
3549
3661
|
stream,
|
|
3550
3662
|
getUris,
|
|
3551
|
-
|
|
3663
|
+
aesKey);
|
|
3552
3664
|
|
|
3553
3665
|
if (reference) {
|
|
3554
3666
|
const bitrate = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
@@ -3569,7 +3681,7 @@ shaka.hls.HlsParser = class {
|
|
|
3569
3681
|
previousReference = reference;
|
|
3570
3682
|
reference.discontinuitySequence = discontinuitySequence;
|
|
3571
3683
|
|
|
3572
|
-
if (this.
|
|
3684
|
+
if (this.ignoreManifestProgramDateTimeFor_(type) &&
|
|
3573
3685
|
this.minSequenceNumber_ != null &&
|
|
3574
3686
|
position < this.minSequenceNumber_) {
|
|
3575
3687
|
// This segment is ignored as part of our fallback synchronization
|
|
@@ -3670,8 +3782,10 @@ shaka.hls.HlsParser = class {
|
|
|
3670
3782
|
// skip this step, and wait until we have sync time across stream types.
|
|
3671
3783
|
const lowestSyncTime = this.lowestSyncTime_;
|
|
3672
3784
|
if (someSyncTime && lowestSyncTime != Infinity) {
|
|
3673
|
-
|
|
3674
|
-
reference
|
|
3785
|
+
if (!this.ignoreManifestProgramDateTimeFor_(type)) {
|
|
3786
|
+
for (const reference of references) {
|
|
3787
|
+
reference.syncAgainst(lowestSyncTime);
|
|
3788
|
+
}
|
|
3675
3789
|
}
|
|
3676
3790
|
}
|
|
3677
3791
|
|
|
@@ -4020,6 +4134,17 @@ shaka.hls.HlsParser = class {
|
|
|
4020
4134
|
return op.promise;
|
|
4021
4135
|
}
|
|
4022
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
|
+
|
|
4023
4148
|
/**
|
|
4024
4149
|
* @param {!shaka.hls.Tag} drmTag
|
|
4025
4150
|
* @param {string} mimeType
|
|
@@ -4042,13 +4167,28 @@ shaka.hls.HlsParser = class {
|
|
|
4042
4167
|
.HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
|
|
4043
4168
|
}
|
|
4044
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
|
+
|
|
4045
4185
|
/*
|
|
4046
4186
|
* Even if we're not able to construct initData through the HLS tag, adding
|
|
4047
4187
|
* a DRMInfo will allow DRM Engine to request a media key system access
|
|
4048
4188
|
* with the correct keySystem and initDataType
|
|
4049
4189
|
*/
|
|
4050
4190
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
4051
|
-
'com.apple.fps', [
|
|
4191
|
+
'com.apple.fps', encryptionScheme, [
|
|
4052
4192
|
{initDataType: 'sinf', initData: new Uint8Array(0), keyId: null},
|
|
4053
4193
|
]);
|
|
4054
4194
|
|
|
@@ -4069,13 +4209,18 @@ shaka.hls.HlsParser = class {
|
|
|
4069
4209
|
return null;
|
|
4070
4210
|
}
|
|
4071
4211
|
|
|
4212
|
+
let encryptionScheme = 'cenc';
|
|
4213
|
+
if (method == 'SAMPLE-AES') {
|
|
4214
|
+
encryptionScheme = 'cbcs';
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4072
4217
|
const uri = drmTag.getRequiredAttrValue('URI');
|
|
4073
4218
|
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);
|
|
4074
4219
|
|
|
4075
4220
|
// The data encoded in the URI is a PSSH box to be used as init data.
|
|
4076
4221
|
const pssh = shaka.util.BufferUtils.toUint8(parsedData.data);
|
|
4077
4222
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
4078
|
-
'com.widevine.alpha', [
|
|
4223
|
+
'com.widevine.alpha', encryptionScheme, [
|
|
4079
4224
|
{initDataType: 'cenc', initData: pssh},
|
|
4080
4225
|
]);
|
|
4081
4226
|
|
|
@@ -4107,6 +4252,11 @@ shaka.hls.HlsParser = class {
|
|
|
4107
4252
|
return null;
|
|
4108
4253
|
}
|
|
4109
4254
|
|
|
4255
|
+
let encryptionScheme = 'cenc';
|
|
4256
|
+
if (method == 'SAMPLE-AES') {
|
|
4257
|
+
encryptionScheme = 'cbcs';
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4110
4260
|
const uri = drmTag.getRequiredAttrValue('URI');
|
|
4111
4261
|
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);
|
|
4112
4262
|
|
|
@@ -4122,7 +4272,7 @@ shaka.hls.HlsParser = class {
|
|
|
4122
4272
|
const pssh =
|
|
4123
4273
|
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
|
|
4124
4274
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
4125
|
-
'com.microsoft.playready', [
|
|
4275
|
+
'com.microsoft.playready', encryptionScheme, [
|
|
4126
4276
|
{initDataType: 'cenc', initData: pssh},
|
|
4127
4277
|
]);
|
|
4128
4278
|
|
|
@@ -4133,10 +4283,15 @@ shaka.hls.HlsParser = class {
|
|
|
4133
4283
|
* See: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-5.1
|
|
4134
4284
|
*
|
|
4135
4285
|
* @param {!shaka.hls.Tag} drmTag
|
|
4136
|
-
* @
|
|
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>}
|
|
4137
4291
|
* @private
|
|
4138
4292
|
*/
|
|
4139
|
-
|
|
4293
|
+
async identityDrmParser_(drmTag, mimeType, getUris, initSegmentRef,
|
|
4294
|
+
variables) {
|
|
4140
4295
|
if (mimeType == 'video/mp2t') {
|
|
4141
4296
|
throw new shaka.util.Error(
|
|
4142
4297
|
shaka.util.Error.Severity.CRITICAL,
|
|
@@ -4160,15 +4315,73 @@ shaka.hls.HlsParser = class {
|
|
|
4160
4315
|
return null;
|
|
4161
4316
|
}
|
|
4162
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
|
+
|
|
4163
4339
|
// NOTE: The ClearKey CDM requires a key-id to key mapping. HLS doesn't
|
|
4164
4340
|
// provide a key ID anywhere. So although we could use the 'URI' attribute
|
|
4165
4341
|
// to fetch the actual 16-byte key, without a key ID, we can't provide this
|
|
4166
|
-
// automatically to the ClearKey CDM.
|
|
4167
|
-
//
|
|
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
|
|
4168
4346
|
// and keys or player.configure('drm.servers.org\.w3\.clearkey', ...) to
|
|
4169
4347
|
// provide a ClearKey license server URI.
|
|
4170
|
-
|
|
4171
|
-
|
|
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);
|
|
4172
4385
|
}
|
|
4173
4386
|
};
|
|
4174
4387
|
|
|
@@ -4349,8 +4562,6 @@ shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
|
|
|
4349
4562
|
shaka.hls.HlsParser.widevineDrmParser_,
|
|
4350
4563
|
'com.microsoft.playready':
|
|
4351
4564
|
shaka.hls.HlsParser.playreadyDrmParser_,
|
|
4352
|
-
'identity':
|
|
4353
|
-
shaka.hls.HlsParser.identityDrmParser_,
|
|
4354
4565
|
};
|
|
4355
4566
|
|
|
4356
4567
|
|