shaka-player 4.3.5 → 4.3.7
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/workflows/demo-version-index.yaml +3 -3
- package/.github/workflows/selenium-lab-tests.yaml +23 -1
- package/AUTHORS +2 -1
- package/CHANGELOG.md +53 -0
- package/CONTRIBUTORS +4 -1
- package/README.md +3 -0
- package/demo/demo.less +1 -0
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +167 -296
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +167 -296
- package/dist/demo.compiled.map +4 -4
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +1 -1
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +34 -63
- package/dist/receiver.compiled.debug.map +4 -4
- package/dist/receiver.compiled.js +34 -63
- package/dist/receiver.compiled.map +4 -4
- package/dist/shaka-player.compiled.d.ts +20 -3
- package/dist/shaka-player.compiled.debug.d.ts +20 -3
- package/dist/shaka-player.compiled.debug.externs.js +29 -14
- package/dist/shaka-player.compiled.debug.js +303 -292
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +29 -14
- package/dist/shaka-player.compiled.js +886 -879
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +20 -3
- package/dist/shaka-player.ui.debug.d.ts +20 -3
- package/dist/shaka-player.ui.debug.externs.js +17 -2
- package/dist/shaka-player.ui.debug.js +321 -309
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +17 -2
- package/dist/shaka-player.ui.js +1027 -1021
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/a11y.md +0 -4
- package/docs/tutorials/ad_monetization.md +0 -4
- package/docs/tutorials/basic-usage.md +0 -5
- package/docs/tutorials/config.md +0 -5
- package/docs/tutorials/debugging.md +0 -5
- package/docs/tutorials/drm-config.md +0 -5
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/license-server-auth.md +0 -5
- package/docs/tutorials/license-wrapping.md +0 -5
- package/docs/tutorials/network-and-buffering-config.md +0 -5
- package/docs/tutorials/ui-customization.md +0 -5
- package/docs/tutorials/ui.md +0 -7
- package/docs/tutorials/welcome.md +1 -6
- package/externs/network_information.js +7 -2
- package/externs/shaka/abr_manager.js +6 -0
- package/externs/shaka/net.js +1 -0
- package/externs/shaka/player.js +13 -7
- package/lib/abr/simple_abr_manager.js +27 -3
- package/lib/ads/client_side_ad_manager.js +6 -3
- package/lib/cea/mp4_cea_parser.js +5 -2
- package/lib/dash/dash_parser.js +49 -48
- package/lib/debug/log.js +9 -13
- package/lib/hls/hls_parser.js +60 -7
- package/lib/hls/manifest_text_parser.js +10 -1
- package/lib/media/drm_engine.js +9 -6
- package/lib/media/media_source_engine.js +33 -13
- package/lib/media/presentation_timeline.js +9 -2
- package/lib/media/segment_index.js +2 -0
- package/lib/media/segment_reference.js +25 -2
- package/lib/media/streaming_engine.js +40 -11
- package/lib/net/http_fetch_plugin.js +79 -57
- package/lib/net/networking_engine.js +1 -0
- package/lib/player.js +26 -3
- package/lib/polyfill/media_capabilities.js +13 -0
- package/lib/text/mp4_vtt_parser.js +4 -0
- package/lib/text/text_engine.js +1 -0
- package/lib/util/manifest_parser_utils.js +1 -2
- package/lib/util/mime_utils.js +11 -0
- package/lib/util/periods.js +5 -5
- package/lib/util/stream_utils.js +2 -2
- package/maintained-branches.md +2 -3
- package/package.json +8 -3
- package/test/cea/mp4_cea_parser_unit.js +19 -0
- package/test/dash/dash_parser_live_unit.js +3 -2
- package/test/hls/hls_live_unit.js +3 -1
- package/test/hls/hls_parser_unit.js +156 -0
- package/test/media/content_workarounds_unit.js +43 -0
- package/test/media/drm_engine_integration.js +4 -17
- package/test/media/media_source_engine_unit.js +30 -0
- package/test/media/streaming_engine_unit.js +6 -1
- package/test/net/networking_engine_unit.js +6 -1
- package/test/player_integration.js +39 -0
- package/test/player_unit.js +57 -0
- package/test/polyfill/media_capabilities_unit.js +1 -0
- package/test/test/assets/h265-cea-init.mp4 +0 -0
- package/test/test/assets/h265-cea-segment.mp4 +0 -0
- package/test/test/assets/text-clip-alt.vtt +63 -0
- package/test/test/util/fake_drm_engine.js +3 -0
- package/test/test/util/fake_media_source_engine.js +4 -0
- package/test/test/util/layout_tests.js +4 -3
- package/test/test/util/simple_fakes.js +6 -0
- package/test/test/util/test_scheme.js +9 -14
- package/test/text/mp4_vtt_parser_unit.js +8 -0
- package/test/util/mime_utils_unit.js +10 -0
- package/ui/less/containers.less +4 -0
package/lib/hls/hls_parser.js
CHANGED
|
@@ -319,6 +319,9 @@ shaka.hls.HlsParser = class {
|
|
|
319
319
|
this.presentationTimeline_.setDuration(Math.min(...maxTimestamps));
|
|
320
320
|
this.playerInterface_.updateDuration();
|
|
321
321
|
}
|
|
322
|
+
if (stillLive) {
|
|
323
|
+
this.determineDuration_();
|
|
324
|
+
}
|
|
322
325
|
}
|
|
323
326
|
|
|
324
327
|
/**
|
|
@@ -400,9 +403,16 @@ shaka.hls.HlsParser = class {
|
|
|
400
403
|
mediaSequenceNumber);
|
|
401
404
|
stream.segmentIndex.evict(playlistStartTime);
|
|
402
405
|
}
|
|
406
|
+
const oldSegment = segments[0];
|
|
407
|
+
goog.asserts.assert(oldSegment, 'Should have segments!');
|
|
408
|
+
|
|
409
|
+
streamInfo.minTimestamp = oldSegment.startTime;
|
|
410
|
+
|
|
403
411
|
const newestSegment = segments[segments.length - 1];
|
|
404
412
|
goog.asserts.assert(newestSegment, 'Should have segments!');
|
|
405
413
|
|
|
414
|
+
streamInfo.maxTimestamp = newestSegment.endTime;
|
|
415
|
+
|
|
406
416
|
// Once the last segment has been added to the playlist,
|
|
407
417
|
// #EXT-X-ENDLIST tag will be appended.
|
|
408
418
|
// If that happened, treat the rest of the EVENT presentation as VOD.
|
|
@@ -413,7 +423,6 @@ shaka.hls.HlsParser = class {
|
|
|
413
423
|
// Flag this for later. We don't convert the whole presentation into VOD
|
|
414
424
|
// until we've seen the ENDLIST tag for all active playlists.
|
|
415
425
|
streamInfo.hasEndList = true;
|
|
416
|
-
streamInfo.maxTimestamp = newestSegment.endTime;
|
|
417
426
|
}
|
|
418
427
|
}
|
|
419
428
|
|
|
@@ -576,6 +585,9 @@ shaka.hls.HlsParser = class {
|
|
|
576
585
|
* @private
|
|
577
586
|
*/
|
|
578
587
|
offsetStreamInfo_(streamInfo, offset) {
|
|
588
|
+
// Adjust our accounting of the minimum timestamp.
|
|
589
|
+
streamInfo.minTimestamp += offset;
|
|
590
|
+
|
|
579
591
|
// Adjust our accounting of the maximum timestamp.
|
|
580
592
|
streamInfo.maxTimestamp += offset;
|
|
581
593
|
goog.asserts.assert(streamInfo.maxTimestamp >= 0,
|
|
@@ -804,7 +816,7 @@ shaka.hls.HlsParser = class {
|
|
|
804
816
|
const PresentationType = shaka.hls.HlsParser.PresentationType_;
|
|
805
817
|
|
|
806
818
|
if (this.presentationType_ == PresentationType.LIVE) {
|
|
807
|
-
let segmentAvailabilityDuration = this.
|
|
819
|
+
let segmentAvailabilityDuration = this.getLiveDuration_();
|
|
808
820
|
|
|
809
821
|
// This defaults to the presentation delay, which has the effect of
|
|
810
822
|
// making the live stream unseekable. This is consistent with Apple's
|
|
@@ -1458,9 +1470,9 @@ shaka.hls.HlsParser = class {
|
|
|
1458
1470
|
let codecs = '';
|
|
1459
1471
|
/** @type {string} */
|
|
1460
1472
|
const type = this.getType_(tag);
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1473
|
+
if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) {
|
|
1474
|
+
codecs = tag.getAttributeValue('CODECS') || '';
|
|
1475
|
+
} else if (groupId && this.groupIdToCodecsMap_.has(groupId)) {
|
|
1464
1476
|
codecs = this.groupIdToCodecsMap_.get(groupId);
|
|
1465
1477
|
}
|
|
1466
1478
|
|
|
@@ -1655,6 +1667,7 @@ shaka.hls.HlsParser = class {
|
|
|
1655
1667
|
verbatimMediaPlaylistUri,
|
|
1656
1668
|
// These values are filled out or updated after lazy-loading:
|
|
1657
1669
|
absoluteMediaPlaylistUri: initialMediaPlaylistUri,
|
|
1670
|
+
minTimestamp: 0,
|
|
1658
1671
|
maxTimestamp: 0,
|
|
1659
1672
|
mediaSequenceToStartTime: new Map(),
|
|
1660
1673
|
canSkipSegments: false,
|
|
@@ -1700,6 +1713,7 @@ shaka.hls.HlsParser = class {
|
|
|
1700
1713
|
|
|
1701
1714
|
// Copy values from the real stream info to our initial one.
|
|
1702
1715
|
streamInfo.absoluteMediaPlaylistUri = absoluteMediaPlaylistUri;
|
|
1716
|
+
streamInfo.minTimestamp = realStreamInfo.minTimestamp;
|
|
1703
1717
|
streamInfo.maxTimestamp = realStreamInfo.maxTimestamp;
|
|
1704
1718
|
streamInfo.canSkipSegments = realStreamInfo.canSkipSegments;
|
|
1705
1719
|
streamInfo.hasEndList = realStreamInfo.hasEndList;
|
|
@@ -1736,6 +1750,13 @@ shaka.hls.HlsParser = class {
|
|
|
1736
1750
|
}
|
|
1737
1751
|
}
|
|
1738
1752
|
|
|
1753
|
+
if (type == ContentType.TEXT) {
|
|
1754
|
+
const firstSegment = realStream.segmentIndex.get(0);
|
|
1755
|
+
if (firstSegment && firstSegment.initSegmentReference) {
|
|
1756
|
+
stream.mimeType = 'application/mp4';
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1739
1760
|
// Add finishing touches to the stream that can only be done once we have
|
|
1740
1761
|
// more full context on the media as a whole.
|
|
1741
1762
|
if (this.hasEnoughInfoToFinalizeStreams_()) {
|
|
@@ -1826,6 +1847,22 @@ shaka.hls.HlsParser = class {
|
|
|
1826
1847
|
return minDuration;
|
|
1827
1848
|
}
|
|
1828
1849
|
|
|
1850
|
+
/**
|
|
1851
|
+
* @return {number}
|
|
1852
|
+
* @private
|
|
1853
|
+
*/
|
|
1854
|
+
getLiveDuration_() {
|
|
1855
|
+
let maxTimestamp = Infinity;
|
|
1856
|
+
let minTimestamp = Infinity;
|
|
1857
|
+
for (const streamInfo of this.uriToStreamInfosMap_.values()) {
|
|
1858
|
+
if (streamInfo.stream.segmentIndex && streamInfo.stream.type != 'text') {
|
|
1859
|
+
maxTimestamp = Math.min(maxTimestamp, streamInfo.maxTimestamp);
|
|
1860
|
+
minTimestamp = Math.min(minTimestamp, streamInfo.minTimestamp);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return maxTimestamp - minTimestamp;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1829
1866
|
/**
|
|
1830
1867
|
* @param {!Array.<!shaka.extern.Stream>} streams
|
|
1831
1868
|
* @private
|
|
@@ -1971,6 +2008,7 @@ shaka.hls.HlsParser = class {
|
|
|
1971
2008
|
this.determinePresentationType_(playlist);
|
|
1972
2009
|
}
|
|
1973
2010
|
|
|
2011
|
+
const firstStartTime = segments[0].startTime;
|
|
1974
2012
|
const lastEndTime = segments[segments.length - 1].endTime;
|
|
1975
2013
|
/** @type {!shaka.media.SegmentIndex} */
|
|
1976
2014
|
const segmentIndex = new shaka.media.SegmentIndex(segments);
|
|
@@ -1993,6 +2031,7 @@ shaka.hls.HlsParser = class {
|
|
|
1993
2031
|
type,
|
|
1994
2032
|
verbatimMediaPlaylistUri,
|
|
1995
2033
|
absoluteMediaPlaylistUri,
|
|
2034
|
+
minTimestamp: firstStartTime,
|
|
1996
2035
|
maxTimestamp: lastEndTime,
|
|
1997
2036
|
canSkipSegments,
|
|
1998
2037
|
hasEndList: false,
|
|
@@ -2371,13 +2410,15 @@ shaka.hls.HlsParser = class {
|
|
|
2371
2410
|
* @param {!Map.<string, string>} variables
|
|
2372
2411
|
* @param {string} absoluteMediaPlaylistUri
|
|
2373
2412
|
* @param {string} type
|
|
2413
|
+
* @param {number} timestampOffset
|
|
2374
2414
|
* @param {shaka.extern.HlsAes128Key=} hlsAes128Key
|
|
2375
2415
|
* @return {shaka.media.SegmentReference}
|
|
2376
2416
|
* @private
|
|
2377
2417
|
*/
|
|
2378
2418
|
createSegmentReference_(
|
|
2379
2419
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
2380
|
-
variables, absoluteMediaPlaylistUri, type,
|
|
2420
|
+
variables, absoluteMediaPlaylistUri, type, timestampOffset,
|
|
2421
|
+
hlsAes128Key) {
|
|
2381
2422
|
const tags = hlsSegment.tags;
|
|
2382
2423
|
const absoluteSegmentUri = this.variableSubstitution_(
|
|
2383
2424
|
hlsSegment.absoluteUri, variables);
|
|
@@ -2489,6 +2530,9 @@ shaka.hls.HlsParser = class {
|
|
|
2489
2530
|
/* tileDuration= */ null,
|
|
2490
2531
|
/* syncTime= */ null,
|
|
2491
2532
|
partialStatus);
|
|
2533
|
+
if (item.name == 'EXT-X-PRELOAD-HINT') {
|
|
2534
|
+
partial.markAsPreload();
|
|
2535
|
+
}
|
|
2492
2536
|
partialSegmentRefs.push(partial);
|
|
2493
2537
|
} // for-loop of hlsSegment.partialSegments
|
|
2494
2538
|
}
|
|
@@ -2547,7 +2591,7 @@ shaka.hls.HlsParser = class {
|
|
|
2547
2591
|
startByte,
|
|
2548
2592
|
endByte,
|
|
2549
2593
|
initSegmentReference,
|
|
2550
|
-
|
|
2594
|
+
timestampOffset, // This value is ignored in sequence mode.
|
|
2551
2595
|
/* appendWindowStart= */ 0,
|
|
2552
2596
|
/* appendWindowEnd= */ Infinity,
|
|
2553
2597
|
partialSegmentRefs,
|
|
@@ -2632,6 +2676,7 @@ shaka.hls.HlsParser = class {
|
|
|
2632
2676
|
const references = [];
|
|
2633
2677
|
|
|
2634
2678
|
let previousReference = null;
|
|
2679
|
+
let lastDiscontinuityStartTime = firstStartTime;
|
|
2635
2680
|
|
|
2636
2681
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2637
2682
|
const item = hlsSegments[i];
|
|
@@ -2643,6 +2688,7 @@ shaka.hls.HlsParser = class {
|
|
|
2643
2688
|
item.tags, 'EXT-X-DISCONTINUITY');
|
|
2644
2689
|
if (discontinuityTag) {
|
|
2645
2690
|
discontinuitySequence++;
|
|
2691
|
+
lastDiscontinuityStartTime = startTime;
|
|
2646
2692
|
}
|
|
2647
2693
|
|
|
2648
2694
|
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
@@ -2681,6 +2727,7 @@ shaka.hls.HlsParser = class {
|
|
|
2681
2727
|
variables,
|
|
2682
2728
|
playlist.absoluteUri,
|
|
2683
2729
|
type,
|
|
2730
|
+
lastDiscontinuityStartTime,
|
|
2684
2731
|
hlsAes128Key);
|
|
2685
2732
|
previousReference = reference;
|
|
2686
2733
|
|
|
@@ -2795,6 +2842,9 @@ shaka.hls.HlsParser = class {
|
|
|
2795
2842
|
* @private
|
|
2796
2843
|
*/
|
|
2797
2844
|
variableSubstitution_(uri, variables) {
|
|
2845
|
+
if (!variables.size) {
|
|
2846
|
+
return uri;
|
|
2847
|
+
}
|
|
2798
2848
|
let newUri = String(uri).replace(/%7B/g, '{').replace(/%7D/g, '}');
|
|
2799
2849
|
|
|
2800
2850
|
const uriVariables = newUri.match(/{\$\w*}/g);
|
|
@@ -3240,6 +3290,7 @@ shaka.hls.HlsParser = class {
|
|
|
3240
3290
|
* type: string,
|
|
3241
3291
|
* verbatimMediaPlaylistUri: string,
|
|
3242
3292
|
* absoluteMediaPlaylistUri: string,
|
|
3293
|
+
* minTimestamp: number,
|
|
3243
3294
|
* maxTimestamp: number,
|
|
3244
3295
|
* mediaSequenceToStartTime: !Map.<number, number>,
|
|
3245
3296
|
* canSkipSegments: boolean,
|
|
@@ -3263,6 +3314,8 @@ shaka.hls.HlsParser = class {
|
|
|
3263
3314
|
* @property {string} absoluteMediaPlaylistUri
|
|
3264
3315
|
* The absolute media playlist URI, resolved relative to the master playlist
|
|
3265
3316
|
* and updated to reflect any redirects.
|
|
3317
|
+
* @property {number} minTimestamp
|
|
3318
|
+
* The minimum timestamp found in the stream.
|
|
3266
3319
|
* @property {number} maxTimestamp
|
|
3267
3320
|
* The maximum timestamp found in the stream.
|
|
3268
3321
|
* @property {!Map.<number, number>} mediaSequenceToStartTime
|
|
@@ -158,7 +158,15 @@ shaka.hls.ManifestTextParser = class {
|
|
|
158
158
|
partialSegmentTags.push(tag);
|
|
159
159
|
} else if (tag.name == 'EXT-X-PRELOAD-HINT') {
|
|
160
160
|
if (tag.getAttributeValue('TYPE') == 'PART') {
|
|
161
|
-
|
|
161
|
+
// Note: BYTERANGE-START without BYTERANGE-LENGTH is being
|
|
162
|
+
// ignored.
|
|
163
|
+
if (tag.getAttributeValue('BYTERANGE-START') != null) {
|
|
164
|
+
if (tag.getAttributeValue('BYTERANGE-LENGTH') != null) {
|
|
165
|
+
partialSegmentTags.push(tag);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
partialSegmentTags.push(tag);
|
|
169
|
+
}
|
|
162
170
|
} else if (tag.getAttributeValue('TYPE') == 'MAP') {
|
|
163
171
|
// Rename the Preload Hint tag to be a Map tag.
|
|
164
172
|
tag.setName('EXT-X-MAP');
|
|
@@ -300,6 +308,7 @@ shaka.hls.ManifestTextParser.MEDIA_PLAYLIST_TAGS = [
|
|
|
300
308
|
'EXT-X-ENDLIST',
|
|
301
309
|
'EXT-X-SERVER-CONTROL',
|
|
302
310
|
'EXT-X-SKIP',
|
|
311
|
+
'EXT-X-PART-INF',
|
|
303
312
|
];
|
|
304
313
|
|
|
305
314
|
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -133,6 +133,9 @@ shaka.media.DrmEngine = class {
|
|
|
133
133
|
|
|
134
134
|
/** @private {Promise} */
|
|
135
135
|
this.mediaKeysAttached_ = null;
|
|
136
|
+
|
|
137
|
+
/** @private {?shaka.extern.InitDataOverride} */
|
|
138
|
+
this.manifestInitData_ = null;
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
/** @override */
|
|
@@ -480,10 +483,10 @@ shaka.media.DrmEngine = class {
|
|
|
480
483
|
() => this.closeOpenSessions_());
|
|
481
484
|
}
|
|
482
485
|
|
|
483
|
-
|
|
484
|
-
this.currentDrmInfo_.initData.find(
|
|
486
|
+
this.manifestInitData_ = this.currentDrmInfo_ ?
|
|
487
|
+
(this.currentDrmInfo_.initData.find(
|
|
485
488
|
(initDataOverride) => initDataOverride.initData.length > 0,
|
|
486
|
-
) : null;
|
|
489
|
+
) || null) : null;
|
|
487
490
|
|
|
488
491
|
/**
|
|
489
492
|
* We can attach media keys before the playback actually begins when:
|
|
@@ -491,7 +494,7 @@ shaka.media.DrmEngine = class {
|
|
|
491
494
|
* - Some initData already has been generated (through the manifest)
|
|
492
495
|
* - In case of an offline session
|
|
493
496
|
*/
|
|
494
|
-
if (
|
|
497
|
+
if (this.manifestInitData_ ||
|
|
495
498
|
this.currentDrmInfo_.keySystem !== 'com.apple.fps' ||
|
|
496
499
|
this.offlineSessionIds_.length) {
|
|
497
500
|
await this.attachMediaKeys_();
|
|
@@ -504,7 +507,7 @@ shaka.media.DrmEngine = class {
|
|
|
504
507
|
// Also suppress 'encrypted' events when parsing in-band ppsh
|
|
505
508
|
// from media segments because that serves the same purpose as the
|
|
506
509
|
// 'encrypted' events.
|
|
507
|
-
if (!
|
|
510
|
+
if (!this.manifestInitData_ && !this.offlineSessionIds_.length &&
|
|
508
511
|
!this.config_.parseInbandPsshEnabled) {
|
|
509
512
|
this.eventManager_.listen(
|
|
510
513
|
this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
|
|
@@ -2361,7 +2364,7 @@ shaka.media.DrmEngine = class {
|
|
|
2361
2364
|
* @return {!Promise<void>}
|
|
2362
2365
|
*/
|
|
2363
2366
|
parseInbandPssh(contentType, mediaSegment) {
|
|
2364
|
-
if (!this.config_.parseInbandPsshEnabled) {
|
|
2367
|
+
if (!this.config_.parseInbandPsshEnabled || this.manifestInitData_) {
|
|
2365
2368
|
return Promise.resolve();
|
|
2366
2369
|
}
|
|
2367
2370
|
|
|
@@ -109,15 +109,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
109
109
|
/** @private {!shaka.util.PublicPromise} */
|
|
110
110
|
this.mediaSourceOpen_ = new shaka.util.PublicPromise();
|
|
111
111
|
|
|
112
|
+
/** @private {string} */
|
|
113
|
+
this.url_ = '';
|
|
114
|
+
|
|
112
115
|
/** @private {MediaSource} */
|
|
113
116
|
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
|
|
114
117
|
|
|
115
118
|
/** @type {!shaka.util.Destroyer} */
|
|
116
119
|
this.destroyer_ = new shaka.util.Destroyer(() => this.doDestroy_());
|
|
117
120
|
|
|
118
|
-
/** @private {string} */
|
|
119
|
-
this.url_ = '';
|
|
120
|
-
|
|
121
121
|
/** @private {boolean} */
|
|
122
122
|
this.sequenceMode_ = false;
|
|
123
123
|
|
|
@@ -154,6 +154,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
154
154
|
* @private
|
|
155
155
|
*/
|
|
156
156
|
onSourceOpen_(p) {
|
|
157
|
+
goog.asserts.assert(this.url_, 'Must have object URL');
|
|
158
|
+
|
|
157
159
|
// Release the object URL that was previously created, to prevent memory
|
|
158
160
|
// leak.
|
|
159
161
|
// createObjectURL creates a strong reference to the MediaSource object
|
|
@@ -202,6 +204,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
202
204
|
'audio/mp4; codecs="ec-3"',
|
|
203
205
|
'audio/mp4; codecs="opus"',
|
|
204
206
|
'audio/mp4; codecs="flac"',
|
|
207
|
+
'audio/mp4; codecs="dtsc"', // DTS Digital Surround
|
|
208
|
+
'audio/mp4; codecs="dtse"', // DTS Express
|
|
209
|
+
'audio/mp4; codecs="dtsx"', // DTS:X
|
|
205
210
|
// WebM types
|
|
206
211
|
'video/webm; codecs="vp8"',
|
|
207
212
|
'video/webm; codecs="vp9"',
|
|
@@ -375,7 +380,22 @@ shaka.media.MediaSourceEngine = class {
|
|
|
375
380
|
shaka.media.Transmuxer.convertCodecs(contentType, mimeType);
|
|
376
381
|
}
|
|
377
382
|
const type = mimeType + this.config_.sourceBufferExtraFeatures;
|
|
378
|
-
|
|
383
|
+
|
|
384
|
+
this.destroyer_.ensureNotDestroyed();
|
|
385
|
+
|
|
386
|
+
let sourceBuffer;
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
sourceBuffer = this.mediaSource_.addSourceBuffer(type);
|
|
390
|
+
} catch (exception) {
|
|
391
|
+
throw new shaka.util.Error(
|
|
392
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
393
|
+
shaka.util.Error.Category.MEDIA,
|
|
394
|
+
shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
|
|
395
|
+
exception,
|
|
396
|
+
'The mediaSource_ status was' + this.mediaSource_.readyState +
|
|
397
|
+
'expected \'open\'');
|
|
398
|
+
}
|
|
379
399
|
|
|
380
400
|
this.eventManager_.listen(
|
|
381
401
|
sourceBuffer, 'error',
|
|
@@ -628,7 +648,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
628
648
|
// For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
|
|
629
649
|
// the video stream, so textEngine may not have been initialized.
|
|
630
650
|
if (!this.textEngine_) {
|
|
631
|
-
this.reinitText(
|
|
651
|
+
this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
|
|
652
|
+
this.sequenceMode_);
|
|
632
653
|
}
|
|
633
654
|
// This doesn't work for native TS support (ex. Edge/Chromecast),
|
|
634
655
|
// since no transmuxing is needed for native TS.
|
|
@@ -647,7 +668,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
647
668
|
data = transmuxedData.data;
|
|
648
669
|
} else if (hasClosedCaptions && contentType == ContentType.VIDEO) {
|
|
649
670
|
if (!this.textEngine_) {
|
|
650
|
-
this.reinitText(
|
|
671
|
+
this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
|
|
672
|
+
this.sequenceMode_);
|
|
651
673
|
}
|
|
652
674
|
if (!this.captionParser_) {
|
|
653
675
|
this.captionParser_ = this.getCaptionParser(mimeType);
|
|
@@ -771,7 +793,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
771
793
|
this.append_(contentType, data);
|
|
772
794
|
});
|
|
773
795
|
|
|
774
|
-
if (goog.DEBUG && reference) {
|
|
796
|
+
if (goog.DEBUG && reference && !reference.isPreload()) {
|
|
775
797
|
const bufferedAfter = this.getBuffered_(contentType);
|
|
776
798
|
const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
|
|
777
799
|
bufferedBefore, bufferedAfter);
|
|
@@ -937,7 +959,6 @@ shaka.media.MediaSourceEngine = class {
|
|
|
937
959
|
|
|
938
960
|
/**
|
|
939
961
|
* Adjust timestamp offset to maintain AV sync across discontinuities.
|
|
940
|
-
* Only used in sequence mode.
|
|
941
962
|
*
|
|
942
963
|
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
943
964
|
* @param {number} timestampOffset
|
|
@@ -946,9 +967,6 @@ shaka.media.MediaSourceEngine = class {
|
|
|
946
967
|
async resync(contentType, timestampOffset) {
|
|
947
968
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
948
969
|
|
|
949
|
-
goog.asserts.assert(this.sequenceMode_,
|
|
950
|
-
'resync only used with sequence mode!');
|
|
951
|
-
|
|
952
970
|
if (contentType == ContentType.TEXT) {
|
|
953
971
|
// This operation is for audio and video only.
|
|
954
972
|
return;
|
|
@@ -978,8 +996,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
978
996
|
async endOfStream(reason) {
|
|
979
997
|
await this.enqueueBlockingOperation_(() => {
|
|
980
998
|
// If endOfStream() has already been called on the media source,
|
|
981
|
-
// don't call it again.
|
|
982
|
-
|
|
999
|
+
// don't call it again. Also do not call if readyState is
|
|
1000
|
+
// 'closed' (not attached to video element) since it is not a
|
|
1001
|
+
// valid operation.
|
|
1002
|
+
if (this.ended() || this.mediaSource_.readyState === 'closed') {
|
|
983
1003
|
return;
|
|
984
1004
|
}
|
|
985
1005
|
// Tizen won't let us pass undefined, but it will let us omit the
|
|
@@ -236,7 +236,16 @@ shaka.media.PresentationTimeline = class {
|
|
|
236
236
|
|
|
237
237
|
let firstReferenceStartTime = references[0].startTime;
|
|
238
238
|
let lastReferenceEndTime = references[0].endTime;
|
|
239
|
+
|
|
240
|
+
// Date.now() is in milliseconds, from which we compute "now" in seconds.
|
|
241
|
+
const now = (Date.now() + this.clockOffset_) / 1000.0;
|
|
242
|
+
|
|
239
243
|
for (const reference of references) {
|
|
244
|
+
// Exclude segments that are in the "future".
|
|
245
|
+
if (now < reference.startTime) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
240
249
|
firstReferenceStartTime = Math.min(
|
|
241
250
|
firstReferenceStartTime, reference.startTime);
|
|
242
251
|
lastReferenceEndTime = Math.max(lastReferenceEndTime, reference.endTime);
|
|
@@ -251,8 +260,6 @@ shaka.media.PresentationTimeline = class {
|
|
|
251
260
|
!this.startTimeLocked_) {
|
|
252
261
|
// Since we have explicit segment end times, calculate a presentation
|
|
253
262
|
// start based on them. This start time accounts for drift.
|
|
254
|
-
// Date.now() is in milliseconds, from which we compute "now" in seconds.
|
|
255
|
-
const now = (Date.now() + this.clockOffset_) / 1000.0;
|
|
256
263
|
this.presentationStartTime_ =
|
|
257
264
|
now - this.maxSegmentEndTime_ - this.maxSegmentDuration_;
|
|
258
265
|
}
|
|
@@ -144,8 +144,9 @@ shaka.media.SegmentReference = class {
|
|
|
144
144
|
* minus the first segment's tfdt box's 'baseMediaDecodeTime' field (after
|
|
145
145
|
* it has been converted to seconds).
|
|
146
146
|
* <br>
|
|
147
|
-
* For HLS, this value should be
|
|
148
|
-
*
|
|
147
|
+
* For HLS, this value should be the start time of the most recent
|
|
148
|
+
* discontinuity, or 0 if there is no preceding discontinuity. Only used
|
|
149
|
+
* in segments mode.
|
|
149
150
|
* @param {number} appendWindowStart
|
|
150
151
|
* The start of the append window for this reference, relative to the
|
|
151
152
|
* presentation. Any content from before this time will be removed by
|
|
@@ -242,6 +243,9 @@ shaka.media.SegmentReference = class {
|
|
|
242
243
|
/** @type {shaka.media.SegmentReference.Status} */
|
|
243
244
|
this.status = status;
|
|
244
245
|
|
|
246
|
+
/** @type {boolean} */
|
|
247
|
+
this.preload = false;
|
|
248
|
+
|
|
245
249
|
/** @type {?shaka.extern.HlsAes128Key} */
|
|
246
250
|
this.hlsAes128Key = hlsAes128Key;
|
|
247
251
|
|
|
@@ -365,6 +369,25 @@ shaka.media.SegmentReference = class {
|
|
|
365
369
|
this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
|
|
366
370
|
}
|
|
367
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Mark the reference as preload.
|
|
374
|
+
*
|
|
375
|
+
* @export
|
|
376
|
+
*/
|
|
377
|
+
markAsPreload() {
|
|
378
|
+
this.preload = true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Returns true if the segment is preloaded.
|
|
383
|
+
*
|
|
384
|
+
* @return {boolean}
|
|
385
|
+
* @export
|
|
386
|
+
*/
|
|
387
|
+
isPreload() {
|
|
388
|
+
return this.preload;
|
|
389
|
+
}
|
|
390
|
+
|
|
368
391
|
/**
|
|
369
392
|
* Set the segment's thumbnail sprite.
|
|
370
393
|
*
|
|
@@ -78,6 +78,9 @@ shaka.media.StreamingEngine = class {
|
|
|
78
78
|
/** @private {?shaka.extern.Stream} */
|
|
79
79
|
this.currentTextStream_ = null;
|
|
80
80
|
|
|
81
|
+
/** @private {number} */
|
|
82
|
+
this.textStreamSequenceId_ = 0;
|
|
83
|
+
|
|
81
84
|
/** @private {boolean} */
|
|
82
85
|
this.parsedPrftEventRaised_ = false;
|
|
83
86
|
|
|
@@ -224,6 +227,8 @@ shaka.media.StreamingEngine = class {
|
|
|
224
227
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
225
228
|
goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
|
|
226
229
|
'Should not call loadNewTextStream_ while streaming text!');
|
|
230
|
+
this.textStreamSequenceId_++;
|
|
231
|
+
const currentSequenceId = this.textStreamSequenceId_;
|
|
227
232
|
|
|
228
233
|
try {
|
|
229
234
|
// Clear MediaSource's buffered text, so that the new text stream will
|
|
@@ -246,7 +251,7 @@ shaka.media.StreamingEngine = class {
|
|
|
246
251
|
const streamText =
|
|
247
252
|
textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
|
|
248
253
|
|
|
249
|
-
if (streamText) {
|
|
254
|
+
if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
|
|
250
255
|
const state = this.createMediaState_(stream);
|
|
251
256
|
this.mediaStates_.set(ContentType.TEXT, state);
|
|
252
257
|
this.scheduleUpdate_(state, 0);
|
|
@@ -357,7 +362,7 @@ shaka.media.StreamingEngine = class {
|
|
|
357
362
|
/**
|
|
358
363
|
* @param {shaka.extern.Stream} textStream
|
|
359
364
|
*/
|
|
360
|
-
switchTextStream(textStream) {
|
|
365
|
+
async switchTextStream(textStream) {
|
|
361
366
|
this.currentTextStream_ = textStream;
|
|
362
367
|
|
|
363
368
|
if (!this.startupComplete_) {
|
|
@@ -369,6 +374,13 @@ shaka.media.StreamingEngine = class {
|
|
|
369
374
|
goog.asserts.assert(textStream && textStream.type == ContentType.TEXT,
|
|
370
375
|
'Wrong stream type passed to switchTextStream!');
|
|
371
376
|
|
|
377
|
+
// In HLS it is possible that the mimetype changes when the media
|
|
378
|
+
// playlist is downloaded, so it is necessary to have the updated data
|
|
379
|
+
// here.
|
|
380
|
+
if (!textStream.segmentIndex) {
|
|
381
|
+
await textStream.createSegmentIndex();
|
|
382
|
+
}
|
|
383
|
+
|
|
372
384
|
this.switchInternal_(
|
|
373
385
|
textStream, /* clearBuffer= */ true,
|
|
374
386
|
/* safeMargin= */ 0, /* force= */ false);
|
|
@@ -639,10 +651,15 @@ shaka.media.StreamingEngine = class {
|
|
|
639
651
|
const mediaState = this.mediaStates_.get(type);
|
|
640
652
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
641
653
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
654
|
+
let segment = null;
|
|
655
|
+
if (mediaState.segmentIterator) {
|
|
656
|
+
segment = mediaState.segmentIterator.current();
|
|
657
|
+
}
|
|
658
|
+
// Only reset the iterator if we seek outside the current segment.
|
|
659
|
+
if (!segment || segment.startTime > presentationTime ||
|
|
660
|
+
segment.endTime < presentationTime) {
|
|
661
|
+
mediaState.segmentIterator = null;
|
|
662
|
+
}
|
|
646
663
|
|
|
647
664
|
if (!newTimeIsBuffered(type)) {
|
|
648
665
|
const bufferEnd =
|
|
@@ -1564,10 +1581,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1564
1581
|
'waiting for another stream to recover...');
|
|
1565
1582
|
}
|
|
1566
1583
|
|
|
1567
|
-
// QuotaExceededError gets thrown if
|
|
1584
|
+
// QuotaExceededError gets thrown if eviction didn't help to make room
|
|
1568
1585
|
// for a segment. We want to wait for a while (4 seconds is just an
|
|
1569
1586
|
// arbitrary number) before updating to give the playhead a chance to
|
|
1570
|
-
// advance, so we don't
|
|
1587
|
+
// advance, so we don't immediately throw again.
|
|
1571
1588
|
this.scheduleUpdate_(mediaState, 4);
|
|
1572
1589
|
}
|
|
1573
1590
|
|
|
@@ -1675,19 +1692,31 @@ shaka.media.StreamingEngine = class {
|
|
|
1675
1692
|
}
|
|
1676
1693
|
}
|
|
1677
1694
|
|
|
1695
|
+
const lastDiscontinuitySequence =
|
|
1696
|
+
mediaState.lastSegmentReference ?
|
|
1697
|
+
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1678
1698
|
if (this.manifest_.sequenceMode) {
|
|
1679
1699
|
// Across discontinuity bounds, we should resync timestamps for
|
|
1680
1700
|
// sequence mode playbacks. The next segment appended should
|
|
1681
1701
|
// land at its theoretical timestamp from the segment index.
|
|
1682
|
-
const lastDiscontinuitySequence =
|
|
1683
|
-
mediaState.lastSegmentReference ?
|
|
1684
|
-
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1685
1702
|
if (reference.discontinuitySequence != lastDiscontinuitySequence ||
|
|
1686
1703
|
mediaState.needsResync) {
|
|
1687
1704
|
mediaState.needsResync = false;
|
|
1688
1705
|
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1689
1706
|
mediaState.type, reference.startTime));
|
|
1690
1707
|
}
|
|
1708
|
+
} else {
|
|
1709
|
+
// In segments mode, we need to resync to set the timestampOffset
|
|
1710
|
+
// to the start of the current discontinuity sequence. This is
|
|
1711
|
+
// because individual discontinuity sequences may have internal
|
|
1712
|
+
// timestamps that overlap, so we adjust the timestampOffset to avoid
|
|
1713
|
+
// having the SourceBuffer get overwritten.
|
|
1714
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1715
|
+
operations.push(
|
|
1716
|
+
this.playerInterface_.mediaSourceEngine.resync(
|
|
1717
|
+
mediaState.type,
|
|
1718
|
+
reference.timestampOffset));
|
|
1719
|
+
}
|
|
1691
1720
|
}
|
|
1692
1721
|
|
|
1693
1722
|
await Promise.all(operations);
|