shaka-player 3.3.5 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/config.yml +1 -0
- package/.github/workflows/demo-version-index.yaml +8 -0
- package/.github/workflows/release-please.yaml +18 -2
- package/.github/workflows/sync-labels.yaml +33 -0
- package/.github/workflows/update-issues.yaml +29 -0
- package/.github/workflows/validate-pr-title.yaml +35 -0
- package/AUTHORS +1 -0
- package/CHANGELOG.md +215 -41
- package/CONTRIBUTORS +1 -0
- package/README.md +14 -11
- package/app-engine/demo-version-index/README.md +3 -3
- package/app-engine/demo-version-index/app.yaml +27 -1
- package/app-engine/demo-version-index/generate.py +164 -0
- package/app-engine/demo-version-index/main.py +4 -155
- package/app-engine/demo-version-index/requirements.txt +0 -1
- package/build/checkversion.py +1 -1
- package/build/compiler.py +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shaka-lab.yaml +0 -2
- package/build/types/polyfill +0 -3
- package/demo/common/asset.js +17 -1
- package/demo/common/assets.js +29 -0
- package/demo/common/message_ids.js +6 -3
- package/demo/config.js +10 -11
- package/demo/custom.js +39 -0
- package/demo/demo.less +0 -1
- package/demo/demo_utils.js +0 -1
- package/demo/locales/en.json +6 -3
- package/demo/locales/source.json +22 -10
- package/demo/main.js +9 -6
- package/demo/search.js +2 -1
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +71 -64
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +71 -64
- package/dist/demo.compiled.map +3 -3
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +17 -20
- package/dist/locales.js +33 -31
- package/dist/receiver.compiled.debug.js +32 -28
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +32 -28
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +311 -314
- package/dist/shaka-player.compiled.debug.d.ts +311 -314
- package/dist/shaka-player.compiled.debug.externs.js +126 -126
- package/dist/shaka-player.compiled.debug.js +430 -484
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +126 -126
- package/dist/shaka-player.compiled.js +829 -845
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +357 -358
- package/dist/shaka-player.ui.debug.d.ts +357 -358
- package/dist/shaka-player.ui.debug.externs.js +134 -126
- package/dist/shaka-player.ui.debug.js +489 -541
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +134 -126
- package/dist/shaka-player.ui.js +988 -1000
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/config.md +0 -2
- package/docs/tutorials/drm-config.md +5 -0
- package/docs/tutorials/fairplay.md +7 -22
- package/docs/tutorials/faq.md +0 -11
- package/docs/tutorials/index.json +2 -8
- package/docs/tutorials/plugins.md +1 -3
- package/docs/tutorials/upgrade.md +85 -11
- package/externs/polyfill.js +15 -0
- package/externs/shaka/manifest.js +5 -1
- package/externs/shaka/net.js +12 -0
- package/externs/shaka/offline.js +5 -1
- package/externs/shaka/player.js +39 -21
- package/externs/shaka/text.js +12 -12
- package/externs/sourcebuffer.js +15 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- package/lib/ads/client_side_ad.js +13 -2
- package/lib/ads/client_side_ad_manager.js +22 -4
- package/lib/cast/cast_utils.js +0 -1
- package/lib/dash/content_protection.js +85 -5
- package/lib/dash/dash_parser.js +1 -0
- package/lib/hls/hls_parser.js +390 -747
- package/lib/hls/manifest_text_parser.js +2 -3
- package/lib/media/drm_engine.js +105 -37
- package/lib/media/gap_jumping_controller.js +10 -45
- package/lib/media/media_source_engine.js +150 -30
- package/lib/media/playhead.js +2 -5
- package/lib/media/presentation_timeline.js +1 -2
- package/lib/media/segment_index.js +13 -46
- package/lib/media/segment_reference.js +47 -1
- package/lib/media/streaming_engine.js +28 -11
- package/lib/media/time_ranges_utils.js +4 -9
- package/lib/net/networking_engine.js +3 -0
- package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/offline/storage.js +8 -41
- package/lib/player.js +17 -201
- package/lib/polyfill/media_capabilities.js +12 -6
- package/lib/polyfill/mediasource.js +0 -28
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
- package/lib/text/cue.js +0 -6
- package/lib/text/lrc_text_parser.js +8 -0
- package/lib/text/mp4_ttml_parser.js +8 -0
- package/lib/text/mp4_vtt_parser.js +8 -0
- package/lib/text/sbv_text_parser.js +8 -0
- package/lib/text/simple_text_displayer.js +1 -7
- package/lib/text/srt_text_parser.js +8 -0
- package/lib/text/ssa_text_parser.js +8 -0
- package/lib/text/text_engine.js +19 -3
- package/lib/text/ttml_text_parser.js +8 -0
- package/lib/text/ui_text_displayer.js +1 -7
- package/lib/text/vtt_text_parser.js +27 -5
- package/lib/text/web_vtt_generator.js +1 -7
- package/lib/util/error.js +9 -13
- package/lib/util/fairplay_utils.js +109 -0
- package/lib/util/functional.js +0 -42
- package/lib/util/mime_utils.js +16 -5
- package/lib/util/platform.js +45 -0
- package/lib/util/player_configuration.js +17 -6
- package/lib/util/pssh.js +23 -4
- package/lib/util/stream_utils.js +92 -22
- package/lib/util/uint8array_utils.js +0 -16
- package/lib/util/xml_utils.js +1 -1
- package/package.json +5 -2
- package/roadmap.md +19 -19
- package/shaka-player.uncompiled.js +0 -3
- package/test/dash/dash_parser_content_protection_unit.js +44 -0
- package/test/dash/dash_parser_segment_template_unit.js +3 -4
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +155 -230
- package/test/hls/hls_parser_unit.js +515 -432
- package/test/media/drm_engine_unit.js +86 -120
- package/test/media/media_source_engine_integration.js +25 -3
- package/test/media/media_source_engine_unit.js +3 -2
- package/test/media/playhead_unit.js +27 -191
- package/test/media/segment_index_unit.js +15 -25
- package/test/media/streaming_engine_integration.js +2 -39
- package/test/media/streaming_engine_unit.js +3 -2
- package/test/media/time_ranges_utils_unit.js +0 -5
- package/test/offline/manifest_convert_unit.js +34 -4
- package/test/offline/offline_manifest_parser_unit.js +1 -0
- package/test/offline/storage_integration.js +5 -4
- package/test/player_external.js +0 -6
- package/test/player_integration.js +16 -0
- package/test/player_unit.js +26 -0
- package/test/test/util/fake_media_source_engine.js +11 -4
- package/test/test/util/manifest_generator.js +3 -1
- package/test/test/util/manifest_parser_util.js +4 -1
- package/test/test/util/offline_utils.js +1 -0
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/test/util/test_scheme.js +24 -1
- package/test/test/util/util.js +4 -4
- package/test/text/cue_integration.js +2 -2
- package/test/text/lrc_text_parser_unit.js +6 -7
- package/test/text/mp4_ttml_parser_unit.js +6 -4
- package/test/text/mp4_vtt_parser_unit.js +6 -5
- package/test/text/sbv_text_parser_unit.js +5 -6
- package/test/text/srt_text_parser_unit.js +5 -6
- package/test/text/ssa_text_parser_unit.js +17 -29
- package/test/text/text_engine_unit.js +45 -5
- package/test/text/ttml_text_parser_unit.js +77 -77
- package/test/text/vtt_text_parser_unit.js +116 -59
- package/test/ui/ui_integration.js +0 -16
- package/test/ui/ui_unit.js +27 -0
- package/test/util/cmcd_manager_unit.js +3 -0
- package/test/util/stream_utils_unit.js +23 -7
- package/ui/controls.js +65 -40
- package/ui/fullscreen_button.js +26 -6
- package/ui/less/containers.less +4 -4
- package/ui/less/general.less +1 -1
- package/ui/less/range_elements.less +1 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/source.json +5 -0
- package/ui/resolution_selection.js +31 -33
- package/ui/ui.js +0 -17
- package/.github/pull_request_template.md +0 -10
- package/docs/tutorials/upgrade-v2-5.md +0 -441
- package/docs/tutorials/upgrade-v3.md +0 -38
- package/lib/polyfill/mathround.js +0 -49
- package/lib/polyfill/patchedmediakeys_apple.js +0 -744
- package/lib/polyfill/patchedmediakeys_ms.js +0 -698
- package/test/util/functional_unit.js +0 -82
package/lib/hls/hls_parser.js
CHANGED
|
@@ -18,6 +18,7 @@ goog.require('shaka.log');
|
|
|
18
18
|
goog.require('shaka.media.DrmEngine');
|
|
19
19
|
goog.require('shaka.media.InitSegmentReference');
|
|
20
20
|
goog.require('shaka.media.ManifestParser');
|
|
21
|
+
goog.require('shaka.media.MediaSourceEngine');
|
|
21
22
|
goog.require('shaka.media.PresentationTimeline');
|
|
22
23
|
goog.require('shaka.media.SegmentIndex');
|
|
23
24
|
goog.require('shaka.media.SegmentReference');
|
|
@@ -26,19 +27,17 @@ goog.require('shaka.net.NetworkingEngine');
|
|
|
26
27
|
goog.require('shaka.util.ArrayUtils');
|
|
27
28
|
goog.require('shaka.util.BufferUtils');
|
|
28
29
|
goog.require('shaka.util.CmcdManager');
|
|
29
|
-
goog.require('shaka.util.DataViewReader');
|
|
30
30
|
goog.require('shaka.util.Error');
|
|
31
31
|
goog.require('shaka.util.FakeEvent');
|
|
32
32
|
goog.require('shaka.util.Functional');
|
|
33
33
|
goog.require('shaka.util.LanguageUtils');
|
|
34
34
|
goog.require('shaka.util.ManifestParserUtils');
|
|
35
35
|
goog.require('shaka.util.MimeUtils');
|
|
36
|
-
goog.require('shaka.util.Mp4Parser');
|
|
37
|
-
goog.require('shaka.util.Mp4BoxParsers');
|
|
38
|
-
goog.require('shaka.util.Networking');
|
|
39
36
|
goog.require('shaka.util.OperationManager');
|
|
40
37
|
goog.require('shaka.util.Pssh');
|
|
41
38
|
goog.require('shaka.util.Timer');
|
|
39
|
+
goog.require('shaka.util.Platform');
|
|
40
|
+
goog.require('shaka.util.XmlUtils');
|
|
42
41
|
goog.requireType('shaka.hls.Segment');
|
|
43
42
|
|
|
44
43
|
|
|
@@ -95,9 +94,6 @@ shaka.hls.HlsParser = class {
|
|
|
95
94
|
* createStreamInfoFromMediaTag_, createStreamInfoFromImageTag_ and
|
|
96
95
|
* createStreamInfoFromVariantTag_.
|
|
97
96
|
*
|
|
98
|
-
* During parsing of updates, used by getStartTime_ to determine the start
|
|
99
|
-
* time of the first segment from existing segment references.
|
|
100
|
-
*
|
|
101
97
|
* @private {!Map.<string, shaka.hls.HlsParser.StreamInfo>}
|
|
102
98
|
*/
|
|
103
99
|
this.uriToStreamInfosMap_ = new Map();
|
|
@@ -124,6 +120,15 @@ shaka.hls.HlsParser = class {
|
|
|
124
120
|
*/
|
|
125
121
|
this.updatePlaylistDelay_ = 0;
|
|
126
122
|
|
|
123
|
+
/**
|
|
124
|
+
* A time offset to apply to EXT-X-PROGRAM-DATE-TIME values to normalize
|
|
125
|
+
* them so that they start at 0. This is necessary because these times will
|
|
126
|
+
* be used to set presentation times for segments.
|
|
127
|
+
* null means we don't have enough data yet.
|
|
128
|
+
* @private {?number}
|
|
129
|
+
*/
|
|
130
|
+
this.syncTimeOffset_ = null;
|
|
131
|
+
|
|
127
132
|
/**
|
|
128
133
|
* This timer is used to trigger the start of a manifest update. A manifest
|
|
129
134
|
* update is async. Once the update is finished, the timer will be restarted
|
|
@@ -175,9 +180,6 @@ shaka.hls.HlsParser = class {
|
|
|
175
180
|
/** @private {Map.<string, string>} */
|
|
176
181
|
this.groupIdToCodecsMap_ = new Map();
|
|
177
182
|
|
|
178
|
-
/** @private {?number} */
|
|
179
|
-
this.playlistStartTime_ = null;
|
|
180
|
-
|
|
181
183
|
/** A cache mapping EXT-X-MAP tag info to the InitSegmentReference created
|
|
182
184
|
* from the tag.
|
|
183
185
|
* The key is a string combining the EXT-X-MAP tag's absolute uri, and
|
|
@@ -185,15 +187,6 @@ shaka.hls.HlsParser = class {
|
|
|
185
187
|
* {!Map.<string, !shaka.media.InitSegmentReference>} */
|
|
186
188
|
this.mapTagToInitSegmentRefMap_ = new Map();
|
|
187
189
|
|
|
188
|
-
/**
|
|
189
|
-
* A cache mapping a discontinuity sequence number of a segment with
|
|
190
|
-
* EXT-X-DISCONTINUITY tag into its timestamp offset.
|
|
191
|
-
* Key: the discontinuity sequence number of a segment
|
|
192
|
-
* Value: the segment reference's timestamp offset.
|
|
193
|
-
* {!Map.<number, number>}
|
|
194
|
-
*/
|
|
195
|
-
this.discontinuityToTso_ = new Map();
|
|
196
|
-
|
|
197
190
|
/** @private {boolean} */
|
|
198
191
|
this.lowLatencyMode_ = false;
|
|
199
192
|
}
|
|
@@ -222,7 +215,7 @@ shaka.hls.HlsParser = class {
|
|
|
222
215
|
this.masterPlaylistUri_ = response.uri;
|
|
223
216
|
|
|
224
217
|
goog.asserts.assert(response.data, 'Response data should be non-null!');
|
|
225
|
-
await this.parseManifest_(response.data);
|
|
218
|
+
await this.parseManifest_(response.data, uri);
|
|
226
219
|
|
|
227
220
|
// Start the update timer if we want updates.
|
|
228
221
|
const delay = this.updatePlaylistDelay_;
|
|
@@ -277,8 +270,6 @@ shaka.hls.HlsParser = class {
|
|
|
277
270
|
|
|
278
271
|
/** @type {!Array.<!Promise>} */
|
|
279
272
|
const updates = [];
|
|
280
|
-
// Reset the start time for the new media playlist.
|
|
281
|
-
this.playlistStartTime_ = null;
|
|
282
273
|
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
|
|
283
274
|
// Wait for the first stream info created, so that the start time is fetched
|
|
284
275
|
// and can be reused.
|
|
@@ -329,11 +320,10 @@ shaka.hls.HlsParser = class {
|
|
|
329
320
|
|
|
330
321
|
const stream = streamInfo.stream;
|
|
331
322
|
|
|
332
|
-
const segments =
|
|
323
|
+
const segments = this.createSegments_(
|
|
333
324
|
streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
|
|
334
325
|
stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
|
|
335
|
-
|
|
336
|
-
stream.bandwidth);
|
|
326
|
+
stream.codecs);
|
|
337
327
|
|
|
338
328
|
stream.segmentIndex.mergeAndEvict(
|
|
339
329
|
segments, this.presentationTimeline_.getSegmentAvailabilityStart());
|
|
@@ -370,14 +360,51 @@ shaka.hls.HlsParser = class {
|
|
|
370
360
|
// No-op
|
|
371
361
|
}
|
|
372
362
|
|
|
363
|
+
/**
|
|
364
|
+
* If necessary, makes sure that sync times will be normalized to 0, so that
|
|
365
|
+
* a stream does not start buffering at 50 years in because sync times are
|
|
366
|
+
* measured in time since 1970.
|
|
367
|
+
* @private
|
|
368
|
+
*/
|
|
369
|
+
calculateSyncTimeOffset_() {
|
|
370
|
+
if (this.syncTimeOffset_ != null) {
|
|
371
|
+
// The offset was already calculated.
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const segments = new Set();
|
|
376
|
+
let lowestSyncTime = Infinity;
|
|
377
|
+
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
378
|
+
const segmentIndex = streamInfo.stream.segmentIndex;
|
|
379
|
+
if (segmentIndex) {
|
|
380
|
+
segmentIndex.forEachTopLevelReference((segment) => {
|
|
381
|
+
if (segment.syncTime != null) {
|
|
382
|
+
lowestSyncTime = Math.min(lowestSyncTime, segment.syncTime);
|
|
383
|
+
segments.add(segment);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (segments.size > 0) {
|
|
389
|
+
this.syncTimeOffset_ = -lowestSyncTime;
|
|
390
|
+
for (const segment of segments) {
|
|
391
|
+
segment.syncTime += this.syncTimeOffset_;
|
|
392
|
+
for (const partial of segment.partialReferences) {
|
|
393
|
+
partial.syncTime += this.syncTimeOffset_;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
373
399
|
/**
|
|
374
400
|
* Parses the manifest.
|
|
375
401
|
*
|
|
376
402
|
* @param {BufferSource} data
|
|
403
|
+
* @param {string} uri
|
|
377
404
|
* @return {!Promise}
|
|
378
405
|
* @private
|
|
379
406
|
*/
|
|
380
|
-
async parseManifest_(data) {
|
|
407
|
+
async parseManifest_(data, uri) {
|
|
381
408
|
const Utils = shaka.hls.Utils;
|
|
382
409
|
|
|
383
410
|
goog.asserts.assert(this.masterPlaylistUri_,
|
|
@@ -386,63 +413,106 @@ shaka.hls.HlsParser = class {
|
|
|
386
413
|
const playlist = this.manifestTextParser_.parsePlaylist(
|
|
387
414
|
data, this.masterPlaylistUri_);
|
|
388
415
|
|
|
389
|
-
// We don't support directly providing a Media Playlist.
|
|
390
|
-
// See the error code for details.
|
|
391
|
-
if (playlist.type != shaka.hls.PlaylistType.MASTER) {
|
|
392
|
-
throw new shaka.util.Error(
|
|
393
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
394
|
-
shaka.util.Error.Category.MANIFEST,
|
|
395
|
-
shaka.util.Error.Code.HLS_MASTER_PLAYLIST_NOT_PROVIDED);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
416
|
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
399
417
|
const variablesTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-DEFINE');
|
|
400
418
|
|
|
401
419
|
this.parseMasterVariables_(variablesTags);
|
|
402
420
|
|
|
403
|
-
/** @type {!Array.<!shaka.
|
|
404
|
-
|
|
405
|
-
/** @type {!Array.<!shaka.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
421
|
+
/** @type {!Array.<!shaka.extern.Variant>} */
|
|
422
|
+
let variants = [];
|
|
423
|
+
/** @type {!Array.<!shaka.extern.Stream>} */
|
|
424
|
+
let textStreams = [];
|
|
425
|
+
/** @type {!Array.<!shaka.extern.Stream>} */
|
|
426
|
+
let imageStreams = [];
|
|
427
|
+
|
|
428
|
+
// Parsing a media playlist results in a single-variant stream.
|
|
429
|
+
if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
|
|
430
|
+
// Get necessary info for this stream, from the config. These are things
|
|
431
|
+
// we would normally find from the master playlist (e.g. from values on
|
|
432
|
+
// EXT-X-MEDIA tags).
|
|
433
|
+
const fullMimeType = this.config_.hls.mediaPlaylistFullMimeType;
|
|
434
|
+
const mimeType = shaka.util.MimeUtils.getBasicType(fullMimeType);
|
|
435
|
+
const type = mimeType.split('/')[0];
|
|
436
|
+
const codecs = shaka.util.MimeUtils.getCodecs(fullMimeType);
|
|
437
|
+
|
|
438
|
+
// Some values we cannot figure out, and aren't important enough to ask
|
|
439
|
+
// the user to provide through config values. A lot of these are only
|
|
440
|
+
// relevant to ABR, which isn't necessary if there's only one variant.
|
|
441
|
+
// So these unknowns should be set to false or null, largely.
|
|
442
|
+
const language = '';
|
|
443
|
+
const channelsCount = null;
|
|
444
|
+
const spatialAudio = false;
|
|
445
|
+
const characteristics = null;
|
|
446
|
+
const closedCaptions = new Map();
|
|
447
|
+
const forced = false; // Only relevant for text.
|
|
448
|
+
const primary = true; // This is the only stream!
|
|
449
|
+
const name = 'Media Playlist';
|
|
450
|
+
|
|
451
|
+
// Make the stream info, with those values.
|
|
452
|
+
const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
|
|
453
|
+
playlist, uri, uri, codecs, type, language, primary, name,
|
|
454
|
+
channelsCount, closedCaptions, characteristics, forced, spatialAudio,
|
|
455
|
+
mimeType);
|
|
456
|
+
goog.asserts.assert(streamInfo != null, 'StreamInfo is null!');
|
|
457
|
+
this.uriToStreamInfosMap_.set(uri, streamInfo);
|
|
458
|
+
|
|
459
|
+
// Wrap the stream from that stream info with a variant.
|
|
460
|
+
variants.push({
|
|
461
|
+
id: 0,
|
|
462
|
+
language: 'und',
|
|
463
|
+
primary: true,
|
|
464
|
+
audio: type == 'audio' ? streamInfo.stream : null,
|
|
465
|
+
video: type == 'video' ? streamInfo.stream : null,
|
|
466
|
+
bandwidth: 0,
|
|
467
|
+
allowedByApplication: true,
|
|
468
|
+
allowedByKeySystem: true,
|
|
469
|
+
decodingInfos: [],
|
|
470
|
+
});
|
|
471
|
+
} else {
|
|
472
|
+
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
473
|
+
const mediaTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-MEDIA');
|
|
474
|
+
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
475
|
+
const variantTags = Utils.filterTagsByName(
|
|
476
|
+
playlist.tags, 'EXT-X-STREAM-INF');
|
|
477
|
+
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
478
|
+
const imageTags = Utils.filterTagsByName(
|
|
479
|
+
playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
|
|
480
|
+
|
|
481
|
+
this.parseCodecs_(variantTags);
|
|
482
|
+
|
|
483
|
+
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
484
|
+
const sesionDataTags =
|
|
485
|
+
Utils.filterTagsByName(playlist.tags, 'EXT-X-SESSION-DATA');
|
|
486
|
+
for (const tag of sesionDataTags) {
|
|
487
|
+
const id = tag.getAttributeValue('DATA-ID');
|
|
488
|
+
const uri = tag.getAttributeValue('URI');
|
|
489
|
+
const language = tag.getAttributeValue('LANGUAGE');
|
|
490
|
+
const value = tag.getAttributeValue('VALUE');
|
|
491
|
+
const data = (new Map()).set('id', id);
|
|
492
|
+
if (uri) {
|
|
493
|
+
data.set('uri', shaka.hls.Utils.constructAbsoluteUri(
|
|
494
|
+
this.masterPlaylistUri_, uri));
|
|
495
|
+
}
|
|
496
|
+
if (language) {
|
|
497
|
+
data.set('language', language);
|
|
498
|
+
}
|
|
499
|
+
if (value) {
|
|
500
|
+
data.set('value', value);
|
|
501
|
+
}
|
|
502
|
+
const event = new shaka.util.FakeEvent('sessiondata', data);
|
|
503
|
+
if (this.playerInterface_) {
|
|
504
|
+
this.playerInterface_.onEvent(event);
|
|
505
|
+
}
|
|
436
506
|
}
|
|
437
|
-
}
|
|
438
507
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
508
|
+
// Parse audio and video media tags first, so that we can extract segment
|
|
509
|
+
// start time from audio/video streams and reuse for text streams.
|
|
510
|
+
await this.createStreamInfosFromMediaTags_(mediaTags);
|
|
511
|
+
this.parseClosedCaptions_(mediaTags);
|
|
512
|
+
variants = await this.createVariantsForTags_(variantTags);
|
|
513
|
+
textStreams = await this.parseTexts_(mediaTags);
|
|
514
|
+
imageStreams = await this.parseImages_(imageTags);
|
|
515
|
+
}
|
|
446
516
|
|
|
447
517
|
// Make sure that the parser has not been destroyed.
|
|
448
518
|
if (!this.playerInterface_) {
|
|
@@ -452,6 +522,10 @@ shaka.hls.HlsParser = class {
|
|
|
452
522
|
shaka.util.Error.Code.OPERATION_ABORTED);
|
|
453
523
|
}
|
|
454
524
|
|
|
525
|
+
// Now that we have generated all streams, we can determine the offset to
|
|
526
|
+
// apply to sync times.
|
|
527
|
+
this.calculateSyncTimeOffset_();
|
|
528
|
+
|
|
455
529
|
if (this.aesEncrypted_ && variants.length == 0) {
|
|
456
530
|
// We do not support AES-128 encryption with HLS yet. Variants is null
|
|
457
531
|
// when the playlist is encrypted with AES-128.
|
|
@@ -564,6 +638,7 @@ shaka.hls.HlsParser = class {
|
|
|
564
638
|
imageStreams,
|
|
565
639
|
offlineSessionIds: [],
|
|
566
640
|
minBufferTime: 0,
|
|
641
|
+
sequenceMode: true,
|
|
567
642
|
};
|
|
568
643
|
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
|
|
569
644
|
}
|
|
@@ -764,7 +839,7 @@ shaka.hls.HlsParser = class {
|
|
|
764
839
|
const videoRange = tag.getAttributeValue('VIDEO-RANGE');
|
|
765
840
|
|
|
766
841
|
const streamInfos = await this.createStreamInfosForVariantTag_(tag,
|
|
767
|
-
resolution, frameRate
|
|
842
|
+
resolution, frameRate);
|
|
768
843
|
|
|
769
844
|
if (streamInfos) {
|
|
770
845
|
goog.asserts.assert(streamInfos.audio.length ||
|
|
@@ -798,11 +873,10 @@ shaka.hls.HlsParser = class {
|
|
|
798
873
|
* @param {!shaka.hls.Tag} tag
|
|
799
874
|
* @param {?string} resolution
|
|
800
875
|
* @param {?string} frameRate
|
|
801
|
-
* @param {number} bandwidth
|
|
802
876
|
* @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
|
|
803
877
|
* @private
|
|
804
878
|
*/
|
|
805
|
-
async createStreamInfosForVariantTag_(tag, resolution, frameRate
|
|
879
|
+
async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
|
|
806
880
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
807
881
|
/** @type {!Array.<string>} */
|
|
808
882
|
let allCodecs = this.getCodecsForVariantTag_(tag);
|
|
@@ -872,8 +946,7 @@ shaka.hls.HlsParser = class {
|
|
|
872
946
|
let streamInfo;
|
|
873
947
|
if (!ignoreStream) {
|
|
874
948
|
streamInfo =
|
|
875
|
-
await this.createStreamInfoFromVariantTag_(tag, allCodecs, type
|
|
876
|
-
bandwidth);
|
|
949
|
+
await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
|
|
877
950
|
}
|
|
878
951
|
if (streamInfo) {
|
|
879
952
|
res[streamInfo.stream.type] = [streamInfo];
|
|
@@ -1314,11 +1387,10 @@ shaka.hls.HlsParser = class {
|
|
|
1314
1387
|
* @param {!shaka.hls.Tag} tag
|
|
1315
1388
|
* @param {!Array.<string>} allCodecs
|
|
1316
1389
|
* @param {string} type
|
|
1317
|
-
* @param {number} bandwidth
|
|
1318
1390
|
* @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
|
|
1319
1391
|
* @private
|
|
1320
1392
|
*/
|
|
1321
|
-
async createStreamInfoFromVariantTag_(tag, allCodecs, type
|
|
1393
|
+
async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
|
|
1322
1394
|
goog.asserts.assert(tag.name == 'EXT-X-STREAM-INF',
|
|
1323
1395
|
'Should only be called on variant tags!');
|
|
1324
1396
|
const verbatimMediaPlaylistUri = this.variableSubstitution_(
|
|
@@ -1334,7 +1406,7 @@ shaka.hls.HlsParser = class {
|
|
|
1334
1406
|
codecs, type, /* language= */ 'und', /* primary= */ false,
|
|
1335
1407
|
/* name= */ null, /* channelcount= */ null, closedCaptions,
|
|
1336
1408
|
/* characteristics= */ null, /* forced= */ false,
|
|
1337
|
-
/* spatialAudio= */ false
|
|
1409
|
+
/* spatialAudio= */ false);
|
|
1338
1410
|
if (streamInfo == null) {
|
|
1339
1411
|
return null;
|
|
1340
1412
|
}
|
|
@@ -1361,13 +1433,12 @@ shaka.hls.HlsParser = class {
|
|
|
1361
1433
|
* @param {?string} characteristics
|
|
1362
1434
|
* @param {boolean} forced
|
|
1363
1435
|
* @param {boolean} spatialAudio
|
|
1364
|
-
* @param {(number|undefined)} bandwidth
|
|
1365
1436
|
* @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
|
|
1366
1437
|
* @private
|
|
1367
1438
|
*/
|
|
1368
1439
|
async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
|
|
1369
1440
|
primary, name, channelsCount, closedCaptions, characteristics, forced,
|
|
1370
|
-
spatialAudio
|
|
1441
|
+
spatialAudio) {
|
|
1371
1442
|
// TODO: Refactor, too many parameters
|
|
1372
1443
|
let absoluteMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
1373
1444
|
this.masterPlaylistUri_, verbatimMediaPlaylistUri);
|
|
@@ -1381,6 +1452,34 @@ shaka.hls.HlsParser = class {
|
|
|
1381
1452
|
const playlist = this.manifestTextParser_.parsePlaylist(
|
|
1382
1453
|
response.data, absoluteMediaPlaylistUri);
|
|
1383
1454
|
|
|
1455
|
+
return this.convertParsedPlaylistIntoStreamInfo_(playlist,
|
|
1456
|
+
verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs, type,
|
|
1457
|
+
language, primary, name, channelsCount, closedCaptions, characteristics,
|
|
1458
|
+
forced, spatialAudio);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
1463
|
+
* @param {string} verbatimMediaPlaylistUri
|
|
1464
|
+
* @param {string} absoluteMediaPlaylistUri
|
|
1465
|
+
* @param {string} codecs
|
|
1466
|
+
* @param {string} type
|
|
1467
|
+
* @param {string} language
|
|
1468
|
+
* @param {boolean} primary
|
|
1469
|
+
* @param {?string} name
|
|
1470
|
+
* @param {?number} channelsCount
|
|
1471
|
+
* @param {Map.<string, string>} closedCaptions
|
|
1472
|
+
* @param {?string} characteristics
|
|
1473
|
+
* @param {boolean} forced
|
|
1474
|
+
* @param {boolean} spatialAudio
|
|
1475
|
+
* @param {(string|undefined)} mimeType
|
|
1476
|
+
* @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
|
|
1477
|
+
* @private
|
|
1478
|
+
*/
|
|
1479
|
+
async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
|
|
1480
|
+
absoluteMediaPlaylistUri, codecs, type, language, primary, name,
|
|
1481
|
+
channelsCount, closedCaptions, characteristics, forced, spatialAudio,
|
|
1482
|
+
mimeType = undefined) {
|
|
1384
1483
|
if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
|
|
1385
1484
|
// EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
|
|
1386
1485
|
// playlists.
|
|
@@ -1390,6 +1489,22 @@ shaka.hls.HlsParser = class {
|
|
|
1390
1489
|
shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
|
|
1391
1490
|
}
|
|
1392
1491
|
|
|
1492
|
+
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
1493
|
+
const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
|
|
1494
|
+
'EXT-X-DEFINE');
|
|
1495
|
+
|
|
1496
|
+
const mediaVariables = this.parseMediaVariables_(variablesTags);
|
|
1497
|
+
|
|
1498
|
+
goog.asserts.assert(playlist.segments != null,
|
|
1499
|
+
'Media playlist should have segments!');
|
|
1500
|
+
|
|
1501
|
+
this.determinePresentationType_(playlist);
|
|
1502
|
+
|
|
1503
|
+
if (!mimeType) {
|
|
1504
|
+
mimeType = await this.guessMimeType_(type, codecs, playlist,
|
|
1505
|
+
mediaVariables);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1393
1508
|
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
1394
1509
|
const drmTags = [];
|
|
1395
1510
|
if (playlist.segments) {
|
|
@@ -1424,7 +1539,7 @@ shaka.hls.HlsParser = class {
|
|
|
1424
1539
|
const drmParser =
|
|
1425
1540
|
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
|
1426
1541
|
|
|
1427
|
-
const drmInfo = drmParser ? drmParser(drmTag) : null;
|
|
1542
|
+
const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
|
|
1428
1543
|
if (drmInfo) {
|
|
1429
1544
|
if (drmInfo.keyIds) {
|
|
1430
1545
|
for (const keyId of drmInfo.keyIds) {
|
|
@@ -1445,47 +1560,19 @@ shaka.hls.HlsParser = class {
|
|
|
1445
1560
|
shaka.util.Error.Code.HLS_KEYFORMATS_NOT_SUPPORTED);
|
|
1446
1561
|
}
|
|
1447
1562
|
|
|
1448
|
-
/** @type {!Array.<!shaka.hls.Tag>} */
|
|
1449
|
-
const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
|
|
1450
|
-
'EXT-X-DEFINE');
|
|
1451
|
-
|
|
1452
|
-
const mediaVariables = this.parseMediaVariables_(variablesTags);
|
|
1453
|
-
|
|
1454
|
-
goog.asserts.assert(playlist.segments != null,
|
|
1455
|
-
'Media playlist should have segments!');
|
|
1456
|
-
|
|
1457
|
-
this.determinePresentationType_(playlist);
|
|
1458
|
-
|
|
1459
|
-
/** @type {string} */
|
|
1460
|
-
const mimeType = await this.guessMimeType_(type, codecs, playlist,
|
|
1461
|
-
mediaVariables);
|
|
1462
|
-
|
|
1463
1563
|
// MediaSource expects no codec strings combined with raw formats.
|
|
1464
|
-
|
|
1465
|
-
if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
|
|
1564
|
+
if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(mimeType)) {
|
|
1466
1565
|
codecs = '';
|
|
1467
1566
|
}
|
|
1468
1567
|
|
|
1469
1568
|
/** @type {!Map.<number, number>} */
|
|
1470
1569
|
const mediaSequenceToStartTime = new Map();
|
|
1471
1570
|
|
|
1472
|
-
/**
|
|
1473
|
-
* A map of a discontinuity sequence number, to the first segment's media
|
|
1474
|
-
* sequence number with the discontinuity sequence number.
|
|
1475
|
-
* Key: the discontinuity sequence number of a few segments
|
|
1476
|
-
* Value: the first segment's media sequence number of the segments with
|
|
1477
|
-
* this discontinuity sequence number.
|
|
1478
|
-
* Used to get the discontinuity sequence number with playlist delta
|
|
1479
|
-
* updates with lowLatencyMode enabled.
|
|
1480
|
-
* {!Map.<number, number>}
|
|
1481
|
-
*/
|
|
1482
|
-
const discontinuityToMediaSequence = new Map();
|
|
1483
|
-
|
|
1484
1571
|
let segments;
|
|
1485
1572
|
try {
|
|
1486
|
-
segments =
|
|
1573
|
+
segments = this.createSegments_(verbatimMediaPlaylistUri,
|
|
1487
1574
|
playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
|
|
1488
|
-
|
|
1575
|
+
codecs);
|
|
1489
1576
|
} catch (error) {
|
|
1490
1577
|
if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
|
|
1491
1578
|
shaka.log.alwaysWarn('Skipping unsupported HLS stream',
|
|
@@ -1557,7 +1644,6 @@ shaka.hls.HlsParser = class {
|
|
|
1557
1644
|
minTimestamp,
|
|
1558
1645
|
maxTimestamp: lastEndTime,
|
|
1559
1646
|
mediaSequenceToStartTime,
|
|
1560
|
-
discontinuityToMediaSequence,
|
|
1561
1647
|
canSkipSegments,
|
|
1562
1648
|
};
|
|
1563
1649
|
}
|
|
@@ -1736,7 +1822,6 @@ shaka.hls.HlsParser = class {
|
|
|
1736
1822
|
* @param {shaka.media.SegmentReference} previousReference
|
|
1737
1823
|
* @param {!shaka.hls.Segment} hlsSegment
|
|
1738
1824
|
* @param {number} startTime
|
|
1739
|
-
* @param {number} timestampOffset
|
|
1740
1825
|
* @param {!Map.<string, string>} variables
|
|
1741
1826
|
* @param {string} absoluteMediaPlaylistUri
|
|
1742
1827
|
* @param {string} type
|
|
@@ -1745,7 +1830,7 @@ shaka.hls.HlsParser = class {
|
|
|
1745
1830
|
*/
|
|
1746
1831
|
createSegmentReference_(
|
|
1747
1832
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
1748
|
-
|
|
1833
|
+
variables, absoluteMediaPlaylistUri, type) {
|
|
1749
1834
|
const tags = hlsSegment.tags;
|
|
1750
1835
|
const absoluteSegmentUri = this.variableSubstitution_(
|
|
1751
1836
|
hlsSegment.absoluteUri, variables);
|
|
@@ -1756,6 +1841,35 @@ shaka.hls.HlsParser = class {
|
|
|
1756
1841
|
let startByte = 0;
|
|
1757
1842
|
let endByte = null;
|
|
1758
1843
|
|
|
1844
|
+
if (hlsSegment.partialSegments.length && !this.lowLatencyMode_) {
|
|
1845
|
+
shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
|
|
1846
|
+
'low-latency streaming mode is not enabled in Shaka ' +
|
|
1847
|
+
'Player. Set streaming.lowLatencyMode configuration to ' +
|
|
1848
|
+
'true, and see https://bit.ly/3clctcj for details.');
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
let syncTime = null;
|
|
1852
|
+
if (!this.config_.hls.ignoreManifestProgramDateTime) {
|
|
1853
|
+
const dateTimeTag =
|
|
1854
|
+
shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
|
|
1855
|
+
if (dateTimeTag && dateTimeTag.value) {
|
|
1856
|
+
const time = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
|
|
1857
|
+
goog.asserts.assert(time != null,
|
|
1858
|
+
'EXT-X-PROGRAM-DATE-TIME format not valid');
|
|
1859
|
+
// Sync time offset is null on the first go-through. This indicates that
|
|
1860
|
+
// we have not yet seen every stream, and thus do not yet have enough
|
|
1861
|
+
// information to determine how to normalize the sync times.
|
|
1862
|
+
// For that first go-through, the sync time will be applied after the
|
|
1863
|
+
// references are all created. Until then, just offset by 0.
|
|
1864
|
+
syncTime = time + (this.syncTimeOffset_ || 0);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
let status = shaka.media.SegmentReference.Status.AVAILABLE;
|
|
1869
|
+
if (shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-GAP')) {
|
|
1870
|
+
status = shaka.media.SegmentReference.Status.MISSING;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1759
1873
|
if (!extinfTag) {
|
|
1760
1874
|
if (hlsSegment.partialSegments.length == 0) {
|
|
1761
1875
|
// EXTINF tag must be available if the segment has no partial segments.
|
|
@@ -1778,9 +1892,20 @@ shaka.hls.HlsParser = class {
|
|
|
1778
1892
|
const pPreviousReference = i == 0 ?
|
|
1779
1893
|
previousReference : partialSegmentRefs[partialSegmentRefs.length - 1];
|
|
1780
1894
|
const pStartTime = (i == 0) ? startTime : pPreviousReference.endTime;
|
|
1781
|
-
|
|
1782
|
-
//
|
|
1783
|
-
//
|
|
1895
|
+
|
|
1896
|
+
// If DURATION is missing from this partial segment, use the target
|
|
1897
|
+
// partial duration from the top of the playlist, which is a required
|
|
1898
|
+
// attribute for content with partial segments.
|
|
1899
|
+
const pDuration = Number(item.getAttributeValue('DURATION')) ||
|
|
1900
|
+
this.partialTargetDuration_;
|
|
1901
|
+
|
|
1902
|
+
// If for some reason we have neither an explicit duration, nor a target
|
|
1903
|
+
// partial duration, we should SKIP this partial segment to avoid
|
|
1904
|
+
// duplicating content in the presentation timeline.
|
|
1905
|
+
if (!pDuration) {
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1784
1909
|
const pEndTime = pStartTime + pDuration;
|
|
1785
1910
|
|
|
1786
1911
|
let pStartByte = 0;
|
|
@@ -1808,7 +1933,7 @@ shaka.hls.HlsParser = class {
|
|
|
1808
1933
|
pStartByte,
|
|
1809
1934
|
pEndByte,
|
|
1810
1935
|
initSegmentReference,
|
|
1811
|
-
timestampOffset,
|
|
1936
|
+
/* timestampOffset= */ 0, // This value is ignored in sequence mode.
|
|
1812
1937
|
/* appendWindowStart= */ 0,
|
|
1813
1938
|
/* appendWindowEnd= */ Infinity);
|
|
1814
1939
|
partialSegmentRefs.push(partial);
|
|
@@ -1869,12 +1994,14 @@ shaka.hls.HlsParser = class {
|
|
|
1869
1994
|
startByte,
|
|
1870
1995
|
endByte,
|
|
1871
1996
|
initSegmentReference,
|
|
1872
|
-
timestampOffset,
|
|
1997
|
+
/* timestampOffset= */ 0, // This value is ignored in sequence mode.
|
|
1873
1998
|
/* appendWindowStart= */ 0,
|
|
1874
1999
|
/* appendWindowEnd= */ Infinity,
|
|
1875
2000
|
partialSegmentRefs,
|
|
1876
2001
|
tilesLayout,
|
|
1877
2002
|
tileDuration,
|
|
2003
|
+
syncTime,
|
|
2004
|
+
status,
|
|
1878
2005
|
);
|
|
1879
2006
|
}
|
|
1880
2007
|
|
|
@@ -1928,15 +2055,12 @@ shaka.hls.HlsParser = class {
|
|
|
1928
2055
|
* @param {string} mimeType
|
|
1929
2056
|
* @param {!Map.<number, number>} mediaSequenceToStartTime
|
|
1930
2057
|
* @param {!Map.<string, string>} variables
|
|
1931
|
-
* @param {!Map.<number, number>} discontinuityToMediaSequence
|
|
1932
2058
|
* @param {string} codecs
|
|
1933
|
-
* @
|
|
1934
|
-
* @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
|
|
2059
|
+
* @return {!Array.<!shaka.media.SegmentReference>}
|
|
1935
2060
|
* @private
|
|
1936
2061
|
*/
|
|
1937
|
-
|
|
1938
|
-
mediaSequenceToStartTime, variables,
|
|
1939
|
-
codecs, bandwidth) {
|
|
2062
|
+
createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
|
|
2063
|
+
mediaSequenceToStartTime, variables, codecs) {
|
|
1940
2064
|
/** @type {Array.<!shaka.hls.Segment>} */
|
|
1941
2065
|
const hlsSegments = playlist.segments;
|
|
1942
2066
|
goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
|
|
@@ -1953,69 +2077,26 @@ shaka.hls.HlsParser = class {
|
|
|
1953
2077
|
const skippedSegments =
|
|
1954
2078
|
skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
|
|
1955
2079
|
let position = mediaSequenceNumber + skippedSegments;
|
|
1956
|
-
let firstStartTime;
|
|
2080
|
+
let firstStartTime = 0;
|
|
2081
|
+
|
|
1957
2082
|
// For live stream, use the cached value in the mediaSequenceToStartTime
|
|
1958
2083
|
// map if available.
|
|
1959
|
-
// Since createSegments_() is asynchronous and we are updating the streams
|
|
1960
|
-
// in parallel, the global playlistStartTime_ may get updated by other
|
|
1961
|
-
// playlist updates rather than the current one.
|
|
1962
|
-
|
|
1963
2084
|
if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
|
|
1964
2085
|
firstStartTime = mediaSequenceToStartTime.get(position);
|
|
1965
|
-
} else {
|
|
1966
|
-
if (this.playlistStartTime_ == null) {
|
|
1967
|
-
// For VOD and EVENT playlists, all variants must start at the same
|
|
1968
|
-
// time, so we can fetch the start time once and reuse for the others.
|
|
1969
|
-
// This is not guaranteed when updating a LIVE stream. We assume the
|
|
1970
|
-
// first segment in each live playlist is no more than one segment out
|
|
1971
|
-
// of sync with the other playlists, so we can fetch the start time for
|
|
1972
|
-
// once.
|
|
1973
|
-
initSegmentRef = this.getInitSegmentReference_(
|
|
1974
|
-
playlist.absoluteUri, hlsSegments[0].tags, variables);
|
|
1975
|
-
goog.asserts.assert(
|
|
1976
|
-
type != shaka.util.ManifestParserUtils.ContentType.TEXT &&
|
|
1977
|
-
type != shaka.util.ManifestParserUtils.ContentType.IMAGE,
|
|
1978
|
-
'Should only get start time from audio or video streams');
|
|
1979
|
-
this.playlistStartTime_ = await this.getStartTime_(
|
|
1980
|
-
verbatimMediaPlaylistUri, initSegmentRef, mimeType,
|
|
1981
|
-
position, /* isDiscontinuity= */ false,
|
|
1982
|
-
hlsSegments[0], variables, type, codecs, bandwidth);
|
|
1983
|
-
}
|
|
1984
|
-
firstStartTime = this.playlistStartTime_;
|
|
1985
2086
|
}
|
|
1986
2087
|
|
|
1987
2088
|
const firstSegmentUri = hlsSegments[0].absoluteUri;
|
|
1988
2089
|
shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
|
|
1989
2090
|
'starts at', firstStartTime);
|
|
1990
2091
|
|
|
1991
|
-
let discontintuitySequenceNum = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
1992
|
-
playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE');
|
|
1993
|
-
if (this.lowLatencyMode_) {
|
|
1994
|
-
if (!discontinuityToMediaSequence.has(discontintuitySequenceNum)) {
|
|
1995
|
-
discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
|
|
1996
|
-
}
|
|
1997
|
-
if (skippedSegments) {
|
|
1998
|
-
// With delta updates, the DISCONTINUITY may be skipped. Check if
|
|
1999
|
-
// the discontintuity Sequence Number based on the media sequence
|
|
2000
|
-
// number.
|
|
2001
|
-
const disconMap = discontinuityToMediaSequence;
|
|
2002
|
-
while (disconMap.has(discontintuitySequenceNum + 1) &&
|
|
2003
|
-
disconMap.get(discontintuitySequenceNum + 1) < position) {
|
|
2004
|
-
discontintuitySequenceNum++;
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
let timestampOffset =
|
|
2009
|
-
this.discontinuityToTso_.get(discontintuitySequenceNum) || 0;
|
|
2010
|
-
|
|
2011
2092
|
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
2012
2093
|
const references = [];
|
|
2013
2094
|
|
|
2014
2095
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2015
2096
|
const item = hlsSegments[i];
|
|
2016
2097
|
const previousReference = references[references.length - 1];
|
|
2017
|
-
const startTime =
|
|
2018
|
-
|
|
2098
|
+
const startTime =
|
|
2099
|
+
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2019
2100
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
2020
2101
|
|
|
2021
2102
|
mediaSequenceToStartTime.set(position, startTime);
|
|
@@ -2023,19 +2104,6 @@ shaka.hls.HlsParser = class {
|
|
|
2023
2104
|
initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
|
|
2024
2105
|
item.tags, variables);
|
|
2025
2106
|
|
|
2026
|
-
const discontintuityTag = shaka.hls.Utils.getFirstTagWithName(item.tags,
|
|
2027
|
-
'EXT-X-DISCONTINUITY');
|
|
2028
|
-
if (discontintuityTag) {
|
|
2029
|
-
discontintuitySequenceNum++;
|
|
2030
|
-
discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
|
|
2031
|
-
|
|
2032
|
-
// eslint-disable-next-line no-await-in-loop
|
|
2033
|
-
timestampOffset = await this.getTimestampOffset_(
|
|
2034
|
-
discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
|
|
2035
|
-
mimeType, position, item, variables, startTime, type, codecs,
|
|
2036
|
-
bandwidth);
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
2107
|
// If the stream is low latency and the user has not configured the
|
|
2040
2108
|
// lowLatencyMode, but if it has been configured to activate the
|
|
2041
2109
|
// lowLatencyMode if a stream of this type is detected, we automatically
|
|
@@ -2048,519 +2116,96 @@ shaka.hls.HlsParser = class {
|
|
|
2048
2116
|
}
|
|
2049
2117
|
}
|
|
2050
2118
|
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
type);
|
|
2063
|
-
|
|
2064
|
-
if (reference) {
|
|
2065
|
-
references.push(reference);
|
|
2066
|
-
}
|
|
2067
|
-
} else if (!this.lowLatencyMode_) {
|
|
2068
|
-
// If a segment has no extinfTag, it must contain partial segments.
|
|
2069
|
-
shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
|
|
2070
|
-
'low-latency streaming mode is not enabled in Shaka Player. ' +
|
|
2071
|
-
'Set streaming.lowLatencyMode configuration to true, and see ' +
|
|
2072
|
-
'https://bit.ly/3clctcj for details.');
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
return references;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
/**
|
|
2080
|
-
* Gets the start time of the first segment of the playlist from existing
|
|
2081
|
-
* value (if possible) or by downloading it and parsing it otherwise.
|
|
2082
|
-
*
|
|
2083
|
-
* @param {number} discontintuitySequenceNum
|
|
2084
|
-
* @param {string} verbatimMediaPlaylistUri
|
|
2085
|
-
* @param {shaka.media.InitSegmentReference} initSegmentRef
|
|
2086
|
-
* @param {string} mimeType
|
|
2087
|
-
* @param {number} mediaSequenceNumber
|
|
2088
|
-
* @param {!shaka.hls.Segment} segment
|
|
2089
|
-
* @param {!Map.<string, string>} variables
|
|
2090
|
-
* @param {number} startTime
|
|
2091
|
-
* @param {string} type
|
|
2092
|
-
* @param {string} codecs
|
|
2093
|
-
* @param {number|undefined} bandwidth
|
|
2094
|
-
* @return {!Promise.<number>}
|
|
2095
|
-
* @throws {shaka.util.Error}
|
|
2096
|
-
* @private
|
|
2097
|
-
*/
|
|
2098
|
-
async getTimestampOffset_(discontintuitySequenceNum,
|
|
2099
|
-
verbatimMediaPlaylistUri, initSegmentRef,
|
|
2100
|
-
mimeType, mediaSequenceNumber, segment, variables, startTime, type,
|
|
2101
|
-
codecs, bandwidth) {
|
|
2102
|
-
let timestampOffset = 0;
|
|
2103
|
-
if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
|
|
2104
|
-
timestampOffset =
|
|
2105
|
-
this.discontinuityToTso_.get(discontintuitySequenceNum);
|
|
2106
|
-
} else {
|
|
2107
|
-
const mediaStartTime = await this.getStartTime_(
|
|
2108
|
-
verbatimMediaPlaylistUri, initSegmentRef, mimeType,
|
|
2109
|
-
mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
|
|
2110
|
-
variables, type, codecs, bandwidth);
|
|
2111
|
-
timestampOffset = startTime - mediaStartTime;
|
|
2112
|
-
shaka.log.v1('Segment timestampOffset =', timestampOffset);
|
|
2113
|
-
this.discontinuityToTso_.set(
|
|
2114
|
-
discontintuitySequenceNum, timestampOffset);
|
|
2115
|
-
}
|
|
2116
|
-
return timestampOffset;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
/**
|
|
2120
|
-
* Try to fetch the starting part of a segment, and fall back to a full
|
|
2121
|
-
* segment if we have to.
|
|
2122
|
-
*
|
|
2123
|
-
* @param {!shaka.media.AnySegmentReference} reference
|
|
2124
|
-
* @param {string} type
|
|
2125
|
-
* @param {string} mimeType
|
|
2126
|
-
* @param {string} codecs
|
|
2127
|
-
* @param {number|undefined} bandwidth
|
|
2128
|
-
* @return {!Promise.<shaka.extern.Response>}
|
|
2129
|
-
* @private
|
|
2130
|
-
*/
|
|
2131
|
-
async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
|
|
2132
|
-
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2133
|
-
|
|
2134
|
-
// Create two requests:
|
|
2135
|
-
// 1. A partial request meant to fetch the smallest part of the segment
|
|
2136
|
-
// required to get the time stamp.
|
|
2137
|
-
// 2. A full request meant as a fallback for when the server does not
|
|
2138
|
-
// support partial requests.
|
|
2139
|
-
const fullRequest = shaka.util.Networking.createSegmentRequest(
|
|
2140
|
-
reference.getUris(),
|
|
2141
|
-
reference.startByte,
|
|
2142
|
-
reference.endByte,
|
|
2143
|
-
this.config_.retryParameters);
|
|
2144
|
-
|
|
2145
|
-
// We can only add partial CMCD data here because the stream
|
|
2146
|
-
// and manifest objects are still being created
|
|
2147
|
-
this.playerInterface_.modifySegmentRequest(
|
|
2148
|
-
fullRequest,
|
|
2149
|
-
{
|
|
2150
|
-
type: type,
|
|
2151
|
-
init: reference instanceof shaka.media.InitSegmentReference,
|
|
2152
|
-
duration: reference.endTime - reference.startTime,
|
|
2153
|
-
mimeType: mimeType,
|
|
2154
|
-
codecs: codecs,
|
|
2155
|
-
bandwidth: bandwidth,
|
|
2156
|
-
},
|
|
2157
|
-
);
|
|
2158
|
-
|
|
2159
|
-
if (this.config_.hls.useFullSegmentsForStartTime) {
|
|
2160
|
-
return this.makeNetworkRequest_(fullRequest, requestType);
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
const partialRequest = shaka.util.Networking.createSegmentRequest(
|
|
2164
|
-
reference.getUris(),
|
|
2165
|
-
reference.startByte,
|
|
2166
|
-
reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
|
|
2167
|
-
this.config_.retryParameters);
|
|
2168
|
-
|
|
2169
|
-
this.playerInterface_.modifySegmentRequest(
|
|
2170
|
-
partialRequest,
|
|
2171
|
-
{
|
|
2172
|
-
type: type,
|
|
2173
|
-
init: reference instanceof shaka.media.InitSegmentReference,
|
|
2174
|
-
duration: reference.endTime - reference.startTime,
|
|
2175
|
-
mimeType: mimeType,
|
|
2176
|
-
codecs: codecs,
|
|
2177
|
-
bandwidth: bandwidth,
|
|
2178
|
-
},
|
|
2179
|
-
);
|
|
2180
|
-
|
|
2181
|
-
// TODO(vaage): The need to do fall back requests is not likely to be unique
|
|
2182
|
-
// to here. It would be nice if the fallback(s) could be included into
|
|
2183
|
-
// the same abortable operation as the original request.
|
|
2184
|
-
//
|
|
2185
|
-
// What would need to change with networking engine to support requests
|
|
2186
|
-
// with fallback(s)?
|
|
2187
|
-
try {
|
|
2188
|
-
const response = await this.makeNetworkRequest_(
|
|
2189
|
-
partialRequest, requestType);
|
|
2190
|
-
|
|
2191
|
-
return response;
|
|
2192
|
-
} catch (e) {
|
|
2193
|
-
// If the networking operation was aborted, we don't want to treat it as
|
|
2194
|
-
// a request failure. We surface the error so that the OPERATION_ABORTED
|
|
2195
|
-
// error will be handled correctly.
|
|
2196
|
-
if (e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
|
|
2197
|
-
throw e;
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
// The partial request may fail for a number of reasons.
|
|
2201
|
-
// Some servers do not support Range requests, and others do not support
|
|
2202
|
-
// the OPTIONS request which must be made before any cross-origin Range
|
|
2203
|
-
// request. Since this fallback is expensive, warn the app developer.
|
|
2204
|
-
shaka.log.alwaysWarn('Unable to fetch the starting part of HLS ' +
|
|
2205
|
-
'segment! Falling back to a full segment request, ' +
|
|
2206
|
-
'which is expensive! Your server should ' +
|
|
2207
|
-
'support Range requests and CORS preflights.',
|
|
2208
|
-
partialRequest.uris[0]);
|
|
2209
|
-
|
|
2210
|
-
const response = await this.makeNetworkRequest_(fullRequest, requestType);
|
|
2211
|
-
|
|
2212
|
-
return response;
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
/**
|
|
2217
|
-
* Gets the start time of a segment from the existing manifest (if possible)
|
|
2218
|
-
* or by downloading it and parsing it otherwise.
|
|
2219
|
-
*
|
|
2220
|
-
* @param {string} verbatimMediaPlaylistUri
|
|
2221
|
-
* @param {shaka.media.InitSegmentReference} initSegmentRef
|
|
2222
|
-
* @param {string} mimeType
|
|
2223
|
-
* @param {number} mediaSequenceNumber
|
|
2224
|
-
* @param {boolean} isDiscontinuity
|
|
2225
|
-
* @param {!shaka.hls.Segment} segment
|
|
2226
|
-
* @param {!Map.<string, string>} variables
|
|
2227
|
-
* @param {string} type
|
|
2228
|
-
* @param {string} codecs
|
|
2229
|
-
* @param {number|undefined} bandwidth
|
|
2230
|
-
* @return {!Promise.<number>}
|
|
2231
|
-
* @private
|
|
2232
|
-
*/
|
|
2233
|
-
async getStartTime_(
|
|
2234
|
-
verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
|
|
2235
|
-
isDiscontinuity, segment, variables, type, codecs, bandwidth) {
|
|
2236
|
-
const segmentRef = this.createSegmentReference_(
|
|
2237
|
-
initSegmentRef,
|
|
2238
|
-
/* previousReference= */ null,
|
|
2239
|
-
segment,
|
|
2240
|
-
/* startTime= */ 0,
|
|
2241
|
-
/* timestampOffset= */ 0,
|
|
2242
|
-
variables,
|
|
2243
|
-
/* absoluteMediaPlaylistUri= */ '',
|
|
2244
|
-
type);
|
|
2245
|
-
goog.asserts.assert(
|
|
2246
|
-
segmentRef != null, 'Segment reference should not be null!');
|
|
2247
|
-
|
|
2248
|
-
// If we are updating the manifest, we can usually skip fetching the segment
|
|
2249
|
-
// by examining the references we already have. This won't be possible if
|
|
2250
|
-
// there was some kind of lag or delay updating the manifest on the server,
|
|
2251
|
-
// in which extreme case we would fall back to fetching a segment. This
|
|
2252
|
-
// allows us to both avoid fetching segments when possible, and recover from
|
|
2253
|
-
// certain server-side issues gracefully.
|
|
2254
|
-
// Do not use cached start time for the segments with discontinuity tags.
|
|
2255
|
-
if (this.manifest_ && !isDiscontinuity) {
|
|
2256
|
-
const streamInfo =
|
|
2257
|
-
this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
|
|
2258
|
-
const startTime = streamInfo.mediaSequenceToStartTime.get(
|
|
2259
|
-
mediaSequenceNumber);
|
|
2260
|
-
if (startTime != undefined) {
|
|
2261
|
-
// We found it! Avoid fetching and parsing the segment.
|
|
2262
|
-
shaka.log.v1('Found segment start time in previous manifest',
|
|
2263
|
-
startTime);
|
|
2264
|
-
return startTime;
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
shaka.log.debug(
|
|
2268
|
-
'Unable to find segment start time in previous manifest!');
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
// TODO: Introduce a new tag to extend HLS and provide the first segment's
|
|
2272
|
-
// start time. This will avoid the need for these fetches in content
|
|
2273
|
-
// packaged with Shaka Packager. This web-friendly extension to HLS can
|
|
2274
|
-
// then be proposed to Apple for inclusion in a future version of HLS.
|
|
2275
|
-
// See https://github.com/google/shaka-packager/issues/294
|
|
2276
|
-
|
|
2277
|
-
shaka.log.v1('Fetching segment to find start time');
|
|
2278
|
-
mimeType = mimeType.toLowerCase();
|
|
2279
|
-
|
|
2280
|
-
if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
|
|
2281
|
-
// Raw formats contain no timestamps. Even if there is an ID3 tag with a
|
|
2282
|
-
// timestamp, that's not going to be honored by MediaSource, which will
|
|
2283
|
-
// use sequence mode for these segments. We don't yet support sequence
|
|
2284
|
-
// mode, so we must reject these streams.
|
|
2285
|
-
// TODO(#2337): Support sequence mode and align raw format timestamps to
|
|
2286
|
-
// other streams.
|
|
2287
|
-
shaka.log.alwaysWarn(
|
|
2288
|
-
'Raw formats are not yet supported. Skipping ' + mimeType);
|
|
2289
|
-
throw new shaka.util.Error(
|
|
2290
|
-
shaka.util.Error.Severity.RECOVERABLE,
|
|
2291
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2292
|
-
shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
if (mimeType == 'video/webm') {
|
|
2296
|
-
shaka.log.alwaysWarn('WebM in HLS is not yet supported. Skipping.');
|
|
2297
|
-
throw new shaka.util.Error(
|
|
2298
|
-
shaka.util.Error.Severity.RECOVERABLE,
|
|
2299
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2300
|
-
shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
|
|
2304
|
-
// We also need the init segment to get the correct timescale. But if the
|
|
2305
|
-
// stream is self-initializing, use the same response for both.
|
|
2306
|
-
const fetches = [this.fetchStartOfSegment_(
|
|
2307
|
-
segmentRef,
|
|
2308
|
-
type,
|
|
2309
|
-
mimeType,
|
|
2310
|
-
codecs,
|
|
2311
|
-
bandwidth,
|
|
2312
|
-
)];
|
|
2313
|
-
|
|
2314
|
-
if (initSegmentRef) {
|
|
2315
|
-
fetches.push(this.fetchStartOfSegment_(
|
|
2316
|
-
initSegmentRef,
|
|
2317
|
-
type,
|
|
2318
|
-
mimeType,
|
|
2319
|
-
codecs,
|
|
2320
|
-
bandwidth,
|
|
2321
|
-
));
|
|
2119
|
+
const reference = this.createSegmentReference_(
|
|
2120
|
+
initSegmentRef,
|
|
2121
|
+
previousReference,
|
|
2122
|
+
item,
|
|
2123
|
+
startTime,
|
|
2124
|
+
variables,
|
|
2125
|
+
playlist.absoluteUri,
|
|
2126
|
+
type);
|
|
2127
|
+
|
|
2128
|
+
if (reference) {
|
|
2129
|
+
references.push(reference);
|
|
2322
2130
|
}
|
|
2323
|
-
|
|
2324
|
-
const responses = await Promise.all(fetches);
|
|
2325
|
-
|
|
2326
|
-
// If the stream is self-initializing, use the main segment in-place of
|
|
2327
|
-
// the init segment.
|
|
2328
|
-
const segmentResponse = responses[0];
|
|
2329
|
-
const initSegmentResponse = responses[1] || responses[0];
|
|
2330
|
-
|
|
2331
|
-
return this.getStartTimeFromMp4Segment_(
|
|
2332
|
-
verbatimMediaPlaylistUri, segmentResponse.uri,
|
|
2333
|
-
segmentResponse.data, initSegmentResponse.data);
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
if (mimeType == 'video/mp2t') {
|
|
2337
|
-
const response = await this.fetchStartOfSegment_(
|
|
2338
|
-
segmentRef,
|
|
2339
|
-
type,
|
|
2340
|
-
mimeType,
|
|
2341
|
-
codecs,
|
|
2342
|
-
bandwidth,
|
|
2343
|
-
);
|
|
2344
|
-
goog.asserts.assert(response.data, 'Should have a response body!');
|
|
2345
|
-
return this.getStartTimeFromTsSegment_(
|
|
2346
|
-
verbatimMediaPlaylistUri, response.uri, response.data);
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
throw new shaka.util.Error(
|
|
2350
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2351
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2352
|
-
shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
|
|
2353
|
-
verbatimMediaPlaylistUri);
|
|
2354
|
-
}
|
|
2355
|
-
|
|
2356
|
-
/**
|
|
2357
|
-
* Parses an mp4 segment to get its start time.
|
|
2358
|
-
*
|
|
2359
|
-
* @param {string} playlistUri
|
|
2360
|
-
* @param {string} segmentUri
|
|
2361
|
-
* @param {BufferSource} mediaData
|
|
2362
|
-
* @param {BufferSource} initData
|
|
2363
|
-
* @return {number}
|
|
2364
|
-
* @private
|
|
2365
|
-
*/
|
|
2366
|
-
getStartTimeFromMp4Segment_(playlistUri, segmentUri, mediaData, initData) {
|
|
2367
|
-
const Mp4Parser = shaka.util.Mp4Parser;
|
|
2368
|
-
|
|
2369
|
-
let timescale = 0;
|
|
2370
|
-
new Mp4Parser()
|
|
2371
|
-
.box('moov', Mp4Parser.children)
|
|
2372
|
-
.box('trak', Mp4Parser.children)
|
|
2373
|
-
.box('mdia', Mp4Parser.children)
|
|
2374
|
-
.fullBox('mdhd', (box) => {
|
|
2375
|
-
goog.asserts.assert(
|
|
2376
|
-
box.version == 0 || box.version == 1,
|
|
2377
|
-
'MDHD version can only be 0 or 1');
|
|
2378
|
-
|
|
2379
|
-
const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
|
|
2380
|
-
box.reader, box.version);
|
|
2381
|
-
|
|
2382
|
-
timescale = parsedMDHDBox.timescale;
|
|
2383
|
-
box.parser.stop();
|
|
2384
|
-
}).parse(initData, /* partialOkay= */ true);
|
|
2385
|
-
|
|
2386
|
-
if (!timescale) {
|
|
2387
|
-
shaka.log.error('Unable to find timescale in init segment!');
|
|
2388
|
-
throw new shaka.util.Error(
|
|
2389
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2390
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2391
|
-
shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
|
|
2392
|
-
playlistUri, segmentUri);
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
let startTime = 0;
|
|
2396
|
-
let parsedMedia = false;
|
|
2397
|
-
new Mp4Parser()
|
|
2398
|
-
.box('moof', Mp4Parser.children)
|
|
2399
|
-
.box('traf', Mp4Parser.children)
|
|
2400
|
-
.fullBox('tfdt', (box) => {
|
|
2401
|
-
goog.asserts.assert(
|
|
2402
|
-
box.version == 0 || box.version == 1,
|
|
2403
|
-
'TFDT version can only be 0 or 1');
|
|
2404
|
-
|
|
2405
|
-
const parsedTFDTBox = shaka.util.Mp4BoxParsers.parseTFDT(
|
|
2406
|
-
box.reader, box.version);
|
|
2407
|
-
const baseTime = parsedTFDTBox.baseMediaDecodeTime;
|
|
2408
|
-
startTime = baseTime / timescale;
|
|
2409
|
-
parsedMedia = true;
|
|
2410
|
-
box.parser.stop();
|
|
2411
|
-
}).parse(mediaData, /* partialOkay= */ true);
|
|
2412
|
-
|
|
2413
|
-
if (!parsedMedia) {
|
|
2414
|
-
throw new shaka.util.Error(
|
|
2415
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2416
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2417
|
-
shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
|
|
2418
|
-
playlistUri, segmentUri);
|
|
2419
2131
|
}
|
|
2420
|
-
return startTime;
|
|
2421
|
-
}
|
|
2422
2132
|
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
*/
|
|
2432
|
-
getStartTimeFromTsSegment_(playlistUri, segmentUri, data) {
|
|
2433
|
-
const reader = new shaka.util.DataViewReader(
|
|
2434
|
-
data, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
|
|
2435
|
-
|
|
2436
|
-
const fail = () => {
|
|
2437
|
-
throw new shaka.util.Error(
|
|
2438
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2439
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2440
|
-
shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
|
|
2441
|
-
playlistUri, segmentUri);
|
|
2442
|
-
};
|
|
2443
|
-
|
|
2444
|
-
let packetStart = 0;
|
|
2445
|
-
let syncByte = 0;
|
|
2446
|
-
|
|
2447
|
-
const skipPacket = () => {
|
|
2448
|
-
// 188-byte packets are standard, so assume that.
|
|
2449
|
-
reader.seek(packetStart + 188);
|
|
2450
|
-
syncByte = reader.readUint8();
|
|
2451
|
-
if (syncByte != 0x47) {
|
|
2452
|
-
// We haven't found the sync byte, so try it as a 192-byte packet.
|
|
2453
|
-
reader.seek(packetStart + 192);
|
|
2454
|
-
syncByte = reader.readUint8();
|
|
2455
|
-
}
|
|
2456
|
-
if (syncByte != 0x47) {
|
|
2457
|
-
// We still haven't found the sync byte, so try as a 204-byte packet.
|
|
2458
|
-
reader.seek(packetStart + 204);
|
|
2459
|
-
syncByte = reader.readUint8();
|
|
2460
|
-
}
|
|
2461
|
-
if (syncByte != 0x47) {
|
|
2462
|
-
// We still haven't found the sync byte, so the packet was of a
|
|
2463
|
-
// non-standard size.
|
|
2464
|
-
fail();
|
|
2465
|
-
}
|
|
2466
|
-
// Put the sync byte back so we can read it in the next loop.
|
|
2467
|
-
reader.rewind(1);
|
|
2468
|
-
};
|
|
2469
|
-
|
|
2470
|
-
// We will look a few packet-lengths forward to find the first sync byte.
|
|
2471
|
-
// Note that we are using this method on what is already a subset of the
|
|
2472
|
-
// file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
|
|
2473
|
-
// we can't look too far ahead to begin with.
|
|
2474
|
-
let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
|
|
2475
|
-
|
|
2476
|
-
// TODO: refactor this while loop for better readability.
|
|
2477
|
-
// eslint-disable-next-line no-constant-condition
|
|
2478
|
-
while (true) {
|
|
2479
|
-
// Format reference: https://bit.ly/TsPacket
|
|
2480
|
-
packetStart = reader.getPosition();
|
|
2481
|
-
|
|
2482
|
-
syncByte = reader.readUint8();
|
|
2483
|
-
if (syncByte != 0x47) {
|
|
2484
|
-
if (syncByteScanLength > 0) {
|
|
2485
|
-
// This file could have started with a cut-off TS packet. Scan forward
|
|
2486
|
-
// until we find a sync byte.
|
|
2487
|
-
syncByteScanLength -= 1;
|
|
2133
|
+
// If some segments have sync times, but not all, extrapolate the sync
|
|
2134
|
+
// times of the ones with none.
|
|
2135
|
+
const someSyncTime = references.some((ref) => ref.syncTime != null);
|
|
2136
|
+
if (someSyncTime) {
|
|
2137
|
+
for (let i = 0; i < references.length; i++) {
|
|
2138
|
+
const reference = references[i];
|
|
2139
|
+
if (reference.syncTime != null) {
|
|
2140
|
+
// No need to extrapolate.
|
|
2488
2141
|
continue;
|
|
2489
2142
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
const pesHeaderLengthRemaining = reader.readUint8();
|
|
2543
|
-
if (pesHeaderLengthRemaining == 0) {
|
|
2544
|
-
fail();
|
|
2143
|
+
// Find the nearest segment with syncTime, in either direction.
|
|
2144
|
+
// This looks forward and backward simultaneously, keeping track of what
|
|
2145
|
+
// to offset the syncTime it finds by as it goes.
|
|
2146
|
+
let forwardAdd = 0;
|
|
2147
|
+
let forwardI = i;
|
|
2148
|
+
/**
|
|
2149
|
+
* Look forwards one reference at a time, summing all durations as we
|
|
2150
|
+
* go, until we find a reference with a syncTime to use as a basis.
|
|
2151
|
+
* This DOES count the original reference, but DOESN'T count the first
|
|
2152
|
+
* reference with a syncTime (as we approach it from behind).
|
|
2153
|
+
* @return {?number}
|
|
2154
|
+
*/
|
|
2155
|
+
const lookForward = () => {
|
|
2156
|
+
const other = references[forwardI];
|
|
2157
|
+
if (other) {
|
|
2158
|
+
if (other.syncTime != null) {
|
|
2159
|
+
return other.syncTime + forwardAdd;
|
|
2160
|
+
}
|
|
2161
|
+
forwardAdd -= other.endTime - other.startTime;
|
|
2162
|
+
forwardI += 1;
|
|
2163
|
+
}
|
|
2164
|
+
return null;
|
|
2165
|
+
};
|
|
2166
|
+
let backwardAdd = 0;
|
|
2167
|
+
let backwardI = i;
|
|
2168
|
+
/**
|
|
2169
|
+
* Look backwards one reference at a time, summing all durations as we
|
|
2170
|
+
* go, until we find a reference with a syncTime to use as a basis.
|
|
2171
|
+
* This DOESN'T count the original reference, but DOES count the first
|
|
2172
|
+
* reference with a syncTime (as we approach it from ahead).
|
|
2173
|
+
* @return {?number}
|
|
2174
|
+
*/
|
|
2175
|
+
const lookBackward = () => {
|
|
2176
|
+
const other = references[backwardI];
|
|
2177
|
+
if (other) {
|
|
2178
|
+
if (other != reference) {
|
|
2179
|
+
backwardAdd += other.endTime - other.startTime;
|
|
2180
|
+
}
|
|
2181
|
+
if (other.syncTime != null) {
|
|
2182
|
+
return other.syncTime + backwardAdd;
|
|
2183
|
+
}
|
|
2184
|
+
backwardI -= 1;
|
|
2185
|
+
}
|
|
2186
|
+
return null;
|
|
2187
|
+
};
|
|
2188
|
+
while (reference.syncTime == null) {
|
|
2189
|
+
reference.syncTime = lookBackward();
|
|
2190
|
+
if (reference.syncTime == null) {
|
|
2191
|
+
reference.syncTime = lookForward();
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2545
2194
|
}
|
|
2195
|
+
}
|
|
2546
2196
|
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2197
|
+
// Split the sync times properly among partial segments.
|
|
2198
|
+
if (someSyncTime) {
|
|
2199
|
+
for (const reference of references) {
|
|
2200
|
+
let syncTime = reference.syncTime;
|
|
2201
|
+
for (const partial of reference.partialReferences) {
|
|
2202
|
+
partial.syncTime = syncTime;
|
|
2203
|
+
syncTime += partial.endTime - partial.startTime;
|
|
2204
|
+
}
|
|
2551
2205
|
}
|
|
2552
|
-
|
|
2553
|
-
const pts0 = reader.readUint8();
|
|
2554
|
-
const pts1 = reader.readUint16();
|
|
2555
|
-
const pts2 = reader.readUint16();
|
|
2556
|
-
// Reconstruct 33-bit PTS from the 5-byte, padded structure.
|
|
2557
|
-
const ptsHigh3 = (pts0 & 0x0e) >> 1;
|
|
2558
|
-
const ptsLow30 = ((pts1 & 0xfffe) << 14) | ((pts2 & 0xfffe) >> 1);
|
|
2559
|
-
// Reconstruct the PTS as a float. Avoid bitwise operations to combine
|
|
2560
|
-
// because bitwise ops treat the values as 32-bit ints.
|
|
2561
|
-
const pts = ptsHigh3 * (1 << 30) + ptsLow30;
|
|
2562
|
-
return pts / shaka.hls.HlsParser.TS_TIMESCALE_;
|
|
2563
2206
|
}
|
|
2207
|
+
|
|
2208
|
+
return references;
|
|
2564
2209
|
}
|
|
2565
2210
|
|
|
2566
2211
|
/**
|
|
@@ -2627,11 +2272,10 @@ shaka.hls.HlsParser = class {
|
|
|
2627
2272
|
|
|
2628
2273
|
if (contentType == ContentType.TEXT) {
|
|
2629
2274
|
// The extension map didn't work.
|
|
2630
|
-
if (
|
|
2275
|
+
if (codecs == 'vtt' || codecs == 'wvtt') {
|
|
2631
2276
|
// If codecs is 'vtt', it's WebVTT.
|
|
2632
|
-
// If there was no codecs string, assume HLS text streams are WebVTT.
|
|
2633
2277
|
return 'text/vtt';
|
|
2634
|
-
} else {
|
|
2278
|
+
} else if (codecs && codecs !== '') {
|
|
2635
2279
|
// Otherwise, assume MP4-embedded text, since text-based formats tend
|
|
2636
2280
|
// not to have a codecs string at all.
|
|
2637
2281
|
return 'application/mp4';
|
|
@@ -2656,6 +2300,11 @@ shaka.hls.HlsParser = class {
|
|
|
2656
2300
|
const contentMimeType = response.headers['content-type'];
|
|
2657
2301
|
|
|
2658
2302
|
if (!contentMimeType) {
|
|
2303
|
+
if (contentType == ContentType.TEXT) {
|
|
2304
|
+
// If there was no codecs string and no content-type, assume HLS text
|
|
2305
|
+
// streams are WebVTT.
|
|
2306
|
+
return 'text/vtt';
|
|
2307
|
+
}
|
|
2659
2308
|
// If the HLS content is lacking in both MIME type metadata and
|
|
2660
2309
|
// segment file extensions, we fall back to assuming it's MP4.
|
|
2661
2310
|
const fallbackMimeType = map['mp4'];
|
|
@@ -2821,6 +2470,33 @@ shaka.hls.HlsParser = class {
|
|
|
2821
2470
|
return op.promise;
|
|
2822
2471
|
}
|
|
2823
2472
|
|
|
2473
|
+
/**
|
|
2474
|
+
* @param {!shaka.hls.Tag} drmTag
|
|
2475
|
+
* @param {string} mimeType
|
|
2476
|
+
* @return {?shaka.extern.DrmInfo}
|
|
2477
|
+
* @private
|
|
2478
|
+
*/
|
|
2479
|
+
static fairplayDrmParser_(drmTag, mimeType) {
|
|
2480
|
+
if (mimeType == 'video/mp2t') {
|
|
2481
|
+
throw new shaka.util.Error(
|
|
2482
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2483
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2484
|
+
shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
/*
|
|
2488
|
+
* Even if we're not able to construct initData through the HLS tag, adding
|
|
2489
|
+
* a DRMInfo will allow DRM Engine to request a media key system access
|
|
2490
|
+
* with the correct keySystem and initDataType
|
|
2491
|
+
*/
|
|
2492
|
+
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
2493
|
+
'com.apple.fps', [
|
|
2494
|
+
{initDataType: 'sinf', initData: new Uint8Array(0)},
|
|
2495
|
+
]);
|
|
2496
|
+
|
|
2497
|
+
return drmInfo;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2824
2500
|
/**
|
|
2825
2501
|
* @param {!shaka.hls.Tag} drmTag
|
|
2826
2502
|
* @return {?shaka.extern.DrmInfo}
|
|
@@ -2883,7 +2559,10 @@ shaka.hls.HlsParser = class {
|
|
|
2883
2559
|
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
|
|
2884
2560
|
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
|
|
2885
2561
|
]);
|
|
2886
|
-
const
|
|
2562
|
+
const keyIds = new Set();
|
|
2563
|
+
const psshVersion = 0;
|
|
2564
|
+
const pssh =
|
|
2565
|
+
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
|
|
2887
2566
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
2888
2567
|
'com.microsoft.playready', [
|
|
2889
2568
|
{initDataType: 'cenc', initData: pssh},
|
|
@@ -2902,7 +2581,6 @@ shaka.hls.HlsParser = class {
|
|
|
2902
2581
|
* minTimestamp: number,
|
|
2903
2582
|
* maxTimestamp: number,
|
|
2904
2583
|
* mediaSequenceToStartTime: !Map.<number, number>,
|
|
2905
|
-
* discontinuityToMediaSequence: !Map.<number, number>,
|
|
2906
2584
|
* canSkipSegments: boolean
|
|
2907
2585
|
* }}
|
|
2908
2586
|
*
|
|
@@ -2925,9 +2603,6 @@ shaka.hls.HlsParser = class {
|
|
|
2925
2603
|
* The maximum timestamp found in the stream.
|
|
2926
2604
|
* @property {!Map.<number, number>} mediaSequenceToStartTime
|
|
2927
2605
|
* A map of media sequence numbers to media start times.
|
|
2928
|
-
* @property {!Map.<number, number>} discontinuityToMediaSequence
|
|
2929
|
-
* A map of discontinuity sequence numbers to the media sequence number of the
|
|
2930
|
-
* segment starting with that discontinuity sequence number.
|
|
2931
2606
|
* @property {boolean} canSkipSegments
|
|
2932
2607
|
* True if the server supports delta playlist updates, and we can send a
|
|
2933
2608
|
* request for a playlist that can skip older media segments.
|
|
@@ -2971,21 +2646,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
2971
2646
|
};
|
|
2972
2647
|
|
|
2973
2648
|
|
|
2974
|
-
/**
|
|
2975
|
-
* MIME types of raw formats.
|
|
2976
|
-
* TODO(#2337): Support raw formats and share this list among parsers.
|
|
2977
|
-
*
|
|
2978
|
-
* @const {!Array.<string>}
|
|
2979
|
-
* @private
|
|
2980
|
-
*/
|
|
2981
|
-
shaka.hls.HlsParser.RAW_FORMATS_ = [
|
|
2982
|
-
'audio/aac',
|
|
2983
|
-
'audio/ac3',
|
|
2984
|
-
'audio/ec3',
|
|
2985
|
-
'audio/mpeg',
|
|
2986
|
-
];
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
2649
|
/**
|
|
2990
2650
|
* @const {!Object.<string, string>}
|
|
2991
2651
|
* @private
|
|
@@ -3043,7 +2703,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
|
|
|
3043
2703
|
|
|
3044
2704
|
|
|
3045
2705
|
/**
|
|
3046
|
-
* @typedef {function(!shaka.hls.Tag):?shaka.extern.DrmInfo}
|
|
2706
|
+
* @typedef {function(!shaka.hls.Tag, string):?shaka.extern.DrmInfo}
|
|
3047
2707
|
* @private
|
|
3048
2708
|
*/
|
|
3049
2709
|
shaka.hls.HlsParser.DrmParser_;
|
|
@@ -3054,10 +2714,8 @@ shaka.hls.HlsParser.DrmParser_;
|
|
|
3054
2714
|
* @private
|
|
3055
2715
|
*/
|
|
3056
2716
|
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
|
|
3057
|
-
/* TODO: https://github.com/google/shaka-player/issues/382
|
|
3058
2717
|
'com.apple.streamingkeydelivery':
|
|
3059
2718
|
shaka.hls.HlsParser.fairplayDrmParser_,
|
|
3060
|
-
*/
|
|
3061
2719
|
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
|
|
3062
2720
|
shaka.hls.HlsParser.widevineDrmParser_,
|
|
3063
2721
|
'com.microsoft.playready':
|
|
@@ -3075,28 +2733,13 @@ shaka.hls.HlsParser.PresentationType_ = {
|
|
|
3075
2733
|
LIVE: 'LIVE',
|
|
3076
2734
|
};
|
|
3077
2735
|
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
shaka.
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
* need to know the segment start time. This allows us to avoid fetching the
|
|
3089
|
-
* entire segment in many cases.
|
|
3090
|
-
*
|
|
3091
|
-
* @const {number}
|
|
3092
|
-
* @private
|
|
3093
|
-
*/
|
|
3094
|
-
shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ = 2048;
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
shaka.media.ManifestParser.registerParserByExtension(
|
|
3098
|
-
'm3u8', () => new shaka.hls.HlsParser());
|
|
3099
|
-
shaka.media.ManifestParser.registerParserByMime(
|
|
3100
|
-
'application/x-mpegurl', () => new shaka.hls.HlsParser());
|
|
3101
|
-
shaka.media.ManifestParser.registerParserByMime(
|
|
3102
|
-
'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
|
|
2736
|
+
if (!shaka.util.Platform.isTizen3() &&
|
|
2737
|
+
!shaka.util.Platform.isTizen2() &&
|
|
2738
|
+
!shaka.util.Platform.isWebOS3()) {
|
|
2739
|
+
shaka.media.ManifestParser.registerParserByExtension(
|
|
2740
|
+
'm3u8', () => new shaka.hls.HlsParser());
|
|
2741
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
2742
|
+
'application/x-mpegurl', () => new shaka.hls.HlsParser());
|
|
2743
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
2744
|
+
'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
|
|
2745
|
+
}
|