shaka-player 4.10.13 → 4.11.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/README.md +5 -1
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +11 -11
- package/dist/locales.js +38 -34
- package/dist/shaka-player.compiled.d.ts +68 -33
- package/dist/shaka-player.compiled.debug.d.ts +68 -33
- package/dist/shaka-player.compiled.debug.externs.js +584 -544
- package/dist/shaka-player.compiled.debug.js +715 -676
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +584 -544
- package/dist/shaka-player.compiled.js +1394 -1371
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +76 -34
- package/dist/shaka-player.ui.debug.d.ts +76 -34
- package/dist/shaka-player.ui.debug.externs.js +77 -26
- package/dist/shaka-player.ui.debug.js +774 -720
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +77 -26
- package/dist/shaka-player.ui.js +1649 -1617
- package/dist/shaka-player.ui.map +4 -4
- package/externs/mediasession.js +10 -1
- package/externs/shaka/ads.js +79 -2
- package/externs/shaka/manifest.js +6 -1
- package/externs/shaka/manifest_parser.js +6 -2
- package/externs/shaka/net.js +23 -2
- package/externs/shaka/player.js +414 -103
- package/externs/shaka/text.js +2 -1
- package/lib/abr/simple_abr_manager.js +5 -0
- package/lib/ads/ad_manager.js +45 -1
- package/lib/ads/ad_utils.js +124 -1
- package/lib/ads/interstitial_ad.js +9 -2
- package/lib/ads/interstitial_ad_manager.js +216 -101
- package/lib/ads/media_tailor_ad.js +1 -39
- package/lib/ads/media_tailor_ad_manager.js +32 -16
- package/lib/cea/cea608_data_channel.js +40 -8
- package/lib/cea/cea608_memory.js +28 -0
- package/lib/dash/dash_parser.js +162 -25
- package/lib/dash/segment_base.js +3 -2
- package/lib/dash/segment_list.js +6 -3
- package/lib/dash/segment_template.js +104 -20
- package/lib/hls/hls_parser.js +319 -215
- package/lib/media/drm_engine.js +11 -3
- package/lib/media/media_source_engine.js +0 -8
- package/lib/media/preload_manager.js +16 -4
- package/lib/media/segment_reference.js +3 -0
- package/lib/media/segment_utils.js +8 -1
- package/lib/media/streaming_engine.js +10 -6
- package/lib/mss/mss_parser.js +14 -2
- package/lib/net/http_fetch_plugin.js +12 -5
- package/lib/net/http_xhr_plugin.js +7 -3
- package/lib/net/networking_engine.js +21 -1
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +336 -121
- package/lib/text/cue.js +373 -0
- package/lib/text/mp4_ttml_parser.js +51 -1
- package/lib/text/mp4_vtt_parser.js +2 -5
- package/lib/text/sbv_text_parser.js +2 -31
- package/lib/text/srt_text_parser.js +1 -1
- package/lib/text/text_engine.js +1 -1
- package/lib/text/text_utils.js +1 -0
- package/lib/text/ttml_text_parser.js +14 -4
- package/lib/text/ui_text_displayer.js +0 -4
- package/lib/text/vtt_text_parser.js +10 -415
- package/lib/util/abortable_operation.js +8 -0
- package/lib/util/cmcd_manager.js +38 -0
- package/lib/util/error.js +16 -0
- package/lib/util/fake_event.js +1 -0
- package/lib/util/manifest_parser_utils.js +12 -3
- package/lib/util/player_configuration.js +4 -2
- package/lib/util/stream_utils.js +57 -2
- package/lib/util/tXml.js +2 -1
- package/lib/util/text_parser.js +46 -0
- package/lib/util/ts_parser.js +32 -3
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +1 -1
- package/ui/chapter_selection.js +179 -0
- package/ui/controls.js +151 -0
- package/ui/enums.js +1 -0
- package/ui/externs/ui.js +7 -1
- package/ui/less/containers.less +3 -4
- package/ui/less/overflow_menu.less +5 -0
- package/ui/less/tooltip.less +1 -1
- package/ui/locales/en.json +1 -0
- package/ui/locales/es.json +1 -0
- package/ui/locales/fr.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/seek_bar.js +3 -2
- package/ui/ui.js +2 -0
- package/lib/polyfill/abort_controller.js +0 -158
package/lib/hls/hls_parser.js
CHANGED
|
@@ -24,6 +24,7 @@ goog.require('shaka.media.SegmentIndex');
|
|
|
24
24
|
goog.require('shaka.media.SegmentReference');
|
|
25
25
|
goog.require('shaka.net.DataUriPlugin');
|
|
26
26
|
goog.require('shaka.net.NetworkingEngine');
|
|
27
|
+
goog.require('shaka.net.NetworkingEngine.PendingRequest');
|
|
27
28
|
goog.require('shaka.util.ArrayUtils');
|
|
28
29
|
goog.require('shaka.util.BufferUtils');
|
|
29
30
|
goog.require('shaka.util.DrmUtils');
|
|
@@ -42,6 +43,7 @@ goog.require('shaka.util.Timer');
|
|
|
42
43
|
goog.require('shaka.util.TsParser');
|
|
43
44
|
goog.require('shaka.util.TXml');
|
|
44
45
|
goog.require('shaka.util.Platform');
|
|
46
|
+
goog.require('shaka.util.StreamUtils');
|
|
45
47
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
46
48
|
goog.requireType('shaka.hls.Segment');
|
|
47
49
|
|
|
@@ -267,15 +269,26 @@ shaka.hls.HlsParser = class {
|
|
|
267
269
|
|
|
268
270
|
/** @private {HTMLMediaElement} */
|
|
269
271
|
this.mediaElement_ = null;
|
|
272
|
+
|
|
273
|
+
/** @private {?number} */
|
|
274
|
+
this.startTime_ = null;
|
|
275
|
+
|
|
276
|
+
/** @private {function():boolean} */
|
|
277
|
+
this.isPreloadFn_ = () => false;
|
|
270
278
|
}
|
|
271
279
|
|
|
272
280
|
|
|
273
281
|
/**
|
|
282
|
+
* @param {shaka.extern.ManifestConfiguration} config
|
|
283
|
+
* @param {(function():boolean)=} isPreloadFn
|
|
274
284
|
* @override
|
|
275
285
|
* @exportInterface
|
|
276
286
|
*/
|
|
277
|
-
configure(config) {
|
|
287
|
+
configure(config, isPreloadFn) {
|
|
278
288
|
this.config_ = config;
|
|
289
|
+
if (isPreloadFn) {
|
|
290
|
+
this.isPreloadFn_ = isPreloadFn;
|
|
291
|
+
}
|
|
279
292
|
|
|
280
293
|
if (this.contentSteeringManager_) {
|
|
281
294
|
this.contentSteeringManager_.configure(this.config_);
|
|
@@ -291,7 +304,7 @@ shaka.hls.HlsParser = class {
|
|
|
291
304
|
this.playerInterface_ = playerInterface;
|
|
292
305
|
this.lowLatencyMode_ = playerInterface.isLowLatencyMode();
|
|
293
306
|
|
|
294
|
-
const response = await this.requestManifest_([uri]);
|
|
307
|
+
const response = await this.requestManifest_([uri]).promise;
|
|
295
308
|
|
|
296
309
|
// Record the master playlist URI after redirects.
|
|
297
310
|
this.masterPlaylistUri_ = response.uri;
|
|
@@ -456,8 +469,8 @@ shaka.hls.HlsParser = class {
|
|
|
456
469
|
}
|
|
457
470
|
let response;
|
|
458
471
|
try {
|
|
459
|
-
response =
|
|
460
|
-
|
|
472
|
+
response = await this.requestManifest_(
|
|
473
|
+
manifestUris, /* isPlaylist= */ true).promise;
|
|
461
474
|
} catch (e) {
|
|
462
475
|
if (this.playerInterface_) {
|
|
463
476
|
this.playerInterface_.disableStream(streamInfo.stream);
|
|
@@ -926,9 +939,10 @@ shaka.hls.HlsParser = class {
|
|
|
926
939
|
this.parseCodecs_(variantTags);
|
|
927
940
|
|
|
928
941
|
this.parseClosedCaptions_(mediaTags);
|
|
942
|
+
const iFrameStreams = this.parseIFrames_(iFrameTags);
|
|
929
943
|
variants = await this.createVariantsForTags_(
|
|
930
944
|
variantTags, sessionKeyTags, mediaTags, getUris,
|
|
931
|
-
this.globalVariables_);
|
|
945
|
+
this.globalVariables_, iFrameStreams);
|
|
932
946
|
textStreams = this.parseTexts_(mediaTags);
|
|
933
947
|
imageStreams = await this.parseImages_(imageTags, iFrameTags);
|
|
934
948
|
}
|
|
@@ -941,6 +955,8 @@ shaka.hls.HlsParser = class {
|
|
|
941
955
|
shaka.util.Error.Code.OPERATION_ABORTED);
|
|
942
956
|
}
|
|
943
957
|
|
|
958
|
+
this.determineStartTime_(playlist);
|
|
959
|
+
|
|
944
960
|
// Single-variant streams aren't lazy-loaded, so for them we already have
|
|
945
961
|
// enough info here to determine the presentation type and duration.
|
|
946
962
|
if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
|
|
@@ -975,6 +991,7 @@ shaka.hls.HlsParser = class {
|
|
|
975
991
|
periodCount: 1,
|
|
976
992
|
gapCount: 0,
|
|
977
993
|
isLowLatency: false,
|
|
994
|
+
startTime: this.startTime_,
|
|
978
995
|
};
|
|
979
996
|
|
|
980
997
|
// If there is no 'CODECS' attribute in the manifest and codec guessing is
|
|
@@ -1045,7 +1062,7 @@ shaka.hls.HlsParser = class {
|
|
|
1045
1062
|
const initType =
|
|
1046
1063
|
shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT;
|
|
1047
1064
|
const initResponse = await this.makeNetworkRequest_(
|
|
1048
|
-
initSegmentRequest, requestType, {type: initType});
|
|
1065
|
+
initSegmentRequest, requestType, {type: initType}).promise;
|
|
1049
1066
|
initData = initResponse.data;
|
|
1050
1067
|
if (initSegmentRef.aesKey) {
|
|
1051
1068
|
initData = await shaka.media.SegmentUtils.aesDecrypt(
|
|
@@ -1064,7 +1081,7 @@ shaka.hls.HlsParser = class {
|
|
|
1064
1081
|
this.config_.retryParameters);
|
|
1065
1082
|
const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
|
|
1066
1083
|
const response = await this.makeNetworkRequest_(
|
|
1067
|
-
segmentRequest, requestType, {type});
|
|
1084
|
+
segmentRequest, requestType, {type}).promise;
|
|
1068
1085
|
let data = response.data;
|
|
1069
1086
|
if (segment.aesKey) {
|
|
1070
1087
|
data = await shaka.media.SegmentUtils.aesDecrypt(
|
|
@@ -1137,14 +1154,7 @@ shaka.hls.HlsParser = class {
|
|
|
1137
1154
|
const PresentationType = shaka.hls.HlsParser.PresentationType_;
|
|
1138
1155
|
|
|
1139
1156
|
if (this.presentationType_ == PresentationType.LIVE) {
|
|
1140
|
-
let segmentAvailabilityDuration = this.getLiveDuration_();
|
|
1141
|
-
|
|
1142
|
-
// This defaults to the presentation delay, which has the effect of
|
|
1143
|
-
// making the live stream unseekable. This is consistent with Apple's
|
|
1144
|
-
// HLS implementation.
|
|
1145
|
-
if (this.config_.hls.useSafariBehaviorForLive) {
|
|
1146
|
-
segmentAvailabilityDuration = this.presentationTimeline_.getDelay();
|
|
1147
|
-
}
|
|
1157
|
+
let segmentAvailabilityDuration = this.getLiveDuration_() || 0;
|
|
1148
1158
|
|
|
1149
1159
|
// The app can override that with a longer duration, to allow seeking.
|
|
1150
1160
|
if (!isNaN(this.config_.availabilityWindowOverride)) {
|
|
@@ -1167,7 +1177,8 @@ shaka.hls.HlsParser = class {
|
|
|
1167
1177
|
if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
|
|
1168
1178
|
continue;
|
|
1169
1179
|
}
|
|
1170
|
-
const firstReference =
|
|
1180
|
+
const firstReference =
|
|
1181
|
+
streamInfo.stream.segmentIndex.earliestReference();
|
|
1171
1182
|
if (firstReference && firstReference.syncTime) {
|
|
1172
1183
|
const syncTime = firstReference.syncTime;
|
|
1173
1184
|
this.presentationTimeline_.setInitialProgramDateTime(syncTime);
|
|
@@ -1449,7 +1460,8 @@ shaka.hls.HlsParser = class {
|
|
|
1449
1460
|
}
|
|
1450
1461
|
try {
|
|
1451
1462
|
const streamInfo = this.createStreamInfoFromIframeTag_(tag);
|
|
1452
|
-
|
|
1463
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
1464
|
+
if (streamInfo.stream.type !== ContentType.IMAGE) {
|
|
1453
1465
|
return null;
|
|
1454
1466
|
}
|
|
1455
1467
|
return streamInfo.stream;
|
|
@@ -1494,6 +1506,26 @@ shaka.hls.HlsParser = class {
|
|
|
1494
1506
|
}
|
|
1495
1507
|
}
|
|
1496
1508
|
|
|
1509
|
+
/**
|
|
1510
|
+
* @param {!Array.<!shaka.hls.Tag>} iFrameTags from the playlist.
|
|
1511
|
+
* @return {!Array.<!shaka.extern.Stream>}
|
|
1512
|
+
* @private
|
|
1513
|
+
*/
|
|
1514
|
+
parseIFrames_(iFrameTags) {
|
|
1515
|
+
// Create iFrame stream for each iFrame tag.
|
|
1516
|
+
const iFrameStreams = iFrameTags.map((tag) => {
|
|
1517
|
+
const streamInfo = this.createStreamInfoFromIframeTag_(tag);
|
|
1518
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
1519
|
+
if (streamInfo.stream.type !== ContentType.VIDEO) {
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
return streamInfo.stream;
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
// Filter mjpg iFrames
|
|
1526
|
+
return iFrameStreams.filter((s) => s);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1497
1529
|
/**
|
|
1498
1530
|
* @param {!Array.<!shaka.hls.Tag>} tags Variant tags from the playlist.
|
|
1499
1531
|
* @param {!Array.<!shaka.hls.Tag>} sessionKeyTags EXT-X-SESSION-KEY tags
|
|
@@ -1501,12 +1533,13 @@ shaka.hls.HlsParser = class {
|
|
|
1501
1533
|
* @param {!Array.<!shaka.hls.Tag>} mediaTags EXT-X-MEDIA tags from the
|
|
1502
1534
|
* playlist.
|
|
1503
1535
|
* @param {function():!Array.<string>} getUris
|
|
1504
|
-
* @param {?Map.<string, string
|
|
1536
|
+
* @param {?Map.<string, string>} variables
|
|
1537
|
+
* @param {!Array.<!shaka.extern.Stream>} iFrameStreams
|
|
1505
1538
|
* @return {!Promise.<!Array.<!shaka.extern.Variant>>}
|
|
1506
1539
|
* @private
|
|
1507
1540
|
*/
|
|
1508
1541
|
async createVariantsForTags_(tags, sessionKeyTags, mediaTags, getUris,
|
|
1509
|
-
variables) {
|
|
1542
|
+
variables, iFrameStreams) {
|
|
1510
1543
|
// EXT-X-SESSION-KEY processing
|
|
1511
1544
|
const drmInfos = [];
|
|
1512
1545
|
const keyIds = new Set();
|
|
@@ -1529,7 +1562,8 @@ shaka.hls.HlsParser = class {
|
|
|
1529
1562
|
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
|
|
1530
1563
|
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
1531
1564
|
keyUris, this.config_.retryParameters);
|
|
1532
|
-
const keyResponse = this.makeNetworkRequest_(request, requestType)
|
|
1565
|
+
const keyResponse = this.makeNetworkRequest_(request, requestType)
|
|
1566
|
+
.promise;
|
|
1533
1567
|
this.aesKeyMap_.set(keyMapKey, keyResponse);
|
|
1534
1568
|
}
|
|
1535
1569
|
continue;
|
|
@@ -1615,7 +1649,8 @@ shaka.hls.HlsParser = class {
|
|
|
1615
1649
|
videoRange,
|
|
1616
1650
|
videoLayout,
|
|
1617
1651
|
drmInfos,
|
|
1618
|
-
keyIds
|
|
1652
|
+
keyIds,
|
|
1653
|
+
iFrameStreams));
|
|
1619
1654
|
}
|
|
1620
1655
|
return allVariants.filter((variant) => variant != null);
|
|
1621
1656
|
}
|
|
@@ -1950,12 +1985,13 @@ shaka.hls.HlsParser = class {
|
|
|
1950
1985
|
* @param {?string} videoLayout
|
|
1951
1986
|
* @param {!Array.<shaka.extern.DrmInfo>} drmInfos
|
|
1952
1987
|
* @param {!Set.<string>} keyIds
|
|
1988
|
+
* @param {!Array.<!shaka.extern.Stream>} iFrameStreams
|
|
1953
1989
|
* @return {!Array.<!shaka.extern.Variant>}
|
|
1954
1990
|
* @private
|
|
1955
1991
|
*/
|
|
1956
1992
|
createVariants_(
|
|
1957
1993
|
audioInfos, videoInfos, bandwidth, width, height, frameRate, videoRange,
|
|
1958
|
-
videoLayout, drmInfos, keyIds) {
|
|
1994
|
+
videoLayout, drmInfos, keyIds, iFrameStreams) {
|
|
1959
1995
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
1960
1996
|
const DrmUtils = shaka.util.DrmUtils;
|
|
1961
1997
|
|
|
@@ -1990,6 +2026,8 @@ shaka.hls.HlsParser = class {
|
|
|
1990
2026
|
if (videoStream) {
|
|
1991
2027
|
videoStream.drmInfos = drmInfos;
|
|
1992
2028
|
videoStream.keyIds = keyIds;
|
|
2029
|
+
shaka.util.StreamUtils.setBetterIFrameStream(
|
|
2030
|
+
videoStream, iFrameStreams);
|
|
1993
2031
|
}
|
|
1994
2032
|
if (videoStream && !audioStream) {
|
|
1995
2033
|
videoStream.bandwidth = bandwidth;
|
|
@@ -2226,7 +2264,7 @@ shaka.hls.HlsParser = class {
|
|
|
2226
2264
|
// lazy-load in this situation.
|
|
2227
2265
|
await streamInfo.stream.createSegmentIndex();
|
|
2228
2266
|
|
|
2229
|
-
const reference = streamInfo.stream.segmentIndex.
|
|
2267
|
+
const reference = streamInfo.stream.segmentIndex.earliestReference();
|
|
2230
2268
|
const layout = reference.getTilesLayout();
|
|
2231
2269
|
if (layout) {
|
|
2232
2270
|
streamInfo.stream.width =
|
|
@@ -2257,11 +2295,15 @@ shaka.hls.HlsParser = class {
|
|
|
2257
2295
|
goog.asserts.assert(tag.name == 'EXT-X-I-FRAME-STREAM-INF',
|
|
2258
2296
|
'Should only be called on iframe tags!');
|
|
2259
2297
|
/** @type {string} */
|
|
2260
|
-
|
|
2298
|
+
let type = shaka.util.ManifestParserUtils.ContentType.VIDEO;
|
|
2261
2299
|
|
|
2262
2300
|
const verbatimIFramePlaylistUri = tag.getRequiredAttrValue('URI');
|
|
2263
2301
|
const codecs = tag.getAttributeValue('CODECS') || '';
|
|
2264
2302
|
|
|
2303
|
+
if (codecs == 'mjpg') {
|
|
2304
|
+
type = shaka.util.ManifestParserUtils.ContentType.IMAGE;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2265
2307
|
// Check if the stream has already been created as part of another Variant
|
|
2266
2308
|
// and return it if it has.
|
|
2267
2309
|
if (this.uriToStreamInfosMap_.has(verbatimIFramePlaylistUri)) {
|
|
@@ -2398,205 +2440,213 @@ shaka.hls.HlsParser = class {
|
|
|
2398
2440
|
loadedOnce: false,
|
|
2399
2441
|
};
|
|
2400
2442
|
|
|
2401
|
-
/** @param {!
|
|
2402
|
-
const downloadSegmentIndex = async (
|
|
2443
|
+
/** @param {!shaka.net.NetworkingEngine.PendingRequest} pendingRequest */
|
|
2444
|
+
const downloadSegmentIndex = async (pendingRequest) => {
|
|
2403
2445
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2404
2446
|
|
|
2405
|
-
const uris = streamInfo.getUris();
|
|
2406
|
-
// Download the actual manifest.
|
|
2407
|
-
let response;
|
|
2408
2447
|
try {
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
const handled = this.playerInterface_.disableStream(stream);
|
|
2414
|
-
if (handled) {
|
|
2448
|
+
const uris = streamInfo.getUris();
|
|
2449
|
+
// Download the actual manifest.
|
|
2450
|
+
const response = await pendingRequest.promise;
|
|
2451
|
+
if (pendingRequest.aborted) {
|
|
2415
2452
|
return;
|
|
2416
|
-
} else {
|
|
2417
|
-
throw e;
|
|
2418
2453
|
}
|
|
2419
|
-
}
|
|
2420
|
-
if (abortSignal.aborted) {
|
|
2421
|
-
return;
|
|
2422
|
-
}
|
|
2423
2454
|
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2455
|
+
// Record the final URI after redirects.
|
|
2456
|
+
const responseUri = response.uri;
|
|
2457
|
+
if (responseUri != response.originalUri &&
|
|
2458
|
+
!uris.includes(responseUri)) {
|
|
2459
|
+
redirectUris.push(responseUri);
|
|
2460
|
+
}
|
|
2429
2461
|
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2462
|
+
// Record the redirected, final URI of this media playlist when we parse
|
|
2463
|
+
// it.
|
|
2464
|
+
/** @type {!shaka.hls.Playlist} */
|
|
2465
|
+
const playlist = this.manifestTextParser_.parsePlaylist(response.data);
|
|
2434
2466
|
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2467
|
+
if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
|
|
2468
|
+
throw new shaka.util.Error(
|
|
2469
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2470
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2471
|
+
shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
|
|
2472
|
+
}
|
|
2441
2473
|
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2474
|
+
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
2475
|
+
const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
|
|
2476
|
+
'EXT-X-DEFINE');
|
|
2445
2477
|
|
|
2446
|
-
|
|
2447
|
-
|
|
2478
|
+
const mediaVariables =
|
|
2479
|
+
this.parseMediaVariables_(variablesTags, responseUri);
|
|
2448
2480
|
|
|
2449
|
-
|
|
2481
|
+
const mimeType = undefined;
|
|
2450
2482
|
|
|
2451
|
-
|
|
2483
|
+
let requestBasicInfo = false;
|
|
2452
2484
|
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2485
|
+
// If no codec info was provided in the manifest and codec guessing is
|
|
2486
|
+
// disabled we try to get necessary info from the media data.
|
|
2487
|
+
if ((!this.codecInfoInManifest_ &&
|
|
2488
|
+
this.config_.hls.disableCodecGuessing) ||
|
|
2489
|
+
(this.needsClosedCaptionsDetection_ && type == ContentType.VIDEO &&
|
|
2490
|
+
!this.config_.hls.disableClosedCaptionsDetection)) {
|
|
2491
|
+
if (playlist.segments.length > 0) {
|
|
2492
|
+
this.needsClosedCaptionsDetection_ = false;
|
|
2493
|
+
requestBasicInfo = true;
|
|
2494
|
+
}
|
|
2462
2495
|
}
|
|
2463
|
-
}
|
|
2464
2496
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2497
|
+
const allowOverrideMimeType = !this.codecInfoInManifest_ &&
|
|
2498
|
+
this.config_.hls.disableCodecGuessing;
|
|
2467
2499
|
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2500
|
+
const wasLive = this.isLive_();
|
|
2501
|
+
const realStreamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
|
|
2502
|
+
streamId, mediaVariables, playlist, getUris, responseUri, codecs,
|
|
2503
|
+
type, languageValue, primary, name, channelsCount, closedCaptions,
|
|
2504
|
+
characteristics, forced, sampleRate, spatialAudio, mimeType,
|
|
2505
|
+
requestBasicInfo, allowOverrideMimeType);
|
|
2506
|
+
if (pendingRequest.aborted) {
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2477
2509
|
|
|
2478
|
-
|
|
2510
|
+
const realStream = realStreamInfo.stream;
|
|
2479
2511
|
|
|
2480
|
-
|
|
2481
|
-
// Now that we know that the presentation is live, convert the timeline
|
|
2482
|
-
// to live.
|
|
2483
|
-
this.changePresentationTimelineToLive_(playlist);
|
|
2484
|
-
}
|
|
2512
|
+
this.determineStartTime_(playlist);
|
|
2485
2513
|
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
streamInfo.canBlockReload = realStreamInfo.canBlockReload;
|
|
2491
|
-
streamInfo.hasEndList = realStreamInfo.hasEndList;
|
|
2492
|
-
streamInfo.mediaSequenceToStartTime =
|
|
2493
|
-
realStreamInfo.mediaSequenceToStartTime;
|
|
2494
|
-
streamInfo.nextMediaSequence = realStreamInfo.nextMediaSequence;
|
|
2495
|
-
streamInfo.nextPart = realStreamInfo.nextPart;
|
|
2496
|
-
streamInfo.loadedOnce = true;
|
|
2497
|
-
stream.segmentIndex = realStream.segmentIndex;
|
|
2498
|
-
stream.encrypted = realStream.encrypted;
|
|
2499
|
-
stream.drmInfos = realStream.drmInfos;
|
|
2500
|
-
stream.keyIds = realStream.keyIds;
|
|
2501
|
-
stream.mimeType = realStream.mimeType;
|
|
2502
|
-
stream.bandwidth = stream.bandwidth || realStream.bandwidth;
|
|
2503
|
-
stream.codecs = stream.codecs || realStream.codecs;
|
|
2504
|
-
stream.closedCaptions =
|
|
2505
|
-
stream.closedCaptions || realStream.closedCaptions;
|
|
2506
|
-
stream.width = stream.width || realStream.width;
|
|
2507
|
-
stream.height = stream.height || realStream.height;
|
|
2508
|
-
stream.hdr = stream.hdr || realStream.hdr;
|
|
2509
|
-
stream.colorGamut = stream.colorGamut || realStream.colorGamut;
|
|
2510
|
-
if (stream.language == 'und' && realStream.language != 'und') {
|
|
2511
|
-
stream.language = realStream.language;
|
|
2512
|
-
}
|
|
2513
|
-
stream.language = stream.language || realStream.language;
|
|
2514
|
-
stream.channelsCount = stream.channelsCount || realStream.channelsCount;
|
|
2515
|
-
stream.audioSamplingRate =
|
|
2516
|
-
stream.audioSamplingRate || realStream.audioSamplingRate;
|
|
2517
|
-
this.setFullTypeForStream_(stream);
|
|
2518
|
-
|
|
2519
|
-
// Since we lazy-loaded this content, the player may need to create new
|
|
2520
|
-
// sessions for the DRM info in this stream.
|
|
2521
|
-
if (stream.drmInfos.length) {
|
|
2522
|
-
this.playerInterface_.newDrmInfo(stream);
|
|
2523
|
-
}
|
|
2524
|
-
|
|
2525
|
-
let closedCaptionsUpdated = false;
|
|
2526
|
-
if ((!closedCaptions && stream.closedCaptions) ||
|
|
2527
|
-
(closedCaptions && stream.closedCaptions &&
|
|
2528
|
-
closedCaptions.size != stream.closedCaptions.size)) {
|
|
2529
|
-
closedCaptionsUpdated = true;
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
|
-
if (this.manifest_ && closedCaptionsUpdated) {
|
|
2533
|
-
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
if (type == ContentType.VIDEO || type == ContentType.AUDIO) {
|
|
2537
|
-
for (const otherStreamInfo of this.uriToStreamInfosMap_.values()) {
|
|
2538
|
-
if (!otherStreamInfo.loadedOnce && otherStreamInfo.type == type) {
|
|
2539
|
-
// To aid manifest filtering, assume before loading that all video
|
|
2540
|
-
// renditions have the same MIME type. (And likewise for audio.)
|
|
2541
|
-
otherStreamInfo.stream.mimeType = realStream.mimeType;
|
|
2542
|
-
this.setFullTypeForStream_(otherStreamInfo.stream);
|
|
2543
|
-
}
|
|
2514
|
+
if (this.isLive_() && !wasLive) {
|
|
2515
|
+
// Now that we know that the presentation is live, convert the
|
|
2516
|
+
// timeline to live.
|
|
2517
|
+
this.changePresentationTimelineToLive_(playlist);
|
|
2544
2518
|
}
|
|
2545
|
-
}
|
|
2546
2519
|
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2520
|
+
// Copy values from the real stream info to our initial one.
|
|
2521
|
+
streamInfo.minTimestamp = realStreamInfo.minTimestamp;
|
|
2522
|
+
streamInfo.maxTimestamp = realStreamInfo.maxTimestamp;
|
|
2523
|
+
streamInfo.canSkipSegments = realStreamInfo.canSkipSegments;
|
|
2524
|
+
streamInfo.canBlockReload = realStreamInfo.canBlockReload;
|
|
2525
|
+
streamInfo.hasEndList = realStreamInfo.hasEndList;
|
|
2526
|
+
streamInfo.mediaSequenceToStartTime =
|
|
2527
|
+
realStreamInfo.mediaSequenceToStartTime;
|
|
2528
|
+
streamInfo.nextMediaSequence = realStreamInfo.nextMediaSequence;
|
|
2529
|
+
streamInfo.nextPart = realStreamInfo.nextPart;
|
|
2530
|
+
streamInfo.loadedOnce = true;
|
|
2531
|
+
stream.segmentIndex = realStream.segmentIndex;
|
|
2532
|
+
stream.encrypted = realStream.encrypted;
|
|
2533
|
+
stream.drmInfos = realStream.drmInfos;
|
|
2534
|
+
stream.keyIds = realStream.keyIds;
|
|
2535
|
+
stream.mimeType = realStream.mimeType;
|
|
2536
|
+
stream.bandwidth = stream.bandwidth || realStream.bandwidth;
|
|
2537
|
+
stream.codecs = stream.codecs || realStream.codecs;
|
|
2538
|
+
stream.closedCaptions =
|
|
2539
|
+
stream.closedCaptions || realStream.closedCaptions;
|
|
2540
|
+
stream.width = stream.width || realStream.width;
|
|
2541
|
+
stream.height = stream.height || realStream.height;
|
|
2542
|
+
stream.hdr = stream.hdr || realStream.hdr;
|
|
2543
|
+
stream.colorGamut = stream.colorGamut || realStream.colorGamut;
|
|
2544
|
+
stream.frameRate = stream.frameRate || realStream.frameRate;
|
|
2545
|
+
if (stream.language == 'und' && realStream.language != 'und') {
|
|
2546
|
+
stream.language = realStream.language;
|
|
2547
|
+
}
|
|
2548
|
+
stream.language = stream.language || realStream.language;
|
|
2549
|
+
stream.channelsCount = stream.channelsCount || realStream.channelsCount;
|
|
2550
|
+
stream.audioSamplingRate =
|
|
2551
|
+
stream.audioSamplingRate || realStream.audioSamplingRate;
|
|
2552
|
+
this.setFullTypeForStream_(stream);
|
|
2553
|
+
|
|
2554
|
+
// Since we lazy-loaded this content, the player may need to create new
|
|
2555
|
+
// sessions for the DRM info in this stream.
|
|
2556
|
+
if (stream.drmInfos.length) {
|
|
2557
|
+
this.playerInterface_.newDrmInfo(stream);
|
|
2552
2558
|
}
|
|
2553
|
-
}
|
|
2554
2559
|
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
+
let closedCaptionsUpdated = false;
|
|
2561
|
+
if ((!closedCaptions && stream.closedCaptions) ||
|
|
2562
|
+
(closedCaptions && stream.closedCaptions &&
|
|
2563
|
+
closedCaptions.size != stream.closedCaptions.size)) {
|
|
2564
|
+
closedCaptionsUpdated = true;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
if (this.manifest_ && closedCaptionsUpdated) {
|
|
2568
|
+
this.playerInterface_.makeTextStreamsForClosedCaptions(
|
|
2569
|
+
this.manifest_);
|
|
2560
2570
|
}
|
|
2561
|
-
});
|
|
2562
2571
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
|
|
2572
|
-
const activeStreamInfos =
|
|
2573
|
-
streamInfos.filter((s) => s.stream.segmentIndex);
|
|
2574
|
-
this.finalizeStreams_(activeStreamInfos);
|
|
2575
|
-
// With the addition of this new stream, we now have enough info to
|
|
2576
|
-
// figure out how long the streams should be. So process all streams
|
|
2577
|
-
// we have downloaded up until this point.
|
|
2578
|
-
this.determineDuration_();
|
|
2579
|
-
// Finally, start the update timer, if this asset has been determined
|
|
2580
|
-
// to be a livestream.
|
|
2581
|
-
const delay = this.getUpdatePlaylistDelay_();
|
|
2582
|
-
if (delay > 0) {
|
|
2583
|
-
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
|
|
2572
|
+
if (type == ContentType.VIDEO || type == ContentType.AUDIO) {
|
|
2573
|
+
for (const otherStreamInfo of this.uriToStreamInfosMap_.values()) {
|
|
2574
|
+
if (!otherStreamInfo.loadedOnce && otherStreamInfo.type == type) {
|
|
2575
|
+
// To aid manifest filtering, assume before loading that all video
|
|
2576
|
+
// renditions have the same MIME type. (And likewise for audio.)
|
|
2577
|
+
otherStreamInfo.stream.mimeType = realStream.mimeType;
|
|
2578
|
+
this.setFullTypeForStream_(otherStreamInfo.stream);
|
|
2579
|
+
}
|
|
2584
2580
|
}
|
|
2585
|
-
} else {
|
|
2586
|
-
// We don't need to go through the full process; just finalize this
|
|
2587
|
-
// single stream.
|
|
2588
|
-
this.finalizeStreams_([streamInfo]);
|
|
2589
2581
|
}
|
|
2590
|
-
}
|
|
2591
2582
|
|
|
2592
|
-
|
|
2593
|
-
|
|
2583
|
+
if (type == ContentType.TEXT) {
|
|
2584
|
+
const firstSegment = realStream.segmentIndex.earliestReference();
|
|
2585
|
+
if (firstSegment && firstSegment.initSegmentReference) {
|
|
2586
|
+
stream.mimeType = 'application/mp4';
|
|
2587
|
+
this.setFullTypeForStream_(stream);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
const qualityInfo =
|
|
2592
|
+
shaka.media.QualityObserver.createQualityInfo(stream);
|
|
2593
|
+
stream.segmentIndex.forEachTopLevelReference((reference) => {
|
|
2594
|
+
if (reference.initSegmentReference) {
|
|
2595
|
+
reference.initSegmentReference.mediaQuality = qualityInfo;
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
|
|
2599
|
+
// Add finishing touches to the stream that can only be done once we
|
|
2600
|
+
// have more full context on the media as a whole.
|
|
2601
|
+
if (this.hasEnoughInfoToFinalizeStreams_()) {
|
|
2602
|
+
if (!this.streamsFinalized_) {
|
|
2603
|
+
// Mark this manifest as having been finalized, so we don't go
|
|
2604
|
+
// through this whole process of finishing touches a second time.
|
|
2605
|
+
this.streamsFinalized_ = true;
|
|
2606
|
+
// Finalize all of the currently-loaded streams.
|
|
2607
|
+
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
|
|
2608
|
+
const activeStreamInfos =
|
|
2609
|
+
streamInfos.filter((s) => s.stream.segmentIndex);
|
|
2610
|
+
this.finalizeStreams_(activeStreamInfos);
|
|
2611
|
+
// With the addition of this new stream, we now have enough info to
|
|
2612
|
+
// figure out how long the streams should be. So process all streams
|
|
2613
|
+
// we have downloaded up until this point.
|
|
2614
|
+
this.determineDuration_();
|
|
2615
|
+
// Finally, start the update timer, if this asset has been
|
|
2616
|
+
// determined to be a livestream.
|
|
2617
|
+
const delay = this.getUpdatePlaylistDelay_();
|
|
2618
|
+
if (delay > 0) {
|
|
2619
|
+
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
|
|
2620
|
+
}
|
|
2621
|
+
} else {
|
|
2622
|
+
// We don't need to go through the full process; just finalize this
|
|
2623
|
+
// single stream.
|
|
2624
|
+
this.finalizeStreams_([streamInfo]);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
this.processDateRangeTags_(
|
|
2629
|
+
playlist.tags, stream.type, mediaVariables, getUris);
|
|
2630
|
+
|
|
2631
|
+
if (this.manifest_) {
|
|
2632
|
+
this.manifest_.startTime = this.startTime_;
|
|
2633
|
+
}
|
|
2634
|
+
} catch (e) {
|
|
2635
|
+
stream.closeSegmentIndex();
|
|
2636
|
+
if (e.code === shaka.util.Error.Code.OPERATION_ABORTED) {
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
const handled = this.playerInterface_.disableStream(stream);
|
|
2640
|
+
if (!handled) {
|
|
2641
|
+
throw e;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2594
2644
|
};
|
|
2595
2645
|
|
|
2596
2646
|
/** @type {Promise} */
|
|
2597
2647
|
let creationPromise = null;
|
|
2598
|
-
/** @type {!
|
|
2599
|
-
let
|
|
2648
|
+
/** @type {!shaka.net.NetworkingEngine.PendingRequest} */
|
|
2649
|
+
let pendingRequest;
|
|
2600
2650
|
const safeCreateSegmentIndex = () => {
|
|
2601
2651
|
// An operation is already in progress. The second and subsequent
|
|
2602
2652
|
// callers receive the same Promise as the first caller, and only one
|
|
@@ -2605,15 +2655,16 @@ shaka.hls.HlsParser = class {
|
|
|
2605
2655
|
return creationPromise;
|
|
2606
2656
|
}
|
|
2607
2657
|
|
|
2608
|
-
// Create a new
|
|
2658
|
+
// Create a new PendingRequest to be able to cancel this specific
|
|
2609
2659
|
// download.
|
|
2610
|
-
|
|
2660
|
+
pendingRequest = this.requestManifest_(streamInfo.getUris(),
|
|
2661
|
+
/* isPlaylist= */ true);
|
|
2611
2662
|
|
|
2612
2663
|
// Create a Promise tied to the outcome of downloadSegmentIndex(). If
|
|
2613
2664
|
// downloadSegmentIndex is rejected, creationPromise will also be
|
|
2614
2665
|
// rejected.
|
|
2615
2666
|
creationPromise = new Promise((resolve) => {
|
|
2616
|
-
resolve(downloadSegmentIndex(
|
|
2667
|
+
resolve(downloadSegmentIndex(pendingRequest));
|
|
2617
2668
|
});
|
|
2618
2669
|
return creationPromise;
|
|
2619
2670
|
};
|
|
@@ -2623,7 +2674,7 @@ shaka.hls.HlsParser = class {
|
|
|
2623
2674
|
stream.closeSegmentIndex = () => {
|
|
2624
2675
|
// If we're mid-creation, cancel it.
|
|
2625
2676
|
if (creationPromise && !stream.segmentIndex) {
|
|
2626
|
-
|
|
2677
|
+
pendingRequest.abort();
|
|
2627
2678
|
}
|
|
2628
2679
|
// If we have a segment index, release it.
|
|
2629
2680
|
if (stream.segmentIndex) {
|
|
@@ -2781,8 +2832,20 @@ shaka.hls.HlsParser = class {
|
|
|
2781
2832
|
channelsCount, closedCaptions, characteristics, forced, sampleRate,
|
|
2782
2833
|
spatialAudio, mimeType = undefined, requestBasicInfo = true,
|
|
2783
2834
|
allowOverrideMimeType = true) {
|
|
2784
|
-
|
|
2785
|
-
|
|
2835
|
+
const playlistSegments = playlist.segments || [];
|
|
2836
|
+
const allAreMissing = playlistSegments.every((seg) => {
|
|
2837
|
+
if (shaka.hls.Utils.getFirstTagWithName(seg.tags, 'EXT-X-GAP')) {
|
|
2838
|
+
return true;
|
|
2839
|
+
}
|
|
2840
|
+
return false;
|
|
2841
|
+
});
|
|
2842
|
+
|
|
2843
|
+
if (!playlistSegments.length || allAreMissing) {
|
|
2844
|
+
throw new shaka.util.Error(
|
|
2845
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2846
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2847
|
+
shaka.util.Error.Code.HLS_EMPTY_MEDIA_PLAYLIST);
|
|
2848
|
+
}
|
|
2786
2849
|
|
|
2787
2850
|
this.determinePresentationType_(playlist);
|
|
2788
2851
|
|
|
@@ -2800,6 +2863,7 @@ shaka.hls.HlsParser = class {
|
|
|
2800
2863
|
let height = null;
|
|
2801
2864
|
let videoRange = null;
|
|
2802
2865
|
let colorGamut = null;
|
|
2866
|
+
let frameRate = null;
|
|
2803
2867
|
if (segments.length > 0 && requestBasicInfo) {
|
|
2804
2868
|
const basicInfo = await this.getBasicInfoFromSegments_(segments);
|
|
2805
2869
|
|
|
@@ -2815,6 +2879,7 @@ shaka.hls.HlsParser = class {
|
|
|
2815
2879
|
width = basicInfo.width;
|
|
2816
2880
|
videoRange = basicInfo.videoRange;
|
|
2817
2881
|
colorGamut = basicInfo.colorGamut;
|
|
2882
|
+
frameRate = basicInfo.frameRate;
|
|
2818
2883
|
|
|
2819
2884
|
if (allowOverrideMimeType) {
|
|
2820
2885
|
mimeType = basicInfo.mimeType;
|
|
@@ -2851,8 +2916,7 @@ shaka.hls.HlsParser = class {
|
|
|
2851
2916
|
if (type == shaka.util.ManifestParserUtils.ContentType.VIDEO &&
|
|
2852
2917
|
(width || height || videoRange || colorGamut)) {
|
|
2853
2918
|
this.addVideoAttributes_(stream, width, height,
|
|
2854
|
-
|
|
2855
|
-
colorGamut);
|
|
2919
|
+
frameRate, videoRange, /* videoLayout= */ null, colorGamut);
|
|
2856
2920
|
}
|
|
2857
2921
|
|
|
2858
2922
|
// This new calculation is necessary for Low Latency streams.
|
|
@@ -3202,7 +3266,8 @@ shaka.hls.HlsParser = class {
|
|
|
3202
3266
|
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
|
|
3203
3267
|
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
3204
3268
|
keyUris, this.config_.retryParameters);
|
|
3205
|
-
const keyResponse = this.makeNetworkRequest_(request, requestType)
|
|
3269
|
+
const keyResponse = this.makeNetworkRequest_(request, requestType)
|
|
3270
|
+
.promise;
|
|
3206
3271
|
this.aesKeyMap_.set(keyMapKey, keyResponse);
|
|
3207
3272
|
}
|
|
3208
3273
|
const keyResponse = await this.aesKeyMap_.get(keyMapKey);
|
|
@@ -3230,6 +3295,24 @@ shaka.hls.HlsParser = class {
|
|
|
3230
3295
|
}
|
|
3231
3296
|
|
|
3232
3297
|
|
|
3298
|
+
/**
|
|
3299
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
3300
|
+
* @private
|
|
3301
|
+
*/
|
|
3302
|
+
determineStartTime_(playlist) {
|
|
3303
|
+
// If we already have a starttime we avoid processing this again.
|
|
3304
|
+
if (this.startTime_ != null) {
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
const startTimeTag =
|
|
3308
|
+
shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-START');
|
|
3309
|
+
if (startTimeTag) {
|
|
3310
|
+
this.startTime_ =
|
|
3311
|
+
Number(startTimeTag.getRequiredAttrValue('TIME-OFFSET'));
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
|
|
3233
3316
|
/**
|
|
3234
3317
|
* @param {!shaka.hls.Playlist} playlist
|
|
3235
3318
|
* @private
|
|
@@ -3359,7 +3442,7 @@ shaka.hls.HlsParser = class {
|
|
|
3359
3442
|
// We accomplish this in our DASH-y model by setting a presentation
|
|
3360
3443
|
// delay of configured value, or 3 segments duration if not configured.
|
|
3361
3444
|
// This will be the "live edge" of the presentation.
|
|
3362
|
-
let presentationDelay;
|
|
3445
|
+
let presentationDelay = 0;
|
|
3363
3446
|
if (this.config_.defaultPresentationDelay) {
|
|
3364
3447
|
presentationDelay = this.config_.defaultPresentationDelay;
|
|
3365
3448
|
} else if (this.lowLatencyPresentationDelay_) {
|
|
@@ -3367,12 +3450,27 @@ shaka.hls.HlsParser = class {
|
|
|
3367
3450
|
} else if (this.presentationDelay_) {
|
|
3368
3451
|
presentationDelay = this.presentationDelay_;
|
|
3369
3452
|
} else {
|
|
3370
|
-
const
|
|
3453
|
+
const totalSegments = playlist.segments.length;
|
|
3371
3454
|
let delaySegments = this.config_.hls.liveSegmentsDelay;
|
|
3372
|
-
if (delaySegments > (
|
|
3373
|
-
delaySegments = Math.max(1,
|
|
3455
|
+
if (delaySegments > (totalSegments - 2)) {
|
|
3456
|
+
delaySegments = Math.max(1, totalSegments - 2);
|
|
3457
|
+
}
|
|
3458
|
+
for (let i = totalSegments - delaySegments; i < totalSegments; i++) {
|
|
3459
|
+
const extinfTag = shaka.hls.Utils.getFirstTagWithName(
|
|
3460
|
+
playlist.segments[i].tags, 'EXTINF');
|
|
3461
|
+
if (extinfTag) {
|
|
3462
|
+
const extinfValues = extinfTag.value.split(',');
|
|
3463
|
+
const duration = Number(extinfValues[0]);
|
|
3464
|
+
presentationDelay += Math.ceil(duration);
|
|
3465
|
+
} else {
|
|
3466
|
+
presentationDelay += this.maxTargetDuration_;
|
|
3467
|
+
}
|
|
3374
3468
|
}
|
|
3375
|
-
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
if (this.startTime_ && this.startTime_ < 0) {
|
|
3472
|
+
presentationDelay = Math.min(-this.startTime_, presentationDelay);
|
|
3473
|
+
this.startTime_ += presentationDelay;
|
|
3376
3474
|
}
|
|
3377
3475
|
|
|
3378
3476
|
this.presentationTimeline_.setPresentationStartTime(0);
|
|
@@ -3937,7 +4035,6 @@ shaka.hls.HlsParser = class {
|
|
|
3937
4035
|
// Exclude these attributes from the metadata since they already go into
|
|
3938
4036
|
// other fields (eg: startTime or endTime) or are not necessary..
|
|
3939
4037
|
const excludedAttributes = [
|
|
3940
|
-
'ID',
|
|
3941
4038
|
'CLASS',
|
|
3942
4039
|
'START-DATE',
|
|
3943
4040
|
'END-DATE',
|
|
@@ -3967,7 +4064,8 @@ shaka.hls.HlsParser = class {
|
|
|
3967
4064
|
values.push(metadataFrame);
|
|
3968
4065
|
}
|
|
3969
4066
|
|
|
3970
|
-
|
|
4067
|
+
// ID is always required. So we need more than 1 value.
|
|
4068
|
+
if (values.length > 1) {
|
|
3971
4069
|
this.playerInterface_.onMetadata(type, startTime, endTime, values);
|
|
3972
4070
|
}
|
|
3973
4071
|
|
|
@@ -4021,7 +4119,8 @@ shaka.hls.HlsParser = class {
|
|
|
4021
4119
|
if (firstStartTime === 0 &&
|
|
4022
4120
|
this.presentationType_ == shaka.hls.HlsParser.PresentationType_.LIVE &&
|
|
4023
4121
|
mediaSequenceToStartTime.size > 0 &&
|
|
4024
|
-
!mediaSequenceToStartTime.has(position)
|
|
4122
|
+
!mediaSequenceToStartTime.has(position) &&
|
|
4123
|
+
this.presentationTimeline_.getPresentationStartTime() != null) {
|
|
4025
4124
|
firstStartTime = this.presentationTimeline_.getSegmentAvailabilityStart();
|
|
4026
4125
|
}
|
|
4027
4126
|
|
|
@@ -4351,7 +4450,7 @@ shaka.hls.HlsParser = class {
|
|
|
4351
4450
|
try {
|
|
4352
4451
|
headRequest.method = 'HEAD';
|
|
4353
4452
|
const response = await this.makeNetworkRequest_(
|
|
4354
|
-
headRequest, requestType, {type});
|
|
4453
|
+
headRequest, requestType, {type}).promise;
|
|
4355
4454
|
|
|
4356
4455
|
contentMimeType = response.headers['content-type'];
|
|
4357
4456
|
} catch (error) {
|
|
@@ -4360,7 +4459,7 @@ shaka.hls.HlsParser = class {
|
|
|
4360
4459
|
error.code == shaka.util.Error.Code.BAD_HTTP_STATUS)) {
|
|
4361
4460
|
headRequest.method = 'GET';
|
|
4362
4461
|
const response = await this.makeNetworkRequest_(
|
|
4363
|
-
headRequest, requestType, {type});
|
|
4462
|
+
headRequest, requestType, {type}).promise;
|
|
4364
4463
|
|
|
4365
4464
|
contentMimeType = response.headers['content-type'];
|
|
4366
4465
|
}
|
|
@@ -4423,7 +4522,7 @@ shaka.hls.HlsParser = class {
|
|
|
4423
4522
|
*
|
|
4424
4523
|
* @param {!Array.<string>} uris
|
|
4425
4524
|
* @param {boolean=} isPlaylist
|
|
4426
|
-
* @return {!
|
|
4525
|
+
* @return {!shaka.net.NetworkingEngine.PendingRequest}
|
|
4427
4526
|
* @private
|
|
4428
4527
|
*/
|
|
4429
4528
|
requestManifest_(uris, isPlaylist) {
|
|
@@ -4556,7 +4655,7 @@ shaka.hls.HlsParser = class {
|
|
|
4556
4655
|
* @param {shaka.extern.Request} request
|
|
4557
4656
|
* @param {shaka.net.NetworkingEngine.RequestType} type
|
|
4558
4657
|
* @param {shaka.extern.RequestContext=} context
|
|
4559
|
-
* @return {!
|
|
4658
|
+
* @return {!shaka.net.NetworkingEngine.PendingRequest}
|
|
4560
4659
|
* @private
|
|
4561
4660
|
*/
|
|
4562
4661
|
makeNetworkRequest_(request, type, context) {
|
|
@@ -4567,11 +4666,15 @@ shaka.hls.HlsParser = class {
|
|
|
4567
4666
|
shaka.util.Error.Code.OPERATION_ABORTED);
|
|
4568
4667
|
}
|
|
4569
4668
|
|
|
4669
|
+
if (!context) {
|
|
4670
|
+
context = {};
|
|
4671
|
+
}
|
|
4672
|
+
context.isPreload = this.isPreloadFn_();
|
|
4570
4673
|
const op = this.playerInterface_.networkingEngine.request(
|
|
4571
4674
|
type, request, context);
|
|
4572
4675
|
this.operationManager_.manage(op);
|
|
4573
4676
|
|
|
4574
|
-
return op
|
|
4677
|
+
return op;
|
|
4575
4678
|
}
|
|
4576
4679
|
|
|
4577
4680
|
/**
|
|
@@ -4769,7 +4872,8 @@ shaka.hls.HlsParser = class {
|
|
|
4769
4872
|
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
|
|
4770
4873
|
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
4771
4874
|
keyUris, this.config_.retryParameters);
|
|
4772
|
-
const keyResponse = this.makeNetworkRequest_(request, requestType)
|
|
4875
|
+
const keyResponse = this.makeNetworkRequest_(request, requestType)
|
|
4876
|
+
.promise;
|
|
4773
4877
|
this.identityKeyMap_.set(keyMapKey, keyResponse);
|
|
4774
4878
|
}
|
|
4775
4879
|
const keyResponse = await this.identityKeyMap_.get(keyMapKey);
|
|
@@ -4801,7 +4905,7 @@ shaka.hls.HlsParser = class {
|
|
|
4801
4905
|
const initType =
|
|
4802
4906
|
shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT;
|
|
4803
4907
|
const initResponse = await this.makeNetworkRequest_(
|
|
4804
|
-
initSegmentRequest, requestType, {type: initType});
|
|
4908
|
+
initSegmentRequest, requestType, {type: initType}).promise;
|
|
4805
4909
|
|
|
4806
4910
|
defaultKID = shaka.media.SegmentUtils.getDefaultKID(
|
|
4807
4911
|
initResponse.data);
|