shaka-player 3.3.4 → 4.0.1
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/build-and-test.yaml +0 -1
- package/.github/workflows/demo-version-index.yaml +8 -0
- package/.github/workflows/release-please.yaml +19 -3
- package/.github/workflows/selenium-lab-tests.yaml +0 -1
- 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 +209 -28
- package/CONTRIBUTORS +1 -0
- package/README.md +10 -7
- 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/README.md +2 -2
- package/build/compiler.py +1 -1
- package/build/generateTsDefs.py +0 -2
- package/build/misspellings.txt +1 -0
- package/build/shaka-lab.yaml +0 -2
- package/build/test.py +7 -1
- package/build/types/core +0 -1
- 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_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 +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 +16 -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 +78 -82
- package/dist/shaka-player.compiled.debug.d.ts +78 -82
- package/dist/shaka-player.compiled.debug.externs.js +127 -126
- package/dist/shaka-player.compiled.debug.js +471 -523
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +127 -126
- package/dist/shaka-player.compiled.js +844 -858
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +80 -82
- package/dist/shaka-player.ui.debug.d.ts +80 -82
- package/dist/shaka-player.ui.debug.externs.js +135 -126
- package/dist/shaka-player.ui.debug.js +531 -581
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +135 -126
- package/dist/shaka-player.ui.js +991 -1001
- 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/karma.conf.js +4 -0
- 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 +3 -3
- package/lib/hls/hls_parser.js +391 -747
- package/lib/hls/manifest_text_parser.js +2 -3
- package/lib/media/drm_engine.js +102 -37
- package/lib/media/gap_jumping_controller.js +10 -45
- package/lib/media/media_source_engine.js +157 -31
- 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/storage_mechanism.js +4 -2
- 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 +100 -132
- package/lib/player.js +181 -307
- 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/config_utils.js +25 -0
- package/lib/util/error.js +16 -13
- package/lib/util/fairplay_utils.js +109 -0
- package/lib/util/functional.js +0 -42
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mime_utils.js +50 -1
- package/lib/util/periods.js +5 -4
- package/lib/util/platform.js +45 -0
- package/lib/util/player_configuration.js +26 -20
- package/lib/util/pssh.js +23 -4
- package/lib/util/stream_utils.js +96 -24
- package/lib/util/uint8array_utils.js +0 -16
- package/lib/util/xml_utils.js +1 -1
- package/package.json +4 -1
- package/roadmap.md +19 -19
- package/shaka-player.uncompiled.js +0 -3
- package/test/cast/cast_utils_unit.js +1 -0
- package/test/dash/dash_parser_content_protection_unit.js +44 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -24
- 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 +121 -220
- package/test/hls/hls_parser_unit.js +515 -432
- package/test/hls/manifest_text_parser_unit.js +19 -0
- package/test/media/drm_engine_unit.js +86 -120
- package/test/media/media_source_engine_integration.js +26 -3
- package/test/media/media_source_engine_unit.js +7 -6
- package/test/media/playhead_unit.js +27 -191
- package/test/media/segment_index_unit.js +15 -25
- package/test/media/streaming_engine_integration.js +4 -39
- package/test/media/streaming_engine_unit.js +3 -2
- package/test/media/time_ranges_utils_unit.js +0 -5
- package/test/offline/indexeddb_storage_unit.js +1 -1
- package/test/offline/manifest_convert_unit.js +34 -4
- package/test/offline/offline_manifest_parser_unit.js +1 -0
- package/test/offline/storage_compatibility_unit.js +1 -1
- package/test/offline/storage_integration.js +5 -4
- package/test/player_external.js +7 -7
- package/test/player_integration.js +22 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +124 -1
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -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/simple_fakes.js +31 -2
- 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/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -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 +3 -3
- 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/docs/tutorials/gsoc-ideas-list.md +0 -3
- 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/lib/util/mutex.js +0 -52
- 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,16 +1822,15 @@ 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
|
-
* @return {!shaka.media.SegmentReference}
|
|
1743
1827
|
* @param {string} type
|
|
1828
|
+
* @return {shaka.media.SegmentReference}
|
|
1744
1829
|
* @private
|
|
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,9 +1841,52 @@ 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
|
+
|
|
1873
|
+
if (!extinfTag) {
|
|
1874
|
+
if (hlsSegment.partialSegments.length == 0) {
|
|
1875
|
+
// EXTINF tag must be available if the segment has no partial segments.
|
|
1876
|
+
throw new shaka.util.Error(
|
|
1877
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1878
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1879
|
+
shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
|
|
1880
|
+
} else if (!this.lowLatencyMode_) {
|
|
1881
|
+
// Without EXTINF and without low-latency mode, partial segments get
|
|
1882
|
+
// ignored.
|
|
1883
|
+
return null;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1759
1887
|
// Create SegmentReferences for the partial segments.
|
|
1760
1888
|
const partialSegmentRefs = [];
|
|
1761
|
-
if (this.lowLatencyMode_
|
|
1889
|
+
if (this.lowLatencyMode_) {
|
|
1762
1890
|
for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
|
|
1763
1891
|
const item = hlsSegment.partialSegments[i];
|
|
1764
1892
|
const pPreviousReference = i == 0 ?
|
|
@@ -1794,19 +1922,11 @@ shaka.hls.HlsParser = class {
|
|
|
1794
1922
|
pStartByte,
|
|
1795
1923
|
pEndByte,
|
|
1796
1924
|
initSegmentReference,
|
|
1797
|
-
timestampOffset,
|
|
1925
|
+
/* timestampOffset= */ 0, // This value is ignored in sequence mode.
|
|
1798
1926
|
/* appendWindowStart= */ 0,
|
|
1799
1927
|
/* appendWindowEnd= */ Infinity);
|
|
1800
1928
|
partialSegmentRefs.push(partial);
|
|
1801
1929
|
} // for-loop of hlsSegment.partialSegments
|
|
1802
|
-
} else {
|
|
1803
|
-
// EXTINF tag must be available if the segment has no partial segments.
|
|
1804
|
-
if (!extinfTag) {
|
|
1805
|
-
throw new shaka.util.Error(
|
|
1806
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
1807
|
-
shaka.util.Error.Category.MANIFEST,
|
|
1808
|
-
shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
|
|
1809
|
-
}
|
|
1810
1930
|
}
|
|
1811
1931
|
|
|
1812
1932
|
// If the segment has EXTINF tag, set the segment's end time, start byte
|
|
@@ -1825,6 +1945,7 @@ shaka.hls.HlsParser = class {
|
|
|
1825
1945
|
} else {
|
|
1826
1946
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
1827
1947
|
}
|
|
1948
|
+
|
|
1828
1949
|
// If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
|
|
1829
1950
|
// base on the byterange information. If segment has no EXT-X-BYTERANGE tag
|
|
1830
1951
|
// and has partial segments, set the start byte and end byte base on the
|
|
@@ -1862,12 +1983,14 @@ shaka.hls.HlsParser = class {
|
|
|
1862
1983
|
startByte,
|
|
1863
1984
|
endByte,
|
|
1864
1985
|
initSegmentReference,
|
|
1865
|
-
timestampOffset,
|
|
1986
|
+
/* timestampOffset= */ 0, // This value is ignored in sequence mode.
|
|
1866
1987
|
/* appendWindowStart= */ 0,
|
|
1867
1988
|
/* appendWindowEnd= */ Infinity,
|
|
1868
1989
|
partialSegmentRefs,
|
|
1869
1990
|
tilesLayout,
|
|
1870
1991
|
tileDuration,
|
|
1992
|
+
syncTime,
|
|
1993
|
+
status,
|
|
1871
1994
|
);
|
|
1872
1995
|
}
|
|
1873
1996
|
|
|
@@ -1921,15 +2044,12 @@ shaka.hls.HlsParser = class {
|
|
|
1921
2044
|
* @param {string} mimeType
|
|
1922
2045
|
* @param {!Map.<number, number>} mediaSequenceToStartTime
|
|
1923
2046
|
* @param {!Map.<string, string>} variables
|
|
1924
|
-
* @param {!Map.<number, number>} discontinuityToMediaSequence
|
|
1925
2047
|
* @param {string} codecs
|
|
1926
|
-
* @
|
|
1927
|
-
* @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
|
|
2048
|
+
* @return {!Array.<!shaka.media.SegmentReference>}
|
|
1928
2049
|
* @private
|
|
1929
2050
|
*/
|
|
1930
|
-
|
|
1931
|
-
mediaSequenceToStartTime, variables,
|
|
1932
|
-
codecs, bandwidth) {
|
|
2051
|
+
createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
|
|
2052
|
+
mediaSequenceToStartTime, variables, codecs) {
|
|
1933
2053
|
/** @type {Array.<!shaka.hls.Segment>} */
|
|
1934
2054
|
const hlsSegments = playlist.segments;
|
|
1935
2055
|
goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
|
|
@@ -1946,69 +2066,26 @@ shaka.hls.HlsParser = class {
|
|
|
1946
2066
|
const skippedSegments =
|
|
1947
2067
|
skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
|
|
1948
2068
|
let position = mediaSequenceNumber + skippedSegments;
|
|
1949
|
-
let firstStartTime;
|
|
2069
|
+
let firstStartTime = 0;
|
|
2070
|
+
|
|
1950
2071
|
// For live stream, use the cached value in the mediaSequenceToStartTime
|
|
1951
2072
|
// map if available.
|
|
1952
|
-
// Since createSegments_() is asynchronous and we are updating the streams
|
|
1953
|
-
// in parallel, the global playlistStartTime_ may get updated by other
|
|
1954
|
-
// playlist updates rather than the current one.
|
|
1955
|
-
|
|
1956
2073
|
if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
|
|
1957
2074
|
firstStartTime = mediaSequenceToStartTime.get(position);
|
|
1958
|
-
} else {
|
|
1959
|
-
if (this.playlistStartTime_ == null) {
|
|
1960
|
-
// For VOD and EVENT playlists, all variants must start at the same
|
|
1961
|
-
// time, so we can fetch the start time once and reuse for the others.
|
|
1962
|
-
// This is not guaranteed when updating a LIVE stream. We assume the
|
|
1963
|
-
// first segment in each live playlist is no more than one segment out
|
|
1964
|
-
// of sync with the other playlists, so we can fetch the start time for
|
|
1965
|
-
// once.
|
|
1966
|
-
initSegmentRef = this.getInitSegmentReference_(
|
|
1967
|
-
playlist.absoluteUri, hlsSegments[0].tags, variables);
|
|
1968
|
-
goog.asserts.assert(
|
|
1969
|
-
type != shaka.util.ManifestParserUtils.ContentType.TEXT &&
|
|
1970
|
-
type != shaka.util.ManifestParserUtils.ContentType.IMAGE,
|
|
1971
|
-
'Should only get start time from audio or video streams');
|
|
1972
|
-
this.playlistStartTime_ = await this.getStartTime_(
|
|
1973
|
-
verbatimMediaPlaylistUri, initSegmentRef, mimeType,
|
|
1974
|
-
position, /* isDiscontinuity= */ false,
|
|
1975
|
-
hlsSegments[0], variables, type, codecs, bandwidth);
|
|
1976
|
-
}
|
|
1977
|
-
firstStartTime = this.playlistStartTime_;
|
|
1978
2075
|
}
|
|
1979
2076
|
|
|
1980
2077
|
const firstSegmentUri = hlsSegments[0].absoluteUri;
|
|
1981
2078
|
shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
|
|
1982
2079
|
'starts at', firstStartTime);
|
|
1983
2080
|
|
|
1984
|
-
let discontintuitySequenceNum = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
1985
|
-
playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE');
|
|
1986
|
-
if (this.lowLatencyMode_) {
|
|
1987
|
-
if (!discontinuityToMediaSequence.has(discontintuitySequenceNum)) {
|
|
1988
|
-
discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
|
|
1989
|
-
}
|
|
1990
|
-
if (skippedSegments) {
|
|
1991
|
-
// With delta updates, the DISCONTINUITY may be skipped. Check if
|
|
1992
|
-
// the discontintuity Sequence Number based on the media sequence
|
|
1993
|
-
// number.
|
|
1994
|
-
const disconMap = discontinuityToMediaSequence;
|
|
1995
|
-
while (disconMap.has(discontintuitySequenceNum + 1) &&
|
|
1996
|
-
disconMap.get(discontintuitySequenceNum + 1) < position) {
|
|
1997
|
-
discontintuitySequenceNum++;
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
let timestampOffset =
|
|
2002
|
-
this.discontinuityToTso_.get(discontintuitySequenceNum) || 0;
|
|
2003
|
-
|
|
2004
2081
|
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
2005
2082
|
const references = [];
|
|
2006
2083
|
|
|
2007
2084
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2008
2085
|
const item = hlsSegments[i];
|
|
2009
2086
|
const previousReference = references[references.length - 1];
|
|
2010
|
-
const startTime =
|
|
2011
|
-
|
|
2087
|
+
const startTime =
|
|
2088
|
+
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2012
2089
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
2013
2090
|
|
|
2014
2091
|
mediaSequenceToStartTime.set(position, startTime);
|
|
@@ -2016,19 +2093,6 @@ shaka.hls.HlsParser = class {
|
|
|
2016
2093
|
initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
|
|
2017
2094
|
item.tags, variables);
|
|
2018
2095
|
|
|
2019
|
-
const discontintuityTag = shaka.hls.Utils.getFirstTagWithName(item.tags,
|
|
2020
|
-
'EXT-X-DISCONTINUITY');
|
|
2021
|
-
if (discontintuityTag) {
|
|
2022
|
-
discontintuitySequenceNum++;
|
|
2023
|
-
discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
|
|
2024
|
-
|
|
2025
|
-
// eslint-disable-next-line no-await-in-loop
|
|
2026
|
-
timestampOffset = await this.getTimestampOffset_(
|
|
2027
|
-
discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
|
|
2028
|
-
mimeType, position, item, variables, startTime, type, codecs,
|
|
2029
|
-
bandwidth);
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
2096
|
// If the stream is low latency and the user has not configured the
|
|
2033
2097
|
// lowLatencyMode, but if it has been configured to activate the
|
|
2034
2098
|
// lowLatencyMode if a stream of this type is detected, we automatically
|
|
@@ -2041,514 +2105,96 @@ shaka.hls.HlsParser = class {
|
|
|
2041
2105
|
}
|
|
2042
2106
|
}
|
|
2043
2107
|
|
|
2044
|
-
const
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
timestampOffset,
|
|
2053
|
-
variables,
|
|
2054
|
-
playlist.absoluteUri,
|
|
2055
|
-
type);
|
|
2108
|
+
const reference = this.createSegmentReference_(
|
|
2109
|
+
initSegmentRef,
|
|
2110
|
+
previousReference,
|
|
2111
|
+
item,
|
|
2112
|
+
startTime,
|
|
2113
|
+
variables,
|
|
2114
|
+
playlist.absoluteUri,
|
|
2115
|
+
type);
|
|
2056
2116
|
|
|
2117
|
+
if (reference) {
|
|
2057
2118
|
references.push(reference);
|
|
2058
|
-
} else if (!this.lowLatencyMode_) {
|
|
2059
|
-
// If a segment has no extinfTag, it must contain partial segments.
|
|
2060
|
-
shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
|
|
2061
|
-
'low-latency streaming mode is not enabled in Shaka Player. ' +
|
|
2062
|
-
'Set streaming.lowLatencyMode configuration to true, and see ' +
|
|
2063
|
-
'https://bit.ly/3clctcj for details.');
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
return references;
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
/**
|
|
2071
|
-
* Gets the start time of the first segment of the playlist from existing
|
|
2072
|
-
* value (if possible) or by downloading it and parsing it otherwise.
|
|
2073
|
-
*
|
|
2074
|
-
* @param {number} discontintuitySequenceNum
|
|
2075
|
-
* @param {string} verbatimMediaPlaylistUri
|
|
2076
|
-
* @param {shaka.media.InitSegmentReference} initSegmentRef
|
|
2077
|
-
* @param {string} mimeType
|
|
2078
|
-
* @param {number} mediaSequenceNumber
|
|
2079
|
-
* @param {!shaka.hls.Segment} segment
|
|
2080
|
-
* @param {!Map.<string, string>} variables
|
|
2081
|
-
* @param {number} startTime
|
|
2082
|
-
* @param {string} type
|
|
2083
|
-
* @param {string} codecs
|
|
2084
|
-
* @param {number|undefined} bandwidth
|
|
2085
|
-
* @return {!Promise.<number>}
|
|
2086
|
-
* @throws {shaka.util.Error}
|
|
2087
|
-
* @private
|
|
2088
|
-
*/
|
|
2089
|
-
async getTimestampOffset_(discontintuitySequenceNum,
|
|
2090
|
-
verbatimMediaPlaylistUri, initSegmentRef,
|
|
2091
|
-
mimeType, mediaSequenceNumber, segment, variables, startTime, type,
|
|
2092
|
-
codecs, bandwidth) {
|
|
2093
|
-
let timestampOffset = 0;
|
|
2094
|
-
if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
|
|
2095
|
-
timestampOffset =
|
|
2096
|
-
this.discontinuityToTso_.get(discontintuitySequenceNum);
|
|
2097
|
-
} else {
|
|
2098
|
-
const mediaStartTime = await this.getStartTime_(
|
|
2099
|
-
verbatimMediaPlaylistUri, initSegmentRef, mimeType,
|
|
2100
|
-
mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
|
|
2101
|
-
variables, type, codecs, bandwidth);
|
|
2102
|
-
timestampOffset = startTime - mediaStartTime;
|
|
2103
|
-
shaka.log.v1('Segment timestampOffset =', timestampOffset);
|
|
2104
|
-
this.discontinuityToTso_.set(
|
|
2105
|
-
discontintuitySequenceNum, timestampOffset);
|
|
2106
|
-
}
|
|
2107
|
-
return timestampOffset;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
/**
|
|
2111
|
-
* Try to fetch the starting part of a segment, and fall back to a full
|
|
2112
|
-
* segment if we have to.
|
|
2113
|
-
*
|
|
2114
|
-
* @param {!shaka.media.AnySegmentReference} reference
|
|
2115
|
-
* @param {string} type
|
|
2116
|
-
* @param {string} mimeType
|
|
2117
|
-
* @param {string} codecs
|
|
2118
|
-
* @param {number|undefined} bandwidth
|
|
2119
|
-
* @return {!Promise.<shaka.extern.Response>}
|
|
2120
|
-
* @private
|
|
2121
|
-
*/
|
|
2122
|
-
async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
|
|
2123
|
-
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2124
|
-
|
|
2125
|
-
// Create two requests:
|
|
2126
|
-
// 1. A partial request meant to fetch the smallest part of the segment
|
|
2127
|
-
// required to get the time stamp.
|
|
2128
|
-
// 2. A full request meant as a fallback for when the server does not
|
|
2129
|
-
// support partial requests.
|
|
2130
|
-
const fullRequest = shaka.util.Networking.createSegmentRequest(
|
|
2131
|
-
reference.getUris(),
|
|
2132
|
-
reference.startByte,
|
|
2133
|
-
reference.endByte,
|
|
2134
|
-
this.config_.retryParameters);
|
|
2135
|
-
|
|
2136
|
-
// We can only add partial CMCD data here because the stream
|
|
2137
|
-
// and manifest objects are still being created
|
|
2138
|
-
this.playerInterface_.modifySegmentRequest(
|
|
2139
|
-
fullRequest,
|
|
2140
|
-
{
|
|
2141
|
-
type: type,
|
|
2142
|
-
init: reference instanceof shaka.media.InitSegmentReference,
|
|
2143
|
-
duration: reference.endTime - reference.startTime,
|
|
2144
|
-
mimeType: mimeType,
|
|
2145
|
-
codecs: codecs,
|
|
2146
|
-
bandwidth: bandwidth,
|
|
2147
|
-
},
|
|
2148
|
-
);
|
|
2149
|
-
|
|
2150
|
-
if (this.config_.hls.useFullSegmentsForStartTime) {
|
|
2151
|
-
return this.makeNetworkRequest_(fullRequest, requestType);
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
const partialRequest = shaka.util.Networking.createSegmentRequest(
|
|
2155
|
-
reference.getUris(),
|
|
2156
|
-
reference.startByte,
|
|
2157
|
-
reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
|
|
2158
|
-
this.config_.retryParameters);
|
|
2159
|
-
|
|
2160
|
-
this.playerInterface_.modifySegmentRequest(
|
|
2161
|
-
partialRequest,
|
|
2162
|
-
{
|
|
2163
|
-
type: type,
|
|
2164
|
-
init: reference instanceof shaka.media.InitSegmentReference,
|
|
2165
|
-
duration: reference.endTime - reference.startTime,
|
|
2166
|
-
mimeType: mimeType,
|
|
2167
|
-
codecs: codecs,
|
|
2168
|
-
bandwidth: bandwidth,
|
|
2169
|
-
},
|
|
2170
|
-
);
|
|
2171
|
-
|
|
2172
|
-
// TODO(vaage): The need to do fall back requests is not likely to be unique
|
|
2173
|
-
// to here. It would be nice if the fallback(s) could be included into
|
|
2174
|
-
// the same abortable operation as the original request.
|
|
2175
|
-
//
|
|
2176
|
-
// What would need to change with networking engine to support requests
|
|
2177
|
-
// with fallback(s)?
|
|
2178
|
-
try {
|
|
2179
|
-
const response = await this.makeNetworkRequest_(
|
|
2180
|
-
partialRequest, requestType);
|
|
2181
|
-
|
|
2182
|
-
return response;
|
|
2183
|
-
} catch (e) {
|
|
2184
|
-
// If the networking operation was aborted, we don't want to treat it as
|
|
2185
|
-
// a request failure. We surface the error so that the OPERATION_ABORTED
|
|
2186
|
-
// error will be handled correctly.
|
|
2187
|
-
if (e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
|
|
2188
|
-
throw e;
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
// The partial request may fail for a number of reasons.
|
|
2192
|
-
// Some servers do not support Range requests, and others do not support
|
|
2193
|
-
// the OPTIONS request which must be made before any cross-origin Range
|
|
2194
|
-
// request. Since this fallback is expensive, warn the app developer.
|
|
2195
|
-
shaka.log.alwaysWarn('Unable to fetch the starting part of HLS ' +
|
|
2196
|
-
'segment! Falling back to a full segment request, ' +
|
|
2197
|
-
'which is expensive! Your server should ' +
|
|
2198
|
-
'support Range requests and CORS preflights.',
|
|
2199
|
-
partialRequest.uris[0]);
|
|
2200
|
-
|
|
2201
|
-
const response = await this.makeNetworkRequest_(fullRequest, requestType);
|
|
2202
|
-
|
|
2203
|
-
return response;
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
/**
|
|
2208
|
-
* Gets the start time of a segment from the existing manifest (if possible)
|
|
2209
|
-
* or by downloading it and parsing it otherwise.
|
|
2210
|
-
*
|
|
2211
|
-
* @param {string} verbatimMediaPlaylistUri
|
|
2212
|
-
* @param {shaka.media.InitSegmentReference} initSegmentRef
|
|
2213
|
-
* @param {string} mimeType
|
|
2214
|
-
* @param {number} mediaSequenceNumber
|
|
2215
|
-
* @param {boolean} isDiscontinuity
|
|
2216
|
-
* @param {!shaka.hls.Segment} segment
|
|
2217
|
-
* @param {!Map.<string, string>} variables
|
|
2218
|
-
* @param {string} type
|
|
2219
|
-
* @param {string} codecs
|
|
2220
|
-
* @param {number|undefined} bandwidth
|
|
2221
|
-
* @return {!Promise.<number>}
|
|
2222
|
-
* @private
|
|
2223
|
-
*/
|
|
2224
|
-
async getStartTime_(
|
|
2225
|
-
verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
|
|
2226
|
-
isDiscontinuity, segment, variables, type, codecs, bandwidth) {
|
|
2227
|
-
const segmentRef = this.createSegmentReference_(
|
|
2228
|
-
initSegmentRef,
|
|
2229
|
-
/* previousReference= */ null,
|
|
2230
|
-
segment,
|
|
2231
|
-
/* startTime= */ 0,
|
|
2232
|
-
/* timestampOffset= */ 0,
|
|
2233
|
-
variables,
|
|
2234
|
-
/* absoluteMediaPlaylistUri= */ '',
|
|
2235
|
-
type);
|
|
2236
|
-
// If we are updating the manifest, we can usually skip fetching the segment
|
|
2237
|
-
// by examining the references we already have. This won't be possible if
|
|
2238
|
-
// there was some kind of lag or delay updating the manifest on the server,
|
|
2239
|
-
// in which extreme case we would fall back to fetching a segment. This
|
|
2240
|
-
// allows us to both avoid fetching segments when possible, and recover from
|
|
2241
|
-
// certain server-side issues gracefully.
|
|
2242
|
-
// Do not use cached start time for the segments with discontinuity tags.
|
|
2243
|
-
if (this.manifest_ && !isDiscontinuity) {
|
|
2244
|
-
const streamInfo =
|
|
2245
|
-
this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
|
|
2246
|
-
const startTime = streamInfo.mediaSequenceToStartTime.get(
|
|
2247
|
-
mediaSequenceNumber);
|
|
2248
|
-
if (startTime != undefined) {
|
|
2249
|
-
// We found it! Avoid fetching and parsing the segment.
|
|
2250
|
-
shaka.log.v1('Found segment start time in previous manifest',
|
|
2251
|
-
startTime);
|
|
2252
|
-
return startTime;
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
shaka.log.debug(
|
|
2256
|
-
'Unable to find segment start time in previous manifest!');
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
|
-
// TODO: Introduce a new tag to extend HLS and provide the first segment's
|
|
2260
|
-
// start time. This will avoid the need for these fetches in content
|
|
2261
|
-
// packaged with Shaka Packager. This web-friendly extension to HLS can
|
|
2262
|
-
// then be proposed to Apple for inclusion in a future version of HLS.
|
|
2263
|
-
// See https://github.com/google/shaka-packager/issues/294
|
|
2264
|
-
|
|
2265
|
-
shaka.log.v1('Fetching segment to find start time');
|
|
2266
|
-
mimeType = mimeType.toLowerCase();
|
|
2267
|
-
|
|
2268
|
-
if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
|
|
2269
|
-
// Raw formats contain no timestamps. Even if there is an ID3 tag with a
|
|
2270
|
-
// timestamp, that's not going to be honored by MediaSource, which will
|
|
2271
|
-
// use sequence mode for these segments. We don't yet support sequence
|
|
2272
|
-
// mode, so we must reject these streams.
|
|
2273
|
-
// TODO(#2337): Support sequence mode and align raw format timestamps to
|
|
2274
|
-
// other streams.
|
|
2275
|
-
shaka.log.alwaysWarn(
|
|
2276
|
-
'Raw formats are not yet supported. Skipping ' + mimeType);
|
|
2277
|
-
throw new shaka.util.Error(
|
|
2278
|
-
shaka.util.Error.Severity.RECOVERABLE,
|
|
2279
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2280
|
-
shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
if (mimeType == 'video/webm') {
|
|
2284
|
-
shaka.log.alwaysWarn('WebM in HLS is not yet supported. Skipping.');
|
|
2285
|
-
throw new shaka.util.Error(
|
|
2286
|
-
shaka.util.Error.Severity.RECOVERABLE,
|
|
2287
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2288
|
-
shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
|
|
2292
|
-
// We also need the init segment to get the correct timescale. But if the
|
|
2293
|
-
// stream is self-initializing, use the same response for both.
|
|
2294
|
-
const fetches = [this.fetchStartOfSegment_(
|
|
2295
|
-
segmentRef,
|
|
2296
|
-
type,
|
|
2297
|
-
mimeType,
|
|
2298
|
-
codecs,
|
|
2299
|
-
bandwidth,
|
|
2300
|
-
)];
|
|
2301
|
-
|
|
2302
|
-
if (initSegmentRef) {
|
|
2303
|
-
fetches.push(this.fetchStartOfSegment_(
|
|
2304
|
-
initSegmentRef,
|
|
2305
|
-
type,
|
|
2306
|
-
mimeType,
|
|
2307
|
-
codecs,
|
|
2308
|
-
bandwidth,
|
|
2309
|
-
));
|
|
2310
2119
|
}
|
|
2311
|
-
|
|
2312
|
-
const responses = await Promise.all(fetches);
|
|
2313
|
-
|
|
2314
|
-
// If the stream is self-initializing, use the main segment in-place of
|
|
2315
|
-
// the init segment.
|
|
2316
|
-
const segmentResponse = responses[0];
|
|
2317
|
-
const initSegmentResponse = responses[1] || responses[0];
|
|
2318
|
-
|
|
2319
|
-
return this.getStartTimeFromMp4Segment_(
|
|
2320
|
-
verbatimMediaPlaylistUri, segmentResponse.uri,
|
|
2321
|
-
segmentResponse.data, initSegmentResponse.data);
|
|
2322
|
-
}
|
|
2323
|
-
|
|
2324
|
-
if (mimeType == 'video/mp2t') {
|
|
2325
|
-
const response = await this.fetchStartOfSegment_(
|
|
2326
|
-
segmentRef,
|
|
2327
|
-
type,
|
|
2328
|
-
mimeType,
|
|
2329
|
-
codecs,
|
|
2330
|
-
bandwidth,
|
|
2331
|
-
);
|
|
2332
|
-
goog.asserts.assert(response.data, 'Should have a response body!');
|
|
2333
|
-
return this.getStartTimeFromTsSegment_(
|
|
2334
|
-
verbatimMediaPlaylistUri, response.uri, response.data);
|
|
2335
2120
|
}
|
|
2336
2121
|
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
* Parses an mp4 segment to get its start time.
|
|
2346
|
-
*
|
|
2347
|
-
* @param {string} playlistUri
|
|
2348
|
-
* @param {string} segmentUri
|
|
2349
|
-
* @param {BufferSource} mediaData
|
|
2350
|
-
* @param {BufferSource} initData
|
|
2351
|
-
* @return {number}
|
|
2352
|
-
* @private
|
|
2353
|
-
*/
|
|
2354
|
-
getStartTimeFromMp4Segment_(playlistUri, segmentUri, mediaData, initData) {
|
|
2355
|
-
const Mp4Parser = shaka.util.Mp4Parser;
|
|
2356
|
-
|
|
2357
|
-
let timescale = 0;
|
|
2358
|
-
new Mp4Parser()
|
|
2359
|
-
.box('moov', Mp4Parser.children)
|
|
2360
|
-
.box('trak', Mp4Parser.children)
|
|
2361
|
-
.box('mdia', Mp4Parser.children)
|
|
2362
|
-
.fullBox('mdhd', (box) => {
|
|
2363
|
-
goog.asserts.assert(
|
|
2364
|
-
box.version == 0 || box.version == 1,
|
|
2365
|
-
'MDHD version can only be 0 or 1');
|
|
2366
|
-
|
|
2367
|
-
const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
|
|
2368
|
-
box.reader, box.version);
|
|
2369
|
-
|
|
2370
|
-
timescale = parsedMDHDBox.timescale;
|
|
2371
|
-
box.parser.stop();
|
|
2372
|
-
}).parse(initData, /* partialOkay= */ true);
|
|
2373
|
-
|
|
2374
|
-
if (!timescale) {
|
|
2375
|
-
shaka.log.error('Unable to find timescale in init segment!');
|
|
2376
|
-
throw new shaka.util.Error(
|
|
2377
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2378
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2379
|
-
shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
|
|
2380
|
-
playlistUri, segmentUri);
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
let startTime = 0;
|
|
2384
|
-
let parsedMedia = false;
|
|
2385
|
-
new Mp4Parser()
|
|
2386
|
-
.box('moof', Mp4Parser.children)
|
|
2387
|
-
.box('traf', Mp4Parser.children)
|
|
2388
|
-
.fullBox('tfdt', (box) => {
|
|
2389
|
-
goog.asserts.assert(
|
|
2390
|
-
box.version == 0 || box.version == 1,
|
|
2391
|
-
'TFDT version can only be 0 or 1');
|
|
2392
|
-
|
|
2393
|
-
const parsedTFDTBox = shaka.util.Mp4BoxParsers.parseTFDT(
|
|
2394
|
-
box.reader, box.version);
|
|
2395
|
-
const baseTime = parsedTFDTBox.baseMediaDecodeTime;
|
|
2396
|
-
startTime = baseTime / timescale;
|
|
2397
|
-
parsedMedia = true;
|
|
2398
|
-
box.parser.stop();
|
|
2399
|
-
}).parse(mediaData, /* partialOkay= */ true);
|
|
2400
|
-
|
|
2401
|
-
if (!parsedMedia) {
|
|
2402
|
-
throw new shaka.util.Error(
|
|
2403
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2404
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2405
|
-
shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
|
|
2406
|
-
playlistUri, segmentUri);
|
|
2407
|
-
}
|
|
2408
|
-
return startTime;
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
/**
|
|
2412
|
-
* Parses a TS segment to get its start time.
|
|
2413
|
-
*
|
|
2414
|
-
* @param {string} playlistUri
|
|
2415
|
-
* @param {string} segmentUri
|
|
2416
|
-
* @param {BufferSource} data
|
|
2417
|
-
* @return {number}
|
|
2418
|
-
* @private
|
|
2419
|
-
*/
|
|
2420
|
-
getStartTimeFromTsSegment_(playlistUri, segmentUri, data) {
|
|
2421
|
-
const reader = new shaka.util.DataViewReader(
|
|
2422
|
-
data, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
|
|
2423
|
-
|
|
2424
|
-
const fail = () => {
|
|
2425
|
-
throw new shaka.util.Error(
|
|
2426
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2427
|
-
shaka.util.Error.Category.MANIFEST,
|
|
2428
|
-
shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
|
|
2429
|
-
playlistUri, segmentUri);
|
|
2430
|
-
};
|
|
2431
|
-
|
|
2432
|
-
let packetStart = 0;
|
|
2433
|
-
let syncByte = 0;
|
|
2434
|
-
|
|
2435
|
-
const skipPacket = () => {
|
|
2436
|
-
// 188-byte packets are standard, so assume that.
|
|
2437
|
-
reader.seek(packetStart + 188);
|
|
2438
|
-
syncByte = reader.readUint8();
|
|
2439
|
-
if (syncByte != 0x47) {
|
|
2440
|
-
// We haven't found the sync byte, so try it as a 192-byte packet.
|
|
2441
|
-
reader.seek(packetStart + 192);
|
|
2442
|
-
syncByte = reader.readUint8();
|
|
2443
|
-
}
|
|
2444
|
-
if (syncByte != 0x47) {
|
|
2445
|
-
// We still haven't found the sync byte, so try as a 204-byte packet.
|
|
2446
|
-
reader.seek(packetStart + 204);
|
|
2447
|
-
syncByte = reader.readUint8();
|
|
2448
|
-
}
|
|
2449
|
-
if (syncByte != 0x47) {
|
|
2450
|
-
// We still haven't found the sync byte, so the packet was of a
|
|
2451
|
-
// non-standard size.
|
|
2452
|
-
fail();
|
|
2453
|
-
}
|
|
2454
|
-
// Put the sync byte back so we can read it in the next loop.
|
|
2455
|
-
reader.rewind(1);
|
|
2456
|
-
};
|
|
2457
|
-
|
|
2458
|
-
// We will look a few packet-lengths forward to find the first sync byte.
|
|
2459
|
-
// Note that we are using this method on what is already a subset of the
|
|
2460
|
-
// file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
|
|
2461
|
-
// we can't look too far ahead to begin with.
|
|
2462
|
-
let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
|
|
2463
|
-
|
|
2464
|
-
// TODO: refactor this while loop for better readability.
|
|
2465
|
-
// eslint-disable-next-line no-constant-condition
|
|
2466
|
-
while (true) {
|
|
2467
|
-
// Format reference: https://bit.ly/TsPacket
|
|
2468
|
-
packetStart = reader.getPosition();
|
|
2469
|
-
|
|
2470
|
-
syncByte = reader.readUint8();
|
|
2471
|
-
if (syncByte != 0x47) {
|
|
2472
|
-
if (syncByteScanLength > 0) {
|
|
2473
|
-
// This file could have started with a cut-off TS packet. Scan forward
|
|
2474
|
-
// until we find a sync byte.
|
|
2475
|
-
syncByteScanLength -= 1;
|
|
2122
|
+
// If some segments have sync times, but not all, extrapolate the sync
|
|
2123
|
+
// times of the ones with none.
|
|
2124
|
+
const someSyncTime = references.some((ref) => ref.syncTime != null);
|
|
2125
|
+
if (someSyncTime) {
|
|
2126
|
+
for (let i = 0; i < references.length; i++) {
|
|
2127
|
+
const reference = references[i];
|
|
2128
|
+
if (reference.syncTime != null) {
|
|
2129
|
+
// No need to extrapolate.
|
|
2476
2130
|
continue;
|
|
2477
2131
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
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
|
-
const pesHeaderLengthRemaining = reader.readUint8();
|
|
2531
|
-
if (pesHeaderLengthRemaining == 0) {
|
|
2532
|
-
fail();
|
|
2132
|
+
// Find the nearest segment with syncTime, in either direction.
|
|
2133
|
+
// This looks forward and backward simultaneously, keeping track of what
|
|
2134
|
+
// to offset the syncTime it finds by as it goes.
|
|
2135
|
+
let forwardAdd = 0;
|
|
2136
|
+
let forwardI = i;
|
|
2137
|
+
/**
|
|
2138
|
+
* Look forwards one reference at a time, summing all durations as we
|
|
2139
|
+
* go, until we find a reference with a syncTime to use as a basis.
|
|
2140
|
+
* This DOES count the original reference, but DOESN'T count the first
|
|
2141
|
+
* reference with a syncTime (as we approach it from behind).
|
|
2142
|
+
* @return {?number}
|
|
2143
|
+
*/
|
|
2144
|
+
const lookForward = () => {
|
|
2145
|
+
const other = references[forwardI];
|
|
2146
|
+
if (other) {
|
|
2147
|
+
if (other.syncTime != null) {
|
|
2148
|
+
return other.syncTime + forwardAdd;
|
|
2149
|
+
}
|
|
2150
|
+
forwardAdd -= other.endTime - other.startTime;
|
|
2151
|
+
forwardI += 1;
|
|
2152
|
+
}
|
|
2153
|
+
return null;
|
|
2154
|
+
};
|
|
2155
|
+
let backwardAdd = 0;
|
|
2156
|
+
let backwardI = i;
|
|
2157
|
+
/**
|
|
2158
|
+
* Look backwards one reference at a time, summing all durations as we
|
|
2159
|
+
* go, until we find a reference with a syncTime to use as a basis.
|
|
2160
|
+
* This DOESN'T count the original reference, but DOES count the first
|
|
2161
|
+
* reference with a syncTime (as we approach it from ahead).
|
|
2162
|
+
* @return {?number}
|
|
2163
|
+
*/
|
|
2164
|
+
const lookBackward = () => {
|
|
2165
|
+
const other = references[backwardI];
|
|
2166
|
+
if (other) {
|
|
2167
|
+
if (other != reference) {
|
|
2168
|
+
backwardAdd += other.endTime - other.startTime;
|
|
2169
|
+
}
|
|
2170
|
+
if (other.syncTime != null) {
|
|
2171
|
+
return other.syncTime + backwardAdd;
|
|
2172
|
+
}
|
|
2173
|
+
backwardI -= 1;
|
|
2174
|
+
}
|
|
2175
|
+
return null;
|
|
2176
|
+
};
|
|
2177
|
+
while (reference.syncTime == null) {
|
|
2178
|
+
reference.syncTime = lookBackward();
|
|
2179
|
+
if (reference.syncTime == null) {
|
|
2180
|
+
reference.syncTime = lookForward();
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2533
2183
|
}
|
|
2184
|
+
}
|
|
2534
2185
|
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2186
|
+
// Split the sync times properly among partial segments.
|
|
2187
|
+
if (someSyncTime) {
|
|
2188
|
+
for (const reference of references) {
|
|
2189
|
+
let syncTime = reference.syncTime;
|
|
2190
|
+
for (const partial of reference.partialReferences) {
|
|
2191
|
+
partial.syncTime = syncTime;
|
|
2192
|
+
syncTime += partial.endTime - partial.startTime;
|
|
2193
|
+
}
|
|
2539
2194
|
}
|
|
2540
|
-
|
|
2541
|
-
const pts0 = reader.readUint8();
|
|
2542
|
-
const pts1 = reader.readUint16();
|
|
2543
|
-
const pts2 = reader.readUint16();
|
|
2544
|
-
// Reconstruct 33-bit PTS from the 5-byte, padded structure.
|
|
2545
|
-
const ptsHigh3 = (pts0 & 0x0e) >> 1;
|
|
2546
|
-
const ptsLow30 = ((pts1 & 0xfffe) << 14) | ((pts2 & 0xfffe) >> 1);
|
|
2547
|
-
// Reconstruct the PTS as a float. Avoid bitwise operations to combine
|
|
2548
|
-
// because bitwise ops treat the values as 32-bit ints.
|
|
2549
|
-
const pts = ptsHigh3 * (1 << 30) + ptsLow30;
|
|
2550
|
-
return pts / shaka.hls.HlsParser.TS_TIMESCALE_;
|
|
2551
2195
|
}
|
|
2196
|
+
|
|
2197
|
+
return references;
|
|
2552
2198
|
}
|
|
2553
2199
|
|
|
2554
2200
|
/**
|
|
@@ -2615,11 +2261,10 @@ shaka.hls.HlsParser = class {
|
|
|
2615
2261
|
|
|
2616
2262
|
if (contentType == ContentType.TEXT) {
|
|
2617
2263
|
// The extension map didn't work.
|
|
2618
|
-
if (
|
|
2264
|
+
if (codecs == 'vtt' || codecs == 'wvtt') {
|
|
2619
2265
|
// If codecs is 'vtt', it's WebVTT.
|
|
2620
|
-
// If there was no codecs string, assume HLS text streams are WebVTT.
|
|
2621
2266
|
return 'text/vtt';
|
|
2622
|
-
} else {
|
|
2267
|
+
} else if (codecs && codecs !== '') {
|
|
2623
2268
|
// Otherwise, assume MP4-embedded text, since text-based formats tend
|
|
2624
2269
|
// not to have a codecs string at all.
|
|
2625
2270
|
return 'application/mp4';
|
|
@@ -2644,6 +2289,11 @@ shaka.hls.HlsParser = class {
|
|
|
2644
2289
|
const contentMimeType = response.headers['content-type'];
|
|
2645
2290
|
|
|
2646
2291
|
if (!contentMimeType) {
|
|
2292
|
+
if (contentType == ContentType.TEXT) {
|
|
2293
|
+
// If there was no codecs string and no content-type, assume HLS text
|
|
2294
|
+
// streams are WebVTT.
|
|
2295
|
+
return 'text/vtt';
|
|
2296
|
+
}
|
|
2647
2297
|
// If the HLS content is lacking in both MIME type metadata and
|
|
2648
2298
|
// segment file extensions, we fall back to assuming it's MP4.
|
|
2649
2299
|
const fallbackMimeType = map['mp4'];
|
|
@@ -2809,6 +2459,33 @@ shaka.hls.HlsParser = class {
|
|
|
2809
2459
|
return op.promise;
|
|
2810
2460
|
}
|
|
2811
2461
|
|
|
2462
|
+
/**
|
|
2463
|
+
* @param {!shaka.hls.Tag} drmTag
|
|
2464
|
+
* @param {string} mimeType
|
|
2465
|
+
* @return {?shaka.extern.DrmInfo}
|
|
2466
|
+
* @private
|
|
2467
|
+
*/
|
|
2468
|
+
static fairplayDrmParser_(drmTag, mimeType) {
|
|
2469
|
+
if (mimeType == 'video/mp2t') {
|
|
2470
|
+
throw new shaka.util.Error(
|
|
2471
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2472
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2473
|
+
shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
/*
|
|
2477
|
+
* Even if we're not able to construct initData through the HLS tag, adding
|
|
2478
|
+
* a DRMInfo will allow DRM Engine to request a media key system access
|
|
2479
|
+
* with the correct keySystem and initDataType
|
|
2480
|
+
*/
|
|
2481
|
+
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
2482
|
+
'com.apple.fps', [
|
|
2483
|
+
{initDataType: 'sinf', initData: new Uint8Array(0)},
|
|
2484
|
+
]);
|
|
2485
|
+
|
|
2486
|
+
return drmInfo;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2812
2489
|
/**
|
|
2813
2490
|
* @param {!shaka.hls.Tag} drmTag
|
|
2814
2491
|
* @return {?shaka.extern.DrmInfo}
|
|
@@ -2871,7 +2548,10 @@ shaka.hls.HlsParser = class {
|
|
|
2871
2548
|
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
|
|
2872
2549
|
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
|
|
2873
2550
|
]);
|
|
2874
|
-
const
|
|
2551
|
+
const keyIds = new Set();
|
|
2552
|
+
const psshVersion = 0;
|
|
2553
|
+
const pssh =
|
|
2554
|
+
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
|
|
2875
2555
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
2876
2556
|
'com.microsoft.playready', [
|
|
2877
2557
|
{initDataType: 'cenc', initData: pssh},
|
|
@@ -2890,7 +2570,6 @@ shaka.hls.HlsParser = class {
|
|
|
2890
2570
|
* minTimestamp: number,
|
|
2891
2571
|
* maxTimestamp: number,
|
|
2892
2572
|
* mediaSequenceToStartTime: !Map.<number, number>,
|
|
2893
|
-
* discontinuityToMediaSequence: !Map.<number, number>,
|
|
2894
2573
|
* canSkipSegments: boolean
|
|
2895
2574
|
* }}
|
|
2896
2575
|
*
|
|
@@ -2913,9 +2592,6 @@ shaka.hls.HlsParser = class {
|
|
|
2913
2592
|
* The maximum timestamp found in the stream.
|
|
2914
2593
|
* @property {!Map.<number, number>} mediaSequenceToStartTime
|
|
2915
2594
|
* A map of media sequence numbers to media start times.
|
|
2916
|
-
* @property {!Map.<number, number>} discontinuityToMediaSequence
|
|
2917
|
-
* A map of discontinuity sequence numbers to the media sequence number of the
|
|
2918
|
-
* segment starting with that discontinuity sequence number.
|
|
2919
2595
|
* @property {boolean} canSkipSegments
|
|
2920
2596
|
* True if the server supports delta playlist updates, and we can send a
|
|
2921
2597
|
* request for a playlist that can skip older media segments.
|
|
@@ -2959,21 +2635,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
2959
2635
|
};
|
|
2960
2636
|
|
|
2961
2637
|
|
|
2962
|
-
/**
|
|
2963
|
-
* MIME types of raw formats.
|
|
2964
|
-
* TODO(#2337): Support raw formats and share this list among parsers.
|
|
2965
|
-
*
|
|
2966
|
-
* @const {!Array.<string>}
|
|
2967
|
-
* @private
|
|
2968
|
-
*/
|
|
2969
|
-
shaka.hls.HlsParser.RAW_FORMATS_ = [
|
|
2970
|
-
'audio/aac',
|
|
2971
|
-
'audio/ac3',
|
|
2972
|
-
'audio/ec3',
|
|
2973
|
-
'audio/mpeg',
|
|
2974
|
-
];
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
2638
|
/**
|
|
2978
2639
|
* @const {!Object.<string, string>}
|
|
2979
2640
|
* @private
|
|
@@ -3031,7 +2692,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
|
|
|
3031
2692
|
|
|
3032
2693
|
|
|
3033
2694
|
/**
|
|
3034
|
-
* @typedef {function(!shaka.hls.Tag):?shaka.extern.DrmInfo}
|
|
2695
|
+
* @typedef {function(!shaka.hls.Tag, string):?shaka.extern.DrmInfo}
|
|
3035
2696
|
* @private
|
|
3036
2697
|
*/
|
|
3037
2698
|
shaka.hls.HlsParser.DrmParser_;
|
|
@@ -3042,10 +2703,8 @@ shaka.hls.HlsParser.DrmParser_;
|
|
|
3042
2703
|
* @private
|
|
3043
2704
|
*/
|
|
3044
2705
|
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
|
|
3045
|
-
/* TODO: https://github.com/google/shaka-player/issues/382
|
|
3046
2706
|
'com.apple.streamingkeydelivery':
|
|
3047
2707
|
shaka.hls.HlsParser.fairplayDrmParser_,
|
|
3048
|
-
*/
|
|
3049
2708
|
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
|
|
3050
2709
|
shaka.hls.HlsParser.widevineDrmParser_,
|
|
3051
2710
|
'com.microsoft.playready':
|
|
@@ -3063,28 +2722,13 @@ shaka.hls.HlsParser.PresentationType_ = {
|
|
|
3063
2722
|
LIVE: 'LIVE',
|
|
3064
2723
|
};
|
|
3065
2724
|
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
shaka.
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
* need to know the segment start time. This allows us to avoid fetching the
|
|
3077
|
-
* entire segment in many cases.
|
|
3078
|
-
*
|
|
3079
|
-
* @const {number}
|
|
3080
|
-
* @private
|
|
3081
|
-
*/
|
|
3082
|
-
shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ = 2048;
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
shaka.media.ManifestParser.registerParserByExtension(
|
|
3086
|
-
'm3u8', () => new shaka.hls.HlsParser());
|
|
3087
|
-
shaka.media.ManifestParser.registerParserByMime(
|
|
3088
|
-
'application/x-mpegurl', () => new shaka.hls.HlsParser());
|
|
3089
|
-
shaka.media.ManifestParser.registerParserByMime(
|
|
3090
|
-
'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
|
|
2725
|
+
if (!shaka.util.Platform.isTizen3() &&
|
|
2726
|
+
!shaka.util.Platform.isTizen2() &&
|
|
2727
|
+
!shaka.util.Platform.isWebOS3()) {
|
|
2728
|
+
shaka.media.ManifestParser.registerParserByExtension(
|
|
2729
|
+
'm3u8', () => new shaka.hls.HlsParser());
|
|
2730
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
2731
|
+
'application/x-mpegurl', () => new shaka.hls.HlsParser());
|
|
2732
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
2733
|
+
'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
|
|
2734
|
+
}
|