shaka-player 3.2.2 → 3.2.3
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/CHANGELOG.md +46 -0
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/locales.js +30 -30
- package/dist/shaka-player.compiled.d.ts +21 -2
- package/dist/shaka-player.compiled.debug.d.ts +21 -2
- package/dist/shaka-player.compiled.debug.externs.js +22 -1
- package/dist/shaka-player.compiled.debug.js +205 -202
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +22 -1
- package/dist/shaka-player.compiled.js +727 -723
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +21 -2
- package/dist/shaka-player.ui.debug.d.ts +21 -2
- package/dist/shaka-player.ui.debug.externs.js +22 -1
- package/dist/shaka-player.ui.debug.js +221 -218
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +22 -1
- package/dist/shaka-player.ui.js +829 -825
- package/dist/shaka-player.ui.map +4 -4
- package/externs/shaka/player.js +8 -0
- package/externs/shaka/text.js +10 -0
- package/lib/dash/dash_parser.js +4 -2
- package/lib/hls/hls_parser.js +30 -0
- package/lib/media/segment_index.js +2 -1
- package/lib/media/segment_reference.js +19 -1
- package/lib/media/streaming_engine.js +5 -2
- package/lib/player.js +20 -13
- package/lib/polyfill/media_capabilities.js +30 -15
- package/lib/text/cue.js +6 -0
- package/lib/text/ttml_text_parser.js +84 -58
- package/lib/text/ui_text_displayer.js +17 -11
- package/lib/util/stream_utils.js +86 -10
- package/lib/util/xml_utils.js +19 -0
- package/package.json +2 -2
- package/test/dash/dash_parser_manifest_unit.js +33 -1
- package/test/media/drm_engine_unit.js +3 -3
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +145 -0
- package/test/player_integration.js +41 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +19 -9
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/util/ttml_utils.js +81 -0
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +495 -228
- package/test/util/stream_utils_unit.js +36 -30
- package/ui/less/buttons.less +0 -4
- package/ui/text_selection.js +1 -0
- package/externs/media_capabilities.js +0 -16
|
@@ -352,19 +352,22 @@ shaka.text.UITextDisplayer = class {
|
|
|
352
352
|
type = 'br';
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
const needWrapper = !isNested && cue.nestedCues.length > 0;
|
|
356
|
+
|
|
355
357
|
// Nested cues are inline elements. Top-level cues are block elements.
|
|
356
358
|
const cueElement = shaka.util.Dom.createHTMLElement(type);
|
|
357
359
|
if (type != 'br') {
|
|
358
|
-
this.setCaptionStyles_(cueElement, cue, parents);
|
|
360
|
+
this.setCaptionStyles_(cueElement, cue, parents, needWrapper);
|
|
359
361
|
}
|
|
360
362
|
|
|
361
363
|
let wrapper = cueElement;
|
|
362
|
-
if (
|
|
364
|
+
if (needWrapper) {
|
|
363
365
|
// Create a wrapper element which will serve to contain all children into
|
|
364
366
|
// a single item. This ensures that nested span elements appear
|
|
365
367
|
// horizontally and br elements occupy no vertical space.
|
|
366
368
|
wrapper = shaka.util.Dom.createHTMLElement('span');
|
|
367
369
|
wrapper.classList.add('shaka-text-wrapper');
|
|
370
|
+
wrapper.style.backgroundColor = cue.backgroundColor;
|
|
368
371
|
cueElement.appendChild(wrapper);
|
|
369
372
|
}
|
|
370
373
|
|
|
@@ -375,9 +378,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
375
378
|
* @param {!HTMLElement} cueElement
|
|
376
379
|
* @param {!shaka.extern.Cue} cue
|
|
377
380
|
* @param {!Array.<!shaka.extern.Cue>} parents
|
|
381
|
+
* @param {boolean} hasWrapper
|
|
378
382
|
* @private
|
|
379
383
|
*/
|
|
380
|
-
setCaptionStyles_(cueElement, cue, parents) {
|
|
384
|
+
setCaptionStyles_(cueElement, cue, parents, hasWrapper) {
|
|
381
385
|
const Cue = shaka.text.Cue;
|
|
382
386
|
const inherit =
|
|
383
387
|
(cb) => shaka.text.UITextDisplayer.inheritProperty_(parents, cb);
|
|
@@ -434,13 +438,15 @@ shaka.text.UITextDisplayer = class {
|
|
|
434
438
|
if (cue.border) {
|
|
435
439
|
elem.style.border = cue.border;
|
|
436
440
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
if (!hasWrapper) {
|
|
442
|
+
const bgColor = inherit((c) => c.backgroundColor);
|
|
443
|
+
if (bgColor) {
|
|
444
|
+
elem.style.backgroundColor = bgColor;
|
|
445
|
+
} else if (text) {
|
|
446
|
+
// If there is no background, default to a semi-transparent black.
|
|
447
|
+
// Only do this for the text itself.
|
|
448
|
+
elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
|
|
449
|
+
}
|
|
444
450
|
}
|
|
445
451
|
if (text) {
|
|
446
452
|
elem.textContent = text;
|
|
@@ -450,7 +456,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
450
456
|
// The displayAlign attribute specifies the vertical alignment of the
|
|
451
457
|
// captions inside the text container. Before means at the top of the
|
|
452
458
|
// text container, and after means at the bottom.
|
|
453
|
-
if (isNested) {
|
|
459
|
+
if (isNested && !parents[parents.length - 1].isContainer) {
|
|
454
460
|
style.display = 'inline';
|
|
455
461
|
} else {
|
|
456
462
|
style.display = 'flex';
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -419,13 +419,48 @@ shaka.util.StreamUtils = class {
|
|
|
419
419
|
*/
|
|
420
420
|
static async filterManifestByMediaCapabilities(
|
|
421
421
|
manifest, usePersistentLicenses) {
|
|
422
|
-
goog.asserts.assert(
|
|
422
|
+
goog.asserts.assert(navigator.mediaCapabilities,
|
|
423
423
|
'MediaCapabilities should be valid.');
|
|
424
424
|
|
|
425
425
|
await shaka.util.StreamUtils.getDecodingInfosForVariants(
|
|
426
426
|
manifest.variants, usePersistentLicenses, /* srcEquals= */ false);
|
|
427
427
|
manifest.variants = manifest.variants.filter((variant) => {
|
|
428
|
+
// See: https://github.com/google/shaka-player/issues/3860
|
|
428
429
|
const video = variant.video;
|
|
430
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
431
|
+
if (video) {
|
|
432
|
+
let videoCodecs = shaka.util.StreamUtils.patchVp9(video.codecs);
|
|
433
|
+
// For multiplexed streams. Here we must check the audio of the
|
|
434
|
+
// stream to see if it is compatible.
|
|
435
|
+
if (video.codecs.includes(',')) {
|
|
436
|
+
const allCodecs = video.codecs.split(',');
|
|
437
|
+
videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
438
|
+
ContentType.VIDEO, allCodecs);
|
|
439
|
+
const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
440
|
+
ContentType.AUDIO, allCodecs);
|
|
441
|
+
const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
442
|
+
video.mimeType, audioCodecs, ContentType.AUDIO);
|
|
443
|
+
if (!MediaSource.isTypeSupported(audioFullType)) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
448
|
+
video.mimeType, videoCodecs, ContentType.VIDEO);
|
|
449
|
+
if (!MediaSource.isTypeSupported(fullType)) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const audio = variant.audio;
|
|
454
|
+
if (audio) {
|
|
455
|
+
const codecs =
|
|
456
|
+
shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
|
|
457
|
+
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
458
|
+
audio.mimeType, codecs, ContentType.AUDIO);
|
|
459
|
+
if (!MediaSource.isTypeSupported(fullType)) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
429
464
|
// See: https://github.com/google/shaka-player/issues/3380
|
|
430
465
|
if (shaka.util.Platform.isXboxOne() && video &&
|
|
431
466
|
((video.width && video.width > 1920) ||
|
|
@@ -468,7 +503,7 @@ shaka.util.StreamUtils = class {
|
|
|
468
503
|
return;
|
|
469
504
|
}
|
|
470
505
|
|
|
471
|
-
const mediaCapabilities =
|
|
506
|
+
const mediaCapabilities = navigator.mediaCapabilities;
|
|
472
507
|
|
|
473
508
|
const operations = [];
|
|
474
509
|
const getVariantDecodingInfos = (async (variant, decodingConfig) => {
|
|
@@ -562,12 +597,8 @@ shaka.util.StreamUtils = class {
|
|
|
562
597
|
}
|
|
563
598
|
}
|
|
564
599
|
if (audio) {
|
|
565
|
-
// Some Tizen devices seem to misreport AC-3 support, but correctly
|
|
566
|
-
// report EC-3 support. So query EC-3 as a fallback for AC-3.
|
|
567
|
-
// See https://github.com/google/shaka-player/issues/2989 for details.
|
|
568
600
|
const codecs =
|
|
569
|
-
(audio.codecs
|
|
570
|
-
shaka.util.Platform.isTizen()) ? 'ec-3' : audio.codecs;
|
|
601
|
+
shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
|
|
571
602
|
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
572
603
|
audio.mimeType, codecs, ContentType.AUDIO);
|
|
573
604
|
|
|
@@ -678,6 +709,26 @@ shaka.util.StreamUtils = class {
|
|
|
678
709
|
return configs;
|
|
679
710
|
}
|
|
680
711
|
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Generates the correct audio codec for MediaDecodingConfiguration and
|
|
715
|
+
* for MediaSource.isTypeSupported.
|
|
716
|
+
* @param {string} codecs
|
|
717
|
+
* @return {string}
|
|
718
|
+
* @private
|
|
719
|
+
*/
|
|
720
|
+
static getCorrectAudioCodecs_(codecs) {
|
|
721
|
+
// Some Tizen devices seem to misreport AC-3 support, but correctly
|
|
722
|
+
// report EC-3 support. So query EC-3 as a fallback for AC-3.
|
|
723
|
+
// See https://github.com/google/shaka-player/issues/2989 for details.
|
|
724
|
+
if (shaka.util.Platform.isTizen()) {
|
|
725
|
+
return codecs.toLowerCase() == 'ac-3' ? 'ec-3' : codecs;
|
|
726
|
+
} else {
|
|
727
|
+
return codecs;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
|
|
681
732
|
/**
|
|
682
733
|
* MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate vp9
|
|
683
734
|
* codec strings into 'vp09...', to allow such content to play with
|
|
@@ -989,6 +1040,31 @@ shaka.util.StreamUtils = class {
|
|
|
989
1040
|
static imageStreamToTrack(stream) {
|
|
990
1041
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
991
1042
|
|
|
1043
|
+
let width = stream.width || null;
|
|
1044
|
+
let height = stream.height || null;
|
|
1045
|
+
|
|
1046
|
+
// The stream width and height represent the size of the entire thumbnail
|
|
1047
|
+
// sheet, so divide by the layout.
|
|
1048
|
+
let reference = null;
|
|
1049
|
+
// Note: segmentIndex is built by default for HLS, but not for DASH, but
|
|
1050
|
+
// in DASH this information comes at the stream level and not at the
|
|
1051
|
+
// segment level.
|
|
1052
|
+
if (stream.segmentIndex) {
|
|
1053
|
+
reference = stream.segmentIndex.get(0);
|
|
1054
|
+
}
|
|
1055
|
+
let layout = stream.tilesLayout;
|
|
1056
|
+
if (reference) {
|
|
1057
|
+
layout = reference.getTilesLayout() || layout;
|
|
1058
|
+
}
|
|
1059
|
+
if (layout && width != null) {
|
|
1060
|
+
width /= Number(layout.split('x')[0]);
|
|
1061
|
+
}
|
|
1062
|
+
if (layout && height != null) {
|
|
1063
|
+
height /= Number(layout.split('x')[1]);
|
|
1064
|
+
}
|
|
1065
|
+
// TODO: What happens if there are multiple grids, with different
|
|
1066
|
+
// layout sizes, inside this image stream?
|
|
1067
|
+
|
|
992
1068
|
/** @type {shaka.extern.Track} */
|
|
993
1069
|
const track = {
|
|
994
1070
|
id: stream.id,
|
|
@@ -998,8 +1074,8 @@ shaka.util.StreamUtils = class {
|
|
|
998
1074
|
language: '',
|
|
999
1075
|
label: null,
|
|
1000
1076
|
kind: null,
|
|
1001
|
-
width
|
|
1002
|
-
height
|
|
1077
|
+
width,
|
|
1078
|
+
height,
|
|
1003
1079
|
frameRate: null,
|
|
1004
1080
|
pixelAspectRatio: null,
|
|
1005
1081
|
hdr: null,
|
|
@@ -1016,7 +1092,7 @@ shaka.util.StreamUtils = class {
|
|
|
1016
1092
|
channelsCount: null,
|
|
1017
1093
|
audioSamplingRate: null,
|
|
1018
1094
|
spatialAudio: false,
|
|
1019
|
-
tilesLayout:
|
|
1095
|
+
tilesLayout: layout || null,
|
|
1020
1096
|
audioBandwidth: null,
|
|
1021
1097
|
videoBandwidth: null,
|
|
1022
1098
|
originalVideoId: null,
|
package/lib/util/xml_utils.js
CHANGED
|
@@ -103,6 +103,25 @@ shaka.util.XmlUtils = class {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Gets a namespace-qualified attribute.
|
|
108
|
+
* @param {!Element} elem The element to get from.
|
|
109
|
+
* @param {!Array.<string>} nsList The lis of namespace URIs.
|
|
110
|
+
* @param {string} name The local name of the attribute.
|
|
111
|
+
* @return {?string} The attribute's value, or null if not present.
|
|
112
|
+
*/
|
|
113
|
+
static getAttributeNSList(elem, nsList, name) {
|
|
114
|
+
// Some browsers return the empty string when the attribute is missing,
|
|
115
|
+
// so check if it exists first. See: https://mzl.la/2L7F0UK
|
|
116
|
+
for (const ns of nsList) {
|
|
117
|
+
if (elem.hasAttributeNS(ns, name)) {
|
|
118
|
+
return elem.getAttributeNS(ns, name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
106
125
|
/**
|
|
107
126
|
* Gets the text contents of a node.
|
|
108
127
|
* @param {!Node} elem The XML element.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shaka-player",
|
|
3
3
|
"description": "DASH/EME video player library",
|
|
4
|
-
"version": "3.2.
|
|
4
|
+
"version": "3.2.3",
|
|
5
5
|
"homepage": "https://github.com/google/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
|
|
15
|
+
"@onslip/karma-safari-launcher": "^1.0.1",
|
|
15
16
|
"@teppeis/clutz": "^1.0.29-4c95e12.v20190929",
|
|
16
17
|
"awesomplete": "^1.1.4",
|
|
17
18
|
"babel-core": "^6.26.3",
|
|
@@ -44,7 +45,6 @@
|
|
|
44
45
|
"karma-jasmine": "^3.3.1",
|
|
45
46
|
"karma-jasmine-ajax": "^0.1.13",
|
|
46
47
|
"karma-opera-launcher": "^1.0.0",
|
|
47
|
-
"karma-safari-launcher": "^1.0.0",
|
|
48
48
|
"karma-sourcemap-loader": "^0.3.7",
|
|
49
49
|
"karma-spec-reporter": "0.0.32",
|
|
50
50
|
"karma-webdriver-launcher": "^1.0.8",
|
|
@@ -1872,7 +1872,7 @@ describe('DashParser Manifest', () => {
|
|
|
1872
1872
|
expect(textStream.kind).toBe('caption');
|
|
1873
1873
|
});
|
|
1874
1874
|
|
|
1875
|
-
it('converts Roles element to "forced"', async () => {
|
|
1875
|
+
it('converts Roles element to "forced" (old role)', async () => {
|
|
1876
1876
|
const manifestText = [
|
|
1877
1877
|
'<MPD minBufferTime="PT75S">',
|
|
1878
1878
|
' <Period id="1" duration="PT30S">',
|
|
@@ -1904,6 +1904,38 @@ describe('DashParser Manifest', () => {
|
|
|
1904
1904
|
expect(textStream.forced).toBe(true);
|
|
1905
1905
|
});
|
|
1906
1906
|
|
|
1907
|
+
it('converts Roles element to "forced"', async () => {
|
|
1908
|
+
const manifestText = [
|
|
1909
|
+
'<MPD minBufferTime="PT75S">',
|
|
1910
|
+
' <Period id="1" duration="PT30S">',
|
|
1911
|
+
' <AdaptationSet id="1" contentType="text">',
|
|
1912
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1913
|
+
' value="captions" />',
|
|
1914
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1915
|
+
' value="forced-subtitle" />',
|
|
1916
|
+
' <Accessibility schemeIdUri="foobar" value="bar" />',
|
|
1917
|
+
' <Representation id="text-en" mimeType="text/webvtt">',
|
|
1918
|
+
' <BaseURL>t-en.vtt</BaseURL>',
|
|
1919
|
+
' </Representation>',
|
|
1920
|
+
' </AdaptationSet>',
|
|
1921
|
+
' <AdaptationSet id="1" mimeType="video/mp4">',
|
|
1922
|
+
' <Representation id="video-sd" width="640" height="480">',
|
|
1923
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
1924
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1925
|
+
' </Representation>',
|
|
1926
|
+
' </AdaptationSet>',
|
|
1927
|
+
' </Period>',
|
|
1928
|
+
'</MPD>',
|
|
1929
|
+
].join('\n');
|
|
1930
|
+
|
|
1931
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
1932
|
+
/** @type {shaka.extern.Manifest} */
|
|
1933
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1934
|
+
const textStream = manifest.textStreams[0];
|
|
1935
|
+
expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
|
|
1936
|
+
expect(textStream.forced).toBe(true);
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1907
1939
|
it('supports HDR signaling via profiles', async () => {
|
|
1908
1940
|
// (DASH-IF IOP v4.3 10.3.3.)
|
|
1909
1941
|
const hdrProfile =
|
|
@@ -31,7 +31,7 @@ describe('DrmEngine', () => {
|
|
|
31
31
|
navigator.requestMediaKeySystemAccess;
|
|
32
32
|
const originalLogError = shaka.log.error;
|
|
33
33
|
const originalBatchTime = shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME;
|
|
34
|
-
const originalDecodingInfo =
|
|
34
|
+
const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
|
|
35
35
|
|
|
36
36
|
/** @type {!jasmine.Spy} */
|
|
37
37
|
let decodingInfoSpy;
|
|
@@ -76,7 +76,7 @@ describe('DrmEngine', () => {
|
|
|
76
76
|
|
|
77
77
|
beforeEach(() => {
|
|
78
78
|
decodingInfoSpy = jasmine.createSpy('decodingInfo');
|
|
79
|
-
|
|
79
|
+
navigator.mediaCapabilities.decodingInfo =
|
|
80
80
|
shaka.test.Util.spyFunc(decodingInfoSpy);
|
|
81
81
|
|
|
82
82
|
logErrorSpy = jasmine.createSpy('shaka.log.error');
|
|
@@ -153,7 +153,7 @@ describe('DrmEngine', () => {
|
|
|
153
153
|
|
|
154
154
|
navigator.requestMediaKeySystemAccess =
|
|
155
155
|
originalRequestMediaKeySystemAccess;
|
|
156
|
-
|
|
156
|
+
navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
|
|
157
157
|
shaka.log.error = originalLogError;
|
|
158
158
|
});
|
|
159
159
|
|
|
@@ -419,8 +419,9 @@ describe('StreamingEngine', () => {
|
|
|
419
419
|
// 3. Playhead seeks to force us back inside the window
|
|
420
420
|
// 4. (maybe) seek if there is a gap at the period boundary
|
|
421
421
|
// 5. (maybe) seek to flush a pipeline stall
|
|
422
|
+
// 6. (maybe) on slower platforms (e.g. GitHub actions)
|
|
422
423
|
expect(seekCount).toBeGreaterThan(2);
|
|
423
|
-
expect(seekCount).toBeLessThan(
|
|
424
|
+
expect(seekCount).toBeLessThan(7);
|
|
424
425
|
});
|
|
425
426
|
});
|
|
426
427
|
|
|
@@ -97,6 +97,9 @@ describe('StreamingEngine', () => {
|
|
|
97
97
|
/** @type {!shaka.media.StreamingEngine} */
|
|
98
98
|
let streamingEngine;
|
|
99
99
|
|
|
100
|
+
/** @type {function(function(), number)} */
|
|
101
|
+
let realSetTimeout;
|
|
102
|
+
|
|
100
103
|
/**
|
|
101
104
|
* Runs the fake event loop.
|
|
102
105
|
* @param {function()=} callback An optional callback that is executed
|
|
@@ -116,6 +119,7 @@ describe('StreamingEngine', () => {
|
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
beforeAll(() => {
|
|
122
|
+
realSetTimeout = window.setTimeout;
|
|
119
123
|
jasmine.clock().install();
|
|
120
124
|
jasmine.clock().mockDate();
|
|
121
125
|
});
|
|
@@ -822,6 +826,53 @@ describe('StreamingEngine', () => {
|
|
|
822
826
|
.toHaveBeenCalledWith('video', 0, lt20, gt40);
|
|
823
827
|
});
|
|
824
828
|
|
|
829
|
+
// Regression test for https://github.com/google/shaka-player/issues/3717
|
|
830
|
+
it('applies fudge factors for the duration', async () => {
|
|
831
|
+
setupVod();
|
|
832
|
+
|
|
833
|
+
// In #3717, the duration was just barely large enough to encompass an
|
|
834
|
+
// additional segment, but that segment didn't exist, so playback never
|
|
835
|
+
// completed. Here, we set the duration to just beyond the 3rd segment, and
|
|
836
|
+
// we make the 4th segment fail when requested.
|
|
837
|
+
const duration = 30.000000005;
|
|
838
|
+
timeline.getDuration.and.returnValue(duration);
|
|
839
|
+
|
|
840
|
+
const targetUri = '1_video_3'; // The URI of the 4th video segment.
|
|
841
|
+
failRequestsForTarget(netEngine, targetUri);
|
|
842
|
+
|
|
843
|
+
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
|
|
844
|
+
createStreamingEngine();
|
|
845
|
+
|
|
846
|
+
// Here we go!
|
|
847
|
+
streamingEngine.switchVariant(variant);
|
|
848
|
+
streamingEngine.switchTextStream(textStream);
|
|
849
|
+
await streamingEngine.start();
|
|
850
|
+
playing = true;
|
|
851
|
+
await runTest();
|
|
852
|
+
|
|
853
|
+
// The end of the stream should have been reached, and the 4th segment from
|
|
854
|
+
// each type should never have been requested.
|
|
855
|
+
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
|
|
856
|
+
|
|
857
|
+
const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
858
|
+
|
|
859
|
+
netEngine.expectRequest('0_audio_0', segmentType);
|
|
860
|
+
netEngine.expectRequest('0_video_0', segmentType);
|
|
861
|
+
netEngine.expectRequest('0_text_0', segmentType);
|
|
862
|
+
|
|
863
|
+
netEngine.expectRequest('0_audio_1', segmentType);
|
|
864
|
+
netEngine.expectRequest('0_video_1', segmentType);
|
|
865
|
+
netEngine.expectRequest('0_text_1', segmentType);
|
|
866
|
+
|
|
867
|
+
netEngine.expectRequest('1_audio_2', segmentType);
|
|
868
|
+
netEngine.expectRequest('1_video_2', segmentType);
|
|
869
|
+
netEngine.expectRequest('1_text_2', segmentType);
|
|
870
|
+
|
|
871
|
+
netEngine.expectNoRequest('1_audio_3', segmentType);
|
|
872
|
+
netEngine.expectNoRequest('1_video_3', segmentType);
|
|
873
|
+
netEngine.expectNoRequest('1_text_3', segmentType);
|
|
874
|
+
});
|
|
875
|
+
|
|
825
876
|
it('does not buffer one media type ahead of another', async () => {
|
|
826
877
|
setupVod();
|
|
827
878
|
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
|
|
@@ -1389,6 +1440,66 @@ describe('StreamingEngine', () => {
|
|
|
1389
1440
|
});
|
|
1390
1441
|
});
|
|
1391
1442
|
|
|
1443
|
+
it('into unbuffered regions when nothing is buffered ' +
|
|
1444
|
+
'and mediaState is performing an update', async () => {
|
|
1445
|
+
// Here we go!
|
|
1446
|
+
streamingEngine.switchVariant(variant);
|
|
1447
|
+
streamingEngine.switchTextStream(textStream);
|
|
1448
|
+
// ensure init source buffer promise does not resolve before seeked()
|
|
1449
|
+
// so mediaState remains in "performingUpdate" state
|
|
1450
|
+
const initSourceBufferPromise = new shaka.util.PublicPromise();
|
|
1451
|
+
mediaSourceEngine.setStreamProperties.and
|
|
1452
|
+
.returnValue(initSourceBufferPromise);
|
|
1453
|
+
await streamingEngine.start();
|
|
1454
|
+
playing = true;
|
|
1455
|
+
|
|
1456
|
+
// tick to trigger mediaState updates
|
|
1457
|
+
jasmine.clock().tick(1);
|
|
1458
|
+
// give a chance for fetchAndAppend_ to be invoked
|
|
1459
|
+
// by async onUpdate_ callback
|
|
1460
|
+
await Util.shortDelay(realSetTimeout);
|
|
1461
|
+
|
|
1462
|
+
// Nothing is buffered yet.
|
|
1463
|
+
expect(mediaSourceEngine.segments).toEqual({
|
|
1464
|
+
audio: [false, false, false, false],
|
|
1465
|
+
video: [false, false, false, false],
|
|
1466
|
+
text: [false, false, false, false],
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
// Seek forward to an unbuffered region in the first Period.
|
|
1470
|
+
presentationTimeInSeconds = 15;
|
|
1471
|
+
streamingEngine.seeked();
|
|
1472
|
+
|
|
1473
|
+
// resolve initSourceBufferPromise after seeked(), waitingToClearBuffer
|
|
1474
|
+
// should have been set, so this will now trigger the actual flush of
|
|
1475
|
+
// the buffer
|
|
1476
|
+
initSourceBufferPromise.resolve();
|
|
1477
|
+
mediaSourceEngine.setStreamProperties.and
|
|
1478
|
+
.returnValue(Promise.resolve());
|
|
1479
|
+
|
|
1480
|
+
// allow mediaState update to resolve
|
|
1481
|
+
await Util.shortDelay(realSetTimeout);
|
|
1482
|
+
|
|
1483
|
+
onTick.and.callFake(() => {
|
|
1484
|
+
expect(mediaSourceEngine.clear).toHaveBeenCalled();
|
|
1485
|
+
onTick.and.stub();
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
await runTest(Util.spyFunc(onTick));
|
|
1489
|
+
|
|
1490
|
+
// Verify buffers.
|
|
1491
|
+
expect(mediaSourceEngine.initSegments).toEqual({
|
|
1492
|
+
audio: [false, true],
|
|
1493
|
+
video: [false, true],
|
|
1494
|
+
text: [],
|
|
1495
|
+
});
|
|
1496
|
+
expect(mediaSourceEngine.segments).toEqual({
|
|
1497
|
+
audio: [false, true, true, true],
|
|
1498
|
+
video: [false, true, true, true],
|
|
1499
|
+
text: [false, true, true, true],
|
|
1500
|
+
});
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1392
1503
|
it('into unbuffered regions near segment start', async () => {
|
|
1393
1504
|
// Here we go!
|
|
1394
1505
|
streamingEngine.switchVariant(variant);
|
|
@@ -3388,6 +3499,40 @@ describe('StreamingEngine', () => {
|
|
|
3388
3499
|
segmentAvailability.end++;
|
|
3389
3500
|
}
|
|
3390
3501
|
|
|
3502
|
+
/**
|
|
3503
|
+
* @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
|
|
3504
|
+
* look-alike.
|
|
3505
|
+
* @param {string} targetUri
|
|
3506
|
+
* @param {shaka.util.Error.Code=} errorCode
|
|
3507
|
+
*/
|
|
3508
|
+
function failRequestsForTarget(
|
|
3509
|
+
netEngine, targetUri, errorCode=shaka.util.Error.Code.BAD_HTTP_STATUS) {
|
|
3510
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
3511
|
+
const originalNetEngineRequest = netEngine.request.bind(netEngine);
|
|
3512
|
+
|
|
3513
|
+
netEngine.request = jasmine.createSpy('request').and.callFake(
|
|
3514
|
+
(requestType, request) => {
|
|
3515
|
+
if (request.uris[0] == targetUri) {
|
|
3516
|
+
const data = [targetUri];
|
|
3517
|
+
|
|
3518
|
+
if (errorCode == shaka.util.Error.Code.BAD_HTTP_STATUS) {
|
|
3519
|
+
data.push(404);
|
|
3520
|
+
data.push('');
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
// The compiler still sees the error code parameter as potentially
|
|
3524
|
+
// undefined, even though we gave it a default value.
|
|
3525
|
+
goog.asserts.assert(errorCode != undefined, 'Undefined error code');
|
|
3526
|
+
|
|
3527
|
+
return shaka.util.AbortableOperation.failed(new shaka.util.Error(
|
|
3528
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
3529
|
+
shaka.util.Error.Category.NETWORK,
|
|
3530
|
+
errorCode, data));
|
|
3531
|
+
}
|
|
3532
|
+
return originalNetEngineRequest(requestType, request);
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3391
3536
|
/**
|
|
3392
3537
|
* @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
|
|
3393
3538
|
* look-alike.
|
|
@@ -964,9 +964,9 @@ describe('Player', () => {
|
|
|
964
964
|
it('add external chapters in vtt format', async () => {
|
|
965
965
|
await player.load('test:sintel_no_text_compiled');
|
|
966
966
|
const locationUri = new goog.Uri(location.href);
|
|
967
|
-
const
|
|
968
|
-
const
|
|
969
|
-
await player.addChaptersTrack(
|
|
967
|
+
const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
|
|
968
|
+
const absoluteUri1 = locationUri.resolve(partialUri1);
|
|
969
|
+
await player.addChaptersTrack(absoluteUri1.toString(), 'en');
|
|
970
970
|
|
|
971
971
|
await shaka.test.Util.delay(1.5);
|
|
972
972
|
|
|
@@ -979,11 +979,44 @@ describe('Player', () => {
|
|
|
979
979
|
const chapter2 = chapters[1];
|
|
980
980
|
expect(chapter2.title).toBe('Chapter 2');
|
|
981
981
|
expect(chapter2.startTime).toBe(5);
|
|
982
|
-
expect(chapter2.endTime).toBe(
|
|
982
|
+
expect(chapter2.endTime).toBe(10);
|
|
983
983
|
const chapter3 = chapters[2];
|
|
984
984
|
expect(chapter3.title).toBe('Chapter 3');
|
|
985
|
-
expect(chapter3.startTime).toBe(
|
|
986
|
-
expect(chapter3.endTime).toBe(
|
|
985
|
+
expect(chapter3.startTime).toBe(10);
|
|
986
|
+
expect(chapter3.endTime).toBe(20);
|
|
987
|
+
|
|
988
|
+
const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
|
|
989
|
+
const absoluteUri2 = locationUri.resolve(partialUri2);
|
|
990
|
+
await player.addChaptersTrack(absoluteUri2.toString(), 'en');
|
|
991
|
+
|
|
992
|
+
await shaka.test.Util.delay(1.5);
|
|
993
|
+
|
|
994
|
+
const chaptersUpdated = player.getChapters('en');
|
|
995
|
+
expect(chaptersUpdated.length).toBe(6);
|
|
996
|
+
const chapterUpdated1 = chaptersUpdated[0];
|
|
997
|
+
expect(chapterUpdated1.title).toBe('Chapter 1');
|
|
998
|
+
expect(chapterUpdated1.startTime).toBe(0);
|
|
999
|
+
expect(chapterUpdated1.endTime).toBe(5);
|
|
1000
|
+
const chapterUpdated2 = chaptersUpdated[1];
|
|
1001
|
+
expect(chapterUpdated2.title).toBe('Chapter 2');
|
|
1002
|
+
expect(chapterUpdated2.startTime).toBe(5);
|
|
1003
|
+
expect(chapterUpdated2.endTime).toBe(10);
|
|
1004
|
+
const chapterUpdated3 = chaptersUpdated[2];
|
|
1005
|
+
expect(chapterUpdated3.title).toBe('Chapter 3');
|
|
1006
|
+
expect(chapterUpdated3.startTime).toBe(10);
|
|
1007
|
+
expect(chapterUpdated3.endTime).toBe(20);
|
|
1008
|
+
const chapterUpdated4 = chaptersUpdated[3];
|
|
1009
|
+
expect(chapterUpdated4.title).toBe('Chapter 4');
|
|
1010
|
+
expect(chapterUpdated4.startTime).toBe(20);
|
|
1011
|
+
expect(chapterUpdated4.endTime).toBe(30);
|
|
1012
|
+
const chapterUpdated5 = chaptersUpdated[4];
|
|
1013
|
+
expect(chapterUpdated5.title).toBe('Chapter 5');
|
|
1014
|
+
expect(chapterUpdated5.startTime).toBe(30);
|
|
1015
|
+
expect(chapterUpdated5.endTime).toBe(40);
|
|
1016
|
+
const chapterUpdated6 = chaptersUpdated[5];
|
|
1017
|
+
expect(chapterUpdated6.title).toBe('Chapter 6');
|
|
1018
|
+
expect(chapterUpdated6.startTime).toBe(40);
|
|
1019
|
+
expect(chapterUpdated6.endTime).toBe(61.349);
|
|
987
1020
|
});
|
|
988
1021
|
|
|
989
1022
|
it('add external chapters in srt format', async () => {
|
|
@@ -991,11 +1024,11 @@ describe('Player', () => {
|
|
|
991
1024
|
const locationUri = new goog.Uri(location.href);
|
|
992
1025
|
const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
|
|
993
1026
|
const absoluteUri = locationUri.resolve(partialUri);
|
|
994
|
-
await player.addChaptersTrack(absoluteUri.toString(), '
|
|
1027
|
+
await player.addChaptersTrack(absoluteUri.toString(), 'es');
|
|
995
1028
|
|
|
996
1029
|
await shaka.test.Util.delay(1.5);
|
|
997
1030
|
|
|
998
|
-
const chapters = player.getChapters('
|
|
1031
|
+
const chapters = player.getChapters('es');
|
|
999
1032
|
expect(chapters.length).toBe(3);
|
|
1000
1033
|
const chapter1 = chapters[0];
|
|
1001
1034
|
expect(chapter1.title).toBe('Chapter 1');
|