shaka-player 4.3.12 → 4.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +7 -0
- package/.github/workflows/release-please.yaml +2 -27
- package/AUTHORS +5 -0
- package/CHANGELOG.md +108 -0
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTORS +6 -0
- package/README.md +2 -0
- package/demo/common/asset.js +5 -1
- package/demo/common/assets.js +16 -2
- package/demo/common/message_ids.js +1 -0
- package/demo/custom.js +2 -0
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +1 -1
- package/dist/demo.compiled.debug.js +56 -54
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +56 -54
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +6 -6
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +24 -22
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +24 -22
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +26 -6
- package/dist/shaka-player.compiled.debug.d.ts +26 -6
- package/dist/shaka-player.compiled.debug.externs.js +79 -49
- package/dist/shaka-player.compiled.debug.js +426 -410
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +79 -49
- package/dist/shaka-player.compiled.js +912 -901
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +38 -7
- package/dist/shaka-player.ui.debug.d.ts +38 -7
- package/dist/shaka-player.ui.debug.externs.js +80 -49
- package/dist/shaka-player.ui.debug.js +460 -442
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +80 -49
- package/dist/shaka-player.ui.js +1060 -1049
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/ad_monetization.md +54 -0
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/offline.md +1 -1
- package/docs/tutorials/plugins.md +4 -0
- package/docs/tutorials/text-displayer.md +15 -0
- package/docs/tutorials/ui-customization.md +29 -0
- package/docs/tutorials/ui.md +4 -1
- package/externs/lcevc.js +6 -0
- package/externs/shaka/manifest_parser.js +8 -0
- package/externs/shaka/player.js +1 -1
- package/externs/shaka/text.js +3 -1
- package/lib/abr/simple_abr_manager.js +1 -1
- package/lib/ads/client_side_ad.js +9 -0
- package/lib/ads/client_side_ad_manager.js +7 -2
- package/lib/ads/server_side_ad_manager.js +4 -0
- package/lib/cast/cast_proxy.js +18 -0
- package/lib/cea/cea608_memory.js +1 -1
- package/lib/cea/cea_decoder.js +11 -1
- package/lib/dash/dash_parser.js +79 -42
- package/lib/dash/mpd_utils.js +39 -27
- package/lib/hls/hls_parser.js +148 -49
- package/lib/hls/hls_utils.js +5 -5
- package/lib/media/content_workarounds.js +30 -0
- package/lib/media/drm_engine.js +16 -12
- package/lib/media/media_source_engine.js +4 -2
- package/lib/media/play_rate_controller.js +1 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/quality_observer.js +1 -1
- package/lib/media/segment_reference.js +16 -7
- package/lib/media/streaming_engine.js +64 -54
- package/lib/media/transmuxer.js +1 -1
- package/lib/media/video_wrapper.js +4 -3
- package/lib/offline/offline_manifest_parser.js +5 -0
- package/lib/offline/session_deleter.js +0 -8
- package/lib/offline/storage.js +4 -1
- package/lib/player.js +79 -18
- package/lib/polyfill/media_capabilities.js +23 -3
- package/lib/text/mp4_ttml_parser.js +2 -2
- package/lib/text/simple_text_displayer.js +7 -6
- package/lib/text/srt_text_parser.js +2 -2
- package/lib/text/text_engine.js +3 -2
- package/lib/text/ttml_text_parser.js +33 -5
- package/lib/text/ui_text_displayer.js +81 -15
- package/lib/text/vtt_text_parser.js +13 -0
- package/lib/util/buffer_utils.js +26 -4
- package/lib/util/cmcd_manager.js +16 -1
- package/lib/util/fairplay_utils.js +1 -1
- package/lib/util/manifest_parser_utils.js +20 -8
- package/lib/util/object_utils.js +1 -1
- package/lib/util/periods.js +91 -36
- package/lib/util/platform.js +39 -2
- package/lib/util/player_configuration.js +5 -3
- package/lib/util/stream_utils.js +36 -28
- package/lib/util/ts_parser.js +3 -3
- package/lib/util/xml_utils.js +5 -0
- package/package.json +2 -2
- package/test/cea/cea_decoder_unit.js +40 -0
- package/test/dash/dash_parser_live_unit.js +5 -8
- package/test/dash/dash_parser_manifest_unit.js +5 -0
- package/test/dash/dash_parser_segment_base_unit.js +5 -0
- package/test/dash/dash_parser_segment_list_unit.js +21 -17
- package/test/dash/dash_parser_segment_template_unit.js +5 -0
- package/test/hls/hls_live_unit.js +33 -0
- package/test/hls/hls_parser_unit.js +52 -60
- package/test/media/buffering_observer_unit.js +13 -0
- package/test/media/content_workarounds_unit.js +1 -1
- package/test/media/drm_engine_unit.js +30 -3
- package/test/media/media_source_engine_unit.js +1 -1
- package/test/media/transmuxer_integration.js +2 -2
- package/test/offline/storage_integration.js +3 -0
- package/test/player_integration.js +40 -40
- package/test/player_unit.js +31 -0
- package/test/polyfill/media_capabilities_unit.js +12 -6
- package/test/test/util/dash_parser_util.js +16 -7
- package/test/test/util/fake_drm_engine.js +7 -0
- package/test/test/util/layout_tests.js +2 -1
- package/test/test/util/simple_fakes.js +2 -2
- package/test/test/util/test_scheme.js +3 -0
- package/test/test/util/waiter.js +5 -0
- package/test/text/mp4_ttml_parser_unit.js +4 -4
- package/test/text/simple_text_displayer_unit.js +1 -1
- package/test/text/srt_text_parser_unit.js +1 -1
- package/test/text/text_engine_unit.js +6 -0
- package/test/text/ttml_text_parser_unit.js +49 -18
- package/test/text/ui_text_displayer_unit.js +9 -0
- package/test/text/vtt_text_parser_unit.js +2 -17
- package/test/util/buffer_utils_unit.js +1 -1
- package/test/util/periods_unit.js +113 -0
- package/test/util/ts_parser_unit.js +1 -1
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/externs/ui.js +24 -0
- package/ui/language_utils.js +11 -5
- package/ui/play_button.js +20 -0
- package/ui/playback_rate_selection.js +4 -0
- package/ui/presentation_time.js +5 -5
- package/ui/small_play_button.js +2 -2
- package/ui/volume_bar.js +10 -6
- /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
package/lib/hls/hls_parser.js
CHANGED
|
@@ -215,6 +215,9 @@ shaka.hls.HlsParser = class {
|
|
|
215
215
|
* {!Map.<string, !shaka.media.InitSegmentReference>} */
|
|
216
216
|
this.mapTagToInitSegmentRefMap_ = new Map();
|
|
217
217
|
|
|
218
|
+
/** @private {Map.<string, !shaka.extern.HlsAes128Key>} */
|
|
219
|
+
this.aesKeyInfoMap_ = new Map();
|
|
220
|
+
|
|
218
221
|
/** @private {boolean} */
|
|
219
222
|
this.lowLatencyMode_ = false;
|
|
220
223
|
}
|
|
@@ -277,6 +280,8 @@ shaka.hls.HlsParser = class {
|
|
|
277
280
|
this.groupIdToStreamInfosMap_.clear();
|
|
278
281
|
this.groupIdToCodecsMap_.clear();
|
|
279
282
|
this.globalVariables_.clear();
|
|
283
|
+
this.mapTagToInitSegmentRefMap_.clear();
|
|
284
|
+
this.aesKeyInfoMap_.clear();
|
|
280
285
|
|
|
281
286
|
return Promise.all(pending);
|
|
282
287
|
}
|
|
@@ -435,6 +440,14 @@ shaka.hls.HlsParser = class {
|
|
|
435
440
|
// No-op
|
|
436
441
|
}
|
|
437
442
|
|
|
443
|
+
/**
|
|
444
|
+
* @override
|
|
445
|
+
* @exportInterface
|
|
446
|
+
*/
|
|
447
|
+
onInitialVariantChosen(variant) {
|
|
448
|
+
// No-op
|
|
449
|
+
}
|
|
450
|
+
|
|
438
451
|
/**
|
|
439
452
|
* Align the streams by sequence number by dropping early segments. Then
|
|
440
453
|
* offset the streams to begin at presentation time 0.
|
|
@@ -774,7 +787,7 @@ shaka.hls.HlsParser = class {
|
|
|
774
787
|
// enough info here to determine the presentation type and duration.
|
|
775
788
|
if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
|
|
776
789
|
if (this.isLive_()) {
|
|
777
|
-
this.changePresentationTimelineToLive_();
|
|
790
|
+
this.changePresentationTimelineToLive_(playlist);
|
|
778
791
|
const delay = this.updatePlaylistDelay_;
|
|
779
792
|
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
|
|
780
793
|
}
|
|
@@ -833,6 +846,22 @@ shaka.hls.HlsParser = class {
|
|
|
833
846
|
this.presentationTimeline_.setSegmentAvailabilityDuration(
|
|
834
847
|
segmentAvailabilityDuration);
|
|
835
848
|
}
|
|
849
|
+
|
|
850
|
+
if (!this.presentationTimeline_.isStartTimeLocked()) {
|
|
851
|
+
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
|
|
852
|
+
const activeStreamInfos =
|
|
853
|
+
streamInfos.filter((s) => s.stream.segmentIndex);
|
|
854
|
+
for (const streamInfo of activeStreamInfos) {
|
|
855
|
+
if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const firstReference = streamInfo.stream.segmentIndex.get(0);
|
|
859
|
+
if (firstReference && firstReference.syncTime) {
|
|
860
|
+
const syncTime = firstReference.syncTime;
|
|
861
|
+
this.presentationTimeline_.setPresentationStartTime(syncTime);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
836
865
|
} else {
|
|
837
866
|
// Use the minimum duration as the presentation duration.
|
|
838
867
|
this.presentationTimeline_.setDuration(this.getMinDuration_());
|
|
@@ -1291,9 +1320,15 @@ shaka.hls.HlsParser = class {
|
|
|
1291
1320
|
// the value NONE. If the value is a quoted-string, it MUST match the
|
|
1292
1321
|
// value of the GROUP-ID attribute of an EXT-X-MEDIA tag elsewhere in the
|
|
1293
1322
|
// Playlist whose TYPE attribute is CLOSED-CAPTIONS.
|
|
1294
|
-
if (type == ContentType.VIDEO
|
|
1295
|
-
|
|
1296
|
-
|
|
1323
|
+
if (type == ContentType.VIDEO) {
|
|
1324
|
+
if (closedCaptionsAttr && closedCaptionsAttr != 'NONE') {
|
|
1325
|
+
return this.groupIdToClosedCaptionsMap_.get(closedCaptionsAttr);
|
|
1326
|
+
} else if (!closedCaptionsAttr &&
|
|
1327
|
+
this.groupIdToClosedCaptionsMap_.size) {
|
|
1328
|
+
for (const key of this.groupIdToClosedCaptionsMap_.keys()) {
|
|
1329
|
+
return this.groupIdToClosedCaptionsMap_.get(key);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1297
1332
|
}
|
|
1298
1333
|
return null;
|
|
1299
1334
|
}
|
|
@@ -1440,7 +1475,14 @@ shaka.hls.HlsParser = class {
|
|
|
1440
1475
|
for (const tag of closedCaptionsTags) {
|
|
1441
1476
|
goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
|
|
1442
1477
|
'Should only be called on media tags!');
|
|
1443
|
-
const
|
|
1478
|
+
const languageValue = tag.getAttributeValue('LANGUAGE');
|
|
1479
|
+
let language = this.getLanguage_(tag);
|
|
1480
|
+
if (!languageValue) {
|
|
1481
|
+
const nameValue = tag.getAttributeValue('NAME');
|
|
1482
|
+
if (nameValue) {
|
|
1483
|
+
language = nameValue;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1444
1486
|
|
|
1445
1487
|
// The GROUP-ID value is a quoted-string that specifies the group to which
|
|
1446
1488
|
// the Rendition belongs.
|
|
@@ -1449,7 +1491,9 @@ shaka.hls.HlsParser = class {
|
|
|
1449
1491
|
// The value of INSTREAM-ID is a quoted-string that specifies a Rendition
|
|
1450
1492
|
// within the segments in the Media Playlist. This attribute is REQUIRED
|
|
1451
1493
|
// if the TYPE attribute is CLOSED-CAPTIONS.
|
|
1452
|
-
|
|
1494
|
+
// We need replace SERVICE string by our internal svc string.
|
|
1495
|
+
const instreamId = tag.getRequiredAttrValue('INSTREAM-ID')
|
|
1496
|
+
.replace('SERVICE', 'svc');
|
|
1453
1497
|
if (!this.groupIdToClosedCaptionsMap_.get(groupId)) {
|
|
1454
1498
|
this.groupIdToClosedCaptionsMap_.set(groupId, new Map());
|
|
1455
1499
|
}
|
|
@@ -1709,7 +1753,7 @@ shaka.hls.HlsParser = class {
|
|
|
1709
1753
|
if (this.isLive_() && !wasLive) {
|
|
1710
1754
|
// Now that we know that the presentation is live, convert the timeline
|
|
1711
1755
|
// to live.
|
|
1712
|
-
this.changePresentationTimelineToLive_();
|
|
1756
|
+
this.changePresentationTimelineToLive_(playlist);
|
|
1713
1757
|
}
|
|
1714
1758
|
|
|
1715
1759
|
// Copy values from the real stream info to our initial one.
|
|
@@ -2074,8 +2118,15 @@ shaka.hls.HlsParser = class {
|
|
|
2074
2118
|
}
|
|
2075
2119
|
}
|
|
2076
2120
|
|
|
2077
|
-
|
|
2078
|
-
|
|
2121
|
+
let kind = undefined;
|
|
2122
|
+
if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) {
|
|
2123
|
+
if (roles.includes('public.accessibility.transcribes-spoken-dialog') &&
|
|
2124
|
+
roles.includes('public.accessibility.describes-music-and-sound')) {
|
|
2125
|
+
kind = shaka.util.ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
|
|
2126
|
+
} else {
|
|
2127
|
+
kind = shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2079
2130
|
|
|
2080
2131
|
// If there are no roles, and we have defaulted to the subtitle "kind" for
|
|
2081
2132
|
// this track, add the implied subtitle role.
|
|
@@ -2231,30 +2282,36 @@ shaka.hls.HlsParser = class {
|
|
|
2231
2282
|
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
2232
2283
|
[keyUri], this.config_.retryParameters);
|
|
2233
2284
|
|
|
2234
|
-
const
|
|
2285
|
+
const aesKeyInfoKey = `${drmTag.toString()}-${firstMediaSequenceNumber}`;
|
|
2286
|
+
if (!this.aesKeyInfoMap_.has(aesKeyInfoKey)) {
|
|
2287
|
+
const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
|
|
2235
2288
|
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2289
|
+
// Don't download the key object until the segment is parsed, to avoid a
|
|
2290
|
+
// startup delay for long manifests with lots of keys.
|
|
2291
|
+
keyInfo.fetchKey = async () => {
|
|
2292
|
+
const keyResponse = await this.makeNetworkRequest_(
|
|
2293
|
+
request, requestType);
|
|
2240
2294
|
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2295
|
+
// keyResponse.status is undefined when URI is
|
|
2296
|
+
// "data:text/plain;base64,"
|
|
2297
|
+
if (!keyResponse.data || keyResponse.data.byteLength != 16) {
|
|
2298
|
+
throw new shaka.util.Error(
|
|
2299
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2300
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2301
|
+
shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
|
|
2302
|
+
}
|
|
2248
2303
|
|
|
2249
|
-
|
|
2250
|
-
|
|
2304
|
+
const algorithm = {
|
|
2305
|
+
name: 'AES-CBC',
|
|
2306
|
+
};
|
|
2307
|
+
keyInfo.cryptoKey = await window.crypto.subtle.importKey(
|
|
2308
|
+
'raw', keyResponse.data, algorithm, true, ['decrypt']);
|
|
2309
|
+
keyInfo.fetchKey = undefined; // No longer needed.
|
|
2251
2310
|
};
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
keyInfo.fetchKey = undefined; // No longer needed.
|
|
2255
|
-
};
|
|
2311
|
+
this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo);
|
|
2312
|
+
}
|
|
2256
2313
|
|
|
2257
|
-
return
|
|
2314
|
+
return this.aesKeyInfoMap_.get(aesKeyInfoKey);
|
|
2258
2315
|
}
|
|
2259
2316
|
|
|
2260
2317
|
|
|
@@ -2323,9 +2380,10 @@ shaka.hls.HlsParser = class {
|
|
|
2323
2380
|
}
|
|
2324
2381
|
|
|
2325
2382
|
/**
|
|
2383
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
2326
2384
|
* @private
|
|
2327
2385
|
*/
|
|
2328
|
-
changePresentationTimelineToLive_() {
|
|
2386
|
+
changePresentationTimelineToLive_(playlist) {
|
|
2329
2387
|
// The live edge will be calculated from segments, so we don't need to
|
|
2330
2388
|
// set a presentation start time. We will assert later that this is
|
|
2331
2389
|
// working as expected.
|
|
@@ -2346,8 +2404,12 @@ shaka.hls.HlsParser = class {
|
|
|
2346
2404
|
} else if (this.lowLatencyPresentationDelay_) {
|
|
2347
2405
|
presentationDelay = this.lowLatencyPresentationDelay_;
|
|
2348
2406
|
} else {
|
|
2349
|
-
const
|
|
2350
|
-
|
|
2407
|
+
const playlistSegments = playlist.segments.length;
|
|
2408
|
+
let delaySegments = this.config_.hls.liveSegmentsDelay;
|
|
2409
|
+
if (delaySegments > (playlistSegments - 2)) {
|
|
2410
|
+
delaySegments = Math.max(1, playlistSegments - 2);
|
|
2411
|
+
}
|
|
2412
|
+
presentationDelay = this.maxTargetDuration_ * delaySegments;
|
|
2351
2413
|
}
|
|
2352
2414
|
|
|
2353
2415
|
this.presentationTimeline_.setPresentationStartTime(0);
|
|
@@ -2357,13 +2419,13 @@ shaka.hls.HlsParser = class {
|
|
|
2357
2419
|
|
|
2358
2420
|
/**
|
|
2359
2421
|
* Get the InitSegmentReference for a segment if it has a EXT-X-MAP tag.
|
|
2360
|
-
* @param {
|
|
2422
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
2361
2423
|
* @param {!Array.<!shaka.hls.Tag>} tags Segment tags
|
|
2362
2424
|
* @param {!Map.<string, string>} variables
|
|
2363
2425
|
* @return {shaka.media.InitSegmentReference}
|
|
2364
2426
|
* @private
|
|
2365
2427
|
*/
|
|
2366
|
-
getInitSegmentReference_(
|
|
2428
|
+
getInitSegmentReference_(playlist, tags, variables) {
|
|
2367
2429
|
/** @type {?shaka.hls.Tag} */
|
|
2368
2430
|
const mapTag = shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-MAP');
|
|
2369
2431
|
|
|
@@ -2374,7 +2436,7 @@ shaka.hls.HlsParser = class {
|
|
|
2374
2436
|
const verbatimInitSegmentUri = mapTag.getRequiredAttrValue('URI');
|
|
2375
2437
|
const absoluteInitSegmentUri = this.variableSubstitution_(
|
|
2376
2438
|
shaka.hls.Utils.constructAbsoluteUri(
|
|
2377
|
-
|
|
2439
|
+
playlist.absoluteUri, verbatimInitSegmentUri),
|
|
2378
2440
|
variables);
|
|
2379
2441
|
|
|
2380
2442
|
const mapTagKey = [
|
|
@@ -2382,8 +2444,21 @@ shaka.hls.HlsParser = class {
|
|
|
2382
2444
|
mapTag.getAttributeValue('BYTERANGE', ''),
|
|
2383
2445
|
].join('-');
|
|
2384
2446
|
if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
|
|
2447
|
+
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2448
|
+
let aes128Key = undefined;
|
|
2449
|
+
let byteRangeTag = null;
|
|
2450
|
+
for (const tag of tags) {
|
|
2451
|
+
if (tag.name == 'EXT-X-KEY') {
|
|
2452
|
+
if (tag.getRequiredAttrValue('METHOD') == 'AES-128' &&
|
|
2453
|
+
tag.id < mapTag.id) {
|
|
2454
|
+
aes128Key = this.parseAES128DrmTag_(tag, playlist);
|
|
2455
|
+
}
|
|
2456
|
+
} else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) {
|
|
2457
|
+
byteRangeTag = tag;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2385
2460
|
const initSegmentRef = this.createInitSegmentReference_(
|
|
2386
|
-
absoluteInitSegmentUri, mapTag);
|
|
2461
|
+
absoluteInitSegmentUri, mapTag, byteRangeTag, aes128Key);
|
|
2387
2462
|
this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
|
|
2388
2463
|
}
|
|
2389
2464
|
return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
|
|
@@ -2394,13 +2469,19 @@ shaka.hls.HlsParser = class {
|
|
|
2394
2469
|
* playlist.
|
|
2395
2470
|
* @param {string} absoluteInitSegmentUri
|
|
2396
2471
|
* @param {!shaka.hls.Tag} mapTag EXT-X-MAP
|
|
2472
|
+
* @param {shaka.hls.Tag=} byteRangeTag EXT-X-BYTERANGE
|
|
2473
|
+
* @param {shaka.extern.HlsAes128Key=} aes128Key
|
|
2397
2474
|
* @return {!shaka.media.InitSegmentReference}
|
|
2398
2475
|
* @private
|
|
2399
2476
|
*/
|
|
2400
|
-
createInitSegmentReference_(absoluteInitSegmentUri, mapTag
|
|
2477
|
+
createInitSegmentReference_(absoluteInitSegmentUri, mapTag, byteRangeTag,
|
|
2478
|
+
aes128Key) {
|
|
2401
2479
|
let startByte = 0;
|
|
2402
2480
|
let endByte = null;
|
|
2403
|
-
|
|
2481
|
+
let byterange = mapTag.getAttributeValue('BYTERANGE');
|
|
2482
|
+
if (!byterange && byteRangeTag) {
|
|
2483
|
+
byterange = byteRangeTag.value;
|
|
2484
|
+
}
|
|
2404
2485
|
// If a BYTERANGE attribute is not specified, the segment consists
|
|
2405
2486
|
// of the entire resource.
|
|
2406
2487
|
if (byterange) {
|
|
@@ -2408,12 +2489,25 @@ shaka.hls.HlsParser = class {
|
|
|
2408
2489
|
const byteLength = Number(blocks[0]);
|
|
2409
2490
|
startByte = Number(blocks[1]);
|
|
2410
2491
|
endByte = startByte + byteLength - 1;
|
|
2492
|
+
|
|
2493
|
+
if (aes128Key) {
|
|
2494
|
+
// MAP segment encrypted with method 'AES-128', when served with
|
|
2495
|
+
// HTTP Range, has the unencrypted size specified in the range.
|
|
2496
|
+
// See: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
|
2497
|
+
const length = (endByte + 1) - startByte;
|
|
2498
|
+
if (length % 16) {
|
|
2499
|
+
endByte += (16 - (length % 16));
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2411
2502
|
}
|
|
2412
2503
|
|
|
2413
2504
|
const initSegmentRef = new shaka.media.InitSegmentReference(
|
|
2414
2505
|
() => [absoluteInitSegmentUri],
|
|
2415
2506
|
startByte,
|
|
2416
|
-
endByte
|
|
2507
|
+
endByte,
|
|
2508
|
+
/* mediaQuality= */ null,
|
|
2509
|
+
/* timescale= */ null,
|
|
2510
|
+
aes128Key);
|
|
2417
2511
|
return initSegmentRef;
|
|
2418
2512
|
}
|
|
2419
2513
|
|
|
@@ -2427,15 +2521,13 @@ shaka.hls.HlsParser = class {
|
|
|
2427
2521
|
* @param {!Map.<string, string>} variables
|
|
2428
2522
|
* @param {string} absoluteMediaPlaylistUri
|
|
2429
2523
|
* @param {string} type
|
|
2430
|
-
* @param {number} timestampOffset
|
|
2431
2524
|
* @param {shaka.extern.HlsAes128Key=} hlsAes128Key
|
|
2432
2525
|
* @return {shaka.media.SegmentReference}
|
|
2433
2526
|
* @private
|
|
2434
2527
|
*/
|
|
2435
2528
|
createSegmentReference_(
|
|
2436
2529
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
2437
|
-
variables, absoluteMediaPlaylistUri, type,
|
|
2438
|
-
hlsAes128Key) {
|
|
2530
|
+
variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
|
|
2439
2531
|
const tags = hlsSegment.tags;
|
|
2440
2532
|
const absoluteSegmentUri = this.variableSubstitution_(
|
|
2441
2533
|
hlsSegment.absoluteUri, variables);
|
|
@@ -2539,7 +2631,7 @@ shaka.hls.HlsParser = class {
|
|
|
2539
2631
|
pStartByte,
|
|
2540
2632
|
pEndByte,
|
|
2541
2633
|
initSegmentReference,
|
|
2542
|
-
/* timestampOffset= */ 0,
|
|
2634
|
+
/* timestampOffset= */ 0,
|
|
2543
2635
|
/* appendWindowStart= */ 0,
|
|
2544
2636
|
/* appendWindowEnd= */ Infinity,
|
|
2545
2637
|
/* partialReferences= */ [],
|
|
@@ -2571,8 +2663,11 @@ shaka.hls.HlsParser = class {
|
|
|
2571
2663
|
return null;
|
|
2572
2664
|
}
|
|
2573
2665
|
endTime = startTime + duration;
|
|
2574
|
-
} else {
|
|
2666
|
+
} else if (partialSegmentRefs.length) {
|
|
2575
2667
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
2668
|
+
} else {
|
|
2669
|
+
// Skip segments without duration and without partialsegments
|
|
2670
|
+
return null;
|
|
2576
2671
|
}
|
|
2577
2672
|
|
|
2578
2673
|
// If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
|
|
@@ -2612,7 +2707,7 @@ shaka.hls.HlsParser = class {
|
|
|
2612
2707
|
startByte,
|
|
2613
2708
|
endByte,
|
|
2614
2709
|
initSegmentReference,
|
|
2615
|
-
timestampOffset
|
|
2710
|
+
/* timestampOffset= */ 0,
|
|
2616
2711
|
/* appendWindowStart= */ 0,
|
|
2617
2712
|
/* appendWindowEnd= */ Infinity,
|
|
2618
2713
|
partialSegmentRefs,
|
|
@@ -2692,12 +2787,18 @@ shaka.hls.HlsParser = class {
|
|
|
2692
2787
|
if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
|
|
2693
2788
|
firstStartTime = mediaSequenceToStartTime.get(position);
|
|
2694
2789
|
}
|
|
2790
|
+
// This is for recovering from disconnects.
|
|
2791
|
+
if (firstStartTime === 0 &&
|
|
2792
|
+
this.presentationType_ == shaka.hls.HlsParser.PresentationType_.LIVE &&
|
|
2793
|
+
mediaSequenceToStartTime.size > 0 &&
|
|
2794
|
+
!mediaSequenceToStartTime.has(position)) {
|
|
2795
|
+
firstStartTime = this.presentationTimeline_.getSegmentAvailabilityStart();
|
|
2796
|
+
}
|
|
2695
2797
|
|
|
2696
2798
|
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
2697
2799
|
const references = [];
|
|
2698
2800
|
|
|
2699
2801
|
let previousReference = null;
|
|
2700
|
-
let lastDiscontinuityStartTime = firstStartTime;
|
|
2701
2802
|
|
|
2702
2803
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2703
2804
|
const item = hlsSegments[i];
|
|
@@ -2709,7 +2810,6 @@ shaka.hls.HlsParser = class {
|
|
|
2709
2810
|
item.tags, 'EXT-X-DISCONTINUITY');
|
|
2710
2811
|
if (discontinuityTag) {
|
|
2711
2812
|
discontinuitySequence++;
|
|
2712
|
-
lastDiscontinuityStartTime = startTime;
|
|
2713
2813
|
}
|
|
2714
2814
|
|
|
2715
2815
|
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
@@ -2725,8 +2825,8 @@ shaka.hls.HlsParser = class {
|
|
|
2725
2825
|
|
|
2726
2826
|
mediaSequenceToStartTime.set(position, startTime);
|
|
2727
2827
|
|
|
2728
|
-
initSegmentRef = this.getInitSegmentReference_(playlist.
|
|
2729
|
-
|
|
2828
|
+
initSegmentRef = this.getInitSegmentReference_(playlist, item.tags,
|
|
2829
|
+
variables);
|
|
2730
2830
|
|
|
2731
2831
|
// If the stream is low latency and the user has not configured the
|
|
2732
2832
|
// lowLatencyMode, but if it has been configured to activate the
|
|
@@ -2748,7 +2848,6 @@ shaka.hls.HlsParser = class {
|
|
|
2748
2848
|
variables,
|
|
2749
2849
|
playlist.absoluteUri,
|
|
2750
2850
|
type,
|
|
2751
|
-
lastDiscontinuityStartTime,
|
|
2752
2851
|
hlsAes128Key);
|
|
2753
2852
|
|
|
2754
2853
|
if (reference) {
|
package/lib/hls/hls_utils.js
CHANGED
|
@@ -45,12 +45,12 @@ shaka.hls.Utils = class {
|
|
|
45
45
|
* @return {?shaka.hls.Tag}
|
|
46
46
|
*/
|
|
47
47
|
static getFirstTagWithName(tags, name) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
for (const tag of tags) {
|
|
49
|
+
if (tag.name === name) {
|
|
50
|
+
return tag;
|
|
51
|
+
}
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
return tagsWithName[0];
|
|
53
|
+
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -69,6 +69,30 @@ shaka.media.ContentWorkarounds = class {
|
|
|
69
69
|
})
|
|
70
70
|
.fullBox('encv', onEncryptionMetadataBox)
|
|
71
71
|
.fullBox('enca', onEncryptionMetadataBox)
|
|
72
|
+
.fullBox('dvav', (box) => {
|
|
73
|
+
boxesToModify.push({
|
|
74
|
+
box,
|
|
75
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
|
+
});
|
|
77
|
+
})
|
|
78
|
+
.fullBox('dva1', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
84
|
+
.fullBox('dvh1', (box) => {
|
|
85
|
+
boxesToModify.push({
|
|
86
|
+
box,
|
|
87
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
.fullBox('dvhe', (box) => {
|
|
91
|
+
boxesToModify.push({
|
|
92
|
+
box,
|
|
93
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
94
|
+
});
|
|
95
|
+
})
|
|
72
96
|
.fullBox('hev1', (box) => {
|
|
73
97
|
boxesToModify.push({
|
|
74
98
|
box,
|
|
@@ -105,6 +129,12 @@ shaka.media.ContentWorkarounds = class {
|
|
|
105
129
|
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
106
130
|
});
|
|
107
131
|
})
|
|
132
|
+
.fullBox('ac-4', (box) => {
|
|
133
|
+
boxesToModify.push({
|
|
134
|
+
box,
|
|
135
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
136
|
+
});
|
|
137
|
+
})
|
|
108
138
|
.fullBox('mp4a', (box) => {
|
|
109
139
|
boxesToModify.push({
|
|
110
140
|
box,
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -520,6 +520,15 @@ shaka.media.DrmEngine = class {
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Returns true if the manifest has init data.
|
|
525
|
+
*
|
|
526
|
+
* @return {boolean}
|
|
527
|
+
*/
|
|
528
|
+
hasManifestInitData() {
|
|
529
|
+
return !!this.manifestInitData_;
|
|
530
|
+
}
|
|
531
|
+
|
|
523
532
|
/**
|
|
524
533
|
* Sets the server certificate based on the current DrmInfo.
|
|
525
534
|
*
|
|
@@ -1920,19 +1929,14 @@ shaka.media.DrmEngine = class {
|
|
|
1920
1929
|
|
|
1921
1930
|
for (const drm1 of drms1) {
|
|
1922
1931
|
for (const drm2 of drms2) {
|
|
1923
|
-
// This method is only called to compare drmInfos of a video and an
|
|
1924
|
-
// audio adaptations, so we shouldn't have to worry about checking
|
|
1925
|
-
// robustness.
|
|
1926
1932
|
if (drm1.keySystem == drm2.keySystem) {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
});
|
|
1935
|
-
});
|
|
1933
|
+
const initDataMap = new Map();
|
|
1934
|
+
const bothInitDatas = (drm1.initData || [])
|
|
1935
|
+
.concat(drm2.initData || []);
|
|
1936
|
+
for (const d of bothInitDatas) {
|
|
1937
|
+
initDataMap.set(d.keyId, d);
|
|
1938
|
+
}
|
|
1939
|
+
const initData = Array.from(initDataMap.values());
|
|
1936
1940
|
|
|
1937
1941
|
const keyIds = drm1.keyIds && drm2.keyIds ?
|
|
1938
1942
|
new Set([...drm1.keyIds, ...drm2.keyIds]) :
|
|
@@ -202,6 +202,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
202
202
|
'audio/mp4; codecs="mp4a.40.2"',
|
|
203
203
|
'audio/mp4; codecs="ac-3"',
|
|
204
204
|
'audio/mp4; codecs="ec-3"',
|
|
205
|
+
'audio/mp4; codecs="ac-4"',
|
|
205
206
|
'audio/mp4; codecs="opus"',
|
|
206
207
|
'audio/mp4; codecs="flac"',
|
|
207
208
|
'audio/mp4; codecs="dtsc"', // DTS Digital Surround
|
|
@@ -601,14 +602,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
601
602
|
await this.textEngine_.appendBuffer(
|
|
602
603
|
data,
|
|
603
604
|
reference ? reference.startTime : null,
|
|
604
|
-
reference ? reference.endTime : null
|
|
605
|
+
reference ? reference.endTime : null,
|
|
606
|
+
reference ? reference.getUris()[0] : null);
|
|
605
607
|
return;
|
|
606
608
|
}
|
|
607
609
|
|
|
608
610
|
const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
|
|
609
611
|
let mimeType = this.sourceBufferTypes_[contentType];
|
|
610
612
|
if (this.transmuxers_[contentType]) {
|
|
611
|
-
mimeType = this.transmuxers_[contentType].
|
|
613
|
+
mimeType = this.transmuxers_[contentType].getOriginalMimeType();
|
|
612
614
|
}
|
|
613
615
|
if (shaka.util.TsParser.probe(uint8ArrayData)) {
|
|
614
616
|
const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
|
|
@@ -288,6 +288,17 @@ shaka.media.PresentationTimeline = class {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Returns if the presentation timeline's start time is locked.
|
|
293
|
+
*
|
|
294
|
+
* @return {boolean}
|
|
295
|
+
* @export
|
|
296
|
+
*/
|
|
297
|
+
isStartTimeLocked() {
|
|
298
|
+
return this.startTimeLocked_;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
291
302
|
/**
|
|
292
303
|
* Gives PresentationTimeline a Stream's minimum segment start time.
|
|
293
304
|
*
|
|
@@ -261,7 +261,7 @@ shaka.media.QualityObserver.QualityChangePosition;
|
|
|
261
261
|
*
|
|
262
262
|
* @description
|
|
263
263
|
* Contains media quality information for a specific content type
|
|
264
|
-
* e.g video or audio.
|
|
264
|
+
* e.g. video or audio.
|
|
265
265
|
*
|
|
266
266
|
* @property {!Array.<shaka.media.QualityObserver.QualityChangePosition>}
|
|
267
267
|
* qualityChangePositions
|
|
@@ -27,11 +27,14 @@ shaka.media.InitSegmentReference = class {
|
|
|
27
27
|
* @param {?number} endByte The offset from the start of the resource
|
|
28
28
|
* to the end of the segment, inclusive. A value of null indicates that the
|
|
29
29
|
* segment extends to the end of the resource.
|
|
30
|
-
* @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information
|
|
31
|
-
* the quality of the media associated with this init segment.
|
|
32
|
-
* @param {number=} timescale
|
|
30
|
+
* @param {(null|shaka.extern.MediaQualityInfo)=} mediaQuality Information
|
|
31
|
+
* about the quality of the media associated with this init segment.
|
|
32
|
+
* @param {(null|number)=} timescale
|
|
33
|
+
* @param {?shaka.extern.HlsAes128Key=} aes128Key
|
|
34
|
+
* The segment's AES-128-CBC full segment encryption key and iv.
|
|
33
35
|
*/
|
|
34
|
-
constructor(uris, startByte, endByte, mediaQuality = null, timescale
|
|
36
|
+
constructor(uris, startByte, endByte, mediaQuality = null, timescale = null,
|
|
37
|
+
aes128Key = null) {
|
|
35
38
|
/** @type {function():!Array.<string>} */
|
|
36
39
|
this.getUris = uris;
|
|
37
40
|
|
|
@@ -44,8 +47,11 @@ shaka.media.InitSegmentReference = class {
|
|
|
44
47
|
/** @const {shaka.extern.MediaQualityInfo|null} */
|
|
45
48
|
this.mediaQuality = mediaQuality;
|
|
46
49
|
|
|
47
|
-
/** @type {number|
|
|
50
|
+
/** @type {number|null} */
|
|
48
51
|
this.timescale = timescale;
|
|
52
|
+
|
|
53
|
+
/** @type {?shaka.extern.HlsAes128Key} */
|
|
54
|
+
this.hlsAes128Key = aes128Key;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/**
|
|
@@ -101,12 +107,15 @@ shaka.media.InitSegmentReference = class {
|
|
|
101
107
|
*/
|
|
102
108
|
static equal(reference1, reference2) {
|
|
103
109
|
const ArrayUtils = shaka.util.ArrayUtils;
|
|
104
|
-
if (
|
|
110
|
+
if (reference1 === reference2) {
|
|
111
|
+
return true;
|
|
112
|
+
} else if (!reference1 || !reference2) {
|
|
105
113
|
return reference1 == reference2;
|
|
106
114
|
} else {
|
|
107
115
|
return reference1.getStartByte() == reference2.getStartByte() &&
|
|
108
116
|
reference1.getEndByte() == reference2.getEndByte() &&
|
|
109
|
-
ArrayUtils.equal(
|
|
117
|
+
ArrayUtils.equal(
|
|
118
|
+
reference1.getUris().sort(), reference2.getUris().sort());
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
};
|