shaka-player 3.1.4 → 3.1.5

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/controls.css +2 -2
  3. package/dist/controls.css.map +1 -1
  4. package/dist/locales.js +29 -29
  5. package/dist/shaka-player.compiled.d.ts +14 -0
  6. package/dist/shaka-player.compiled.debug.d.ts +14 -0
  7. package/dist/shaka-player.compiled.debug.externs.js +11 -0
  8. package/dist/shaka-player.compiled.debug.js +170 -168
  9. package/dist/shaka-player.compiled.debug.map +3 -3
  10. package/dist/shaka-player.compiled.externs.js +11 -0
  11. package/dist/shaka-player.compiled.js +650 -650
  12. package/dist/shaka-player.compiled.map +2 -2
  13. package/dist/shaka-player.ui.d.ts +14 -0
  14. package/dist/shaka-player.ui.debug.d.ts +14 -0
  15. package/dist/shaka-player.ui.debug.externs.js +11 -0
  16. package/dist/shaka-player.ui.debug.js +178 -176
  17. package/dist/shaka-player.ui.debug.map +3 -3
  18. package/dist/shaka-player.ui.externs.js +11 -0
  19. package/dist/shaka-player.ui.js +792 -792
  20. package/dist/shaka-player.ui.map +2 -2
  21. package/externs/shaka/text.js +10 -0
  22. package/lib/dash/dash_parser.js +4 -2
  23. package/lib/media/streaming_engine.js +5 -2
  24. package/lib/player.js +1 -1
  25. package/lib/polyfill/media_capabilities.js +30 -15
  26. package/lib/text/cue.js +6 -0
  27. package/lib/text/ttml_text_parser.js +84 -58
  28. package/lib/text/ui_text_displayer.js +17 -11
  29. package/lib/util/stream_utils.js +58 -7
  30. package/lib/util/xml_utils.js +19 -0
  31. package/package.json +2 -2
  32. package/test/dash/dash_parser_manifest_unit.js +33 -1
  33. package/test/media/drm_engine_unit.js +3 -3
  34. package/test/media/streaming_engine_integration.js +2 -1
  35. package/test/media/streaming_engine_unit.js +145 -0
  36. package/test/player_unit.js +5 -5
  37. package/test/test/util/ttml_utils.js +81 -0
  38. package/test/text/mp4_ttml_parser_unit.js +11 -18
  39. package/test/text/ttml_text_parser_unit.js +495 -228
  40. package/test/util/stream_utils_unit.js +35 -29
  41. package/ui/less/buttons.less +0 -4
  42. package/ui/text_selection.js +1 -0
  43. package/externs/media_capabilities.js +0 -16
@@ -1830,7 +1830,7 @@ describe('DashParser Manifest', () => {
1830
1830
  expect(textStream.kind).toBe('caption');
1831
1831
  });
1832
1832
 
1833
- it('converts Roles element to "forced"', async () => {
1833
+ it('converts Roles element to "forced" (old role)', async () => {
1834
1834
  const manifestText = [
1835
1835
  '<MPD minBufferTime="PT75S">',
1836
1836
  ' <Period id="1" duration="PT30S">',
@@ -1862,6 +1862,38 @@ describe('DashParser Manifest', () => {
1862
1862
  expect(textStream.forced).toBe(true);
1863
1863
  });
1864
1864
 
1865
+ it('converts Roles element to "forced"', async () => {
1866
+ const manifestText = [
1867
+ '<MPD minBufferTime="PT75S">',
1868
+ ' <Period id="1" duration="PT30S">',
1869
+ ' <AdaptationSet id="1" contentType="text">',
1870
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1871
+ ' value="captions" />',
1872
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1873
+ ' value="forced-subtitle" />',
1874
+ ' <Accessibility schemeIdUri="foobar" value="bar" />',
1875
+ ' <Representation id="text-en" mimeType="text/webvtt">',
1876
+ ' <BaseURL>t-en.vtt</BaseURL>',
1877
+ ' </Representation>',
1878
+ ' </AdaptationSet>',
1879
+ ' <AdaptationSet id="1" mimeType="video/mp4">',
1880
+ ' <Representation id="video-sd" width="640" height="480">',
1881
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1882
+ ' <SegmentBase indexRange="100-200" />',
1883
+ ' </Representation>',
1884
+ ' </AdaptationSet>',
1885
+ ' </Period>',
1886
+ '</MPD>',
1887
+ ].join('\n');
1888
+
1889
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1890
+ /** @type {shaka.extern.Manifest} */
1891
+ const manifest = await parser.start('dummy://foo', playerInterface);
1892
+ const textStream = manifest.textStreams[0];
1893
+ expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
1894
+ expect(textStream.forced).toBe(true);
1895
+ });
1896
+
1865
1897
  it('supports HDR signaling via profiles', async () => {
1866
1898
  // (DASH-IF IOP v4.3 10.3.3.)
1867
1899
  const hdrProfile =
@@ -37,7 +37,7 @@ function testDrmEngine(useMediaCapabilities) {
37
37
  navigator.requestMediaKeySystemAccess;
38
38
  const originalLogError = shaka.log.error;
39
39
  const originalBatchTime = shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME;
40
- const originalDecodingInfo = window.shakaMediaCapabilities.decodingInfo;
40
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
41
41
 
42
42
  /** @type {!jasmine.Spy} */
43
43
  let requestMediaKeySystemAccessSpy;
@@ -88,7 +88,7 @@ function testDrmEngine(useMediaCapabilities) {
88
88
  navigator.requestMediaKeySystemAccess =
89
89
  shaka.test.Util.spyFunc(requestMediaKeySystemAccessSpy);
90
90
  decodingInfoSpy = jasmine.createSpy('decodingInfo');
91
- window.shakaMediaCapabilities.decodingInfo =
91
+ navigator.mediaCapabilities.decodingInfo =
92
92
  shaka.test.Util.spyFunc(decodingInfoSpy);
93
93
 
94
94
  logErrorSpy = jasmine.createSpy('shaka.log.error');
@@ -165,7 +165,7 @@ function testDrmEngine(useMediaCapabilities) {
165
165
 
166
166
  navigator.requestMediaKeySystemAccess =
167
167
  originalRequestMediaKeySystemAccess;
168
- window.shakaMediaCapabilities.decodingInfo = originalDecodingInfo;
168
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
169
169
  shaka.log.error = originalLogError;
170
170
  });
171
171
 
@@ -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(6);
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.
@@ -36,7 +36,7 @@ describe('Player', () => {
36
36
  const originalLogWarn = shaka.log.warning;
37
37
  const originalLogAlwaysWarn = shaka.log.alwaysWarn;
38
38
  const originalIsTypeSupported = window.MediaSource.isTypeSupported;
39
- const originalDecodingInfo = window.shakaMediaCapabilities.decodingInfo;
39
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
40
40
 
41
41
  const fakeManifestUri = 'fake-manifest-uri';
42
42
  const fakeMimeType = 'application/test';
@@ -93,7 +93,7 @@ describe('Player', () => {
93
93
  // Since this is not an integration test, we don't want MediaCapabilities to
94
94
  // fail assertions based on browser support for types. Pretend that all
95
95
  // video and audio types are supported.
96
- window.shakaMediaCapabilities.decodingInfo = async (config) => {
96
+ navigator.mediaCapabilities.decodingInfo = async (config) => {
97
97
  await Promise.resolve();
98
98
  const videoType = config['video'] ?
99
99
  config['video'].contentType.split('/')[0] : null;
@@ -177,7 +177,7 @@ describe('Player', () => {
177
177
  shaka.log.alwaysWarn = originalLogAlwaysWarn;
178
178
  window.MediaSource.isTypeSupported = originalIsTypeSupported;
179
179
  shaka.media.ManifestParser.unregisterParserByMime(fakeMimeType);
180
- window.shakaMediaCapabilities.decodingInfo = originalDecodingInfo;
180
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
181
181
  }
182
182
  });
183
183
 
@@ -2463,7 +2463,7 @@ describe('Player', () => {
2463
2463
  it('throws CONTENT_UNSUPPORTED_BY_BROWSER', async () => {
2464
2464
  window.MediaSource.isTypeSupported = (mimeType) => false;
2465
2465
 
2466
- window.shakaMediaCapabilities.decodingInfo = async (config) => {
2466
+ navigator.mediaCapabilities.decodingInfo = async (config) => {
2467
2467
  await Promise.resolve();
2468
2468
  return {supported: false};
2469
2469
  };
@@ -2837,7 +2837,7 @@ describe('Player', () => {
2837
2837
  });
2838
2838
 
2839
2839
  if (useMediaCapabilities) {
2840
- window.shakaMediaCapabilities.decodingInfo = async (config) => {
2840
+ navigator.mediaCapabilities.decodingInfo = async (config) => {
2841
2841
  await Promise.resolve();
2842
2842
  const videoType = config['video'] ? config['video'].contentType : '';
2843
2843
  if (videoType.includes('video') &&
@@ -0,0 +1,81 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.test.TtmlUtils');
8
+
9
+ shaka.test.TtmlUtils = class {
10
+ /**
11
+ * @param {!Array} expectedCues
12
+ * @param {!Array} actualCues
13
+ * @param {!Object} bodyProperties
14
+ * @param {Object=} divProperties
15
+ */
16
+ static verifyHelper(expectedCues, actualCues, bodyProperties, divProperties) {
17
+ const mapExpected = (cue) => {
18
+ if (cue.region) {
19
+ cue.region = jasmine.objectContaining(cue.region);
20
+ }
21
+
22
+ if (cue.nestedCues && (cue.nestedCues instanceof Array)) {
23
+ cue.nestedCues = cue.nestedCues.map(mapExpected);
24
+ }
25
+
26
+ if (cue.isContainer == undefined) {
27
+ // If not specified to be true, check for isContainer to be false.
28
+ cue.isContainer = false;
29
+ }
30
+
31
+ return jasmine.objectContaining(cue);
32
+ };
33
+
34
+ /**
35
+ * @param {!Object} properties
36
+ * @return {!shaka.extern.Cue}
37
+ */
38
+ const makeContainer = (properties) => {
39
+ const region = {
40
+ id: '',
41
+ viewportAnchorX: 0,
42
+ viewportAnchorY: 0,
43
+ regionAnchorX: 0,
44
+ regionAnchorY: 0,
45
+ width: 100,
46
+ height: 100,
47
+ widthUnits: shaka.text.CueRegion.units.PERCENTAGE,
48
+ heightUnits: shaka.text.CueRegion.units.PERCENTAGE,
49
+ viewportAnchorUnits: shaka.text.CueRegion.units.PERCENTAGE,
50
+ scroll: '',
51
+ };
52
+ const containerCue = /** @type {!shaka.extern.Cue} */ ({
53
+ region,
54
+ nestedCues: jasmine.any(Object),
55
+ payload: '',
56
+ startTime: 0,
57
+ endTime: Infinity,
58
+ isContainer: true,
59
+ });
60
+ Object.assign(containerCue, properties);
61
+ return mapExpected(containerCue);
62
+ };
63
+
64
+ if (expectedCues.length == 0 && !divProperties) {
65
+ expect(actualCues.length).toBe(0);
66
+ } else {
67
+ // Body.
68
+ expect(actualCues.length).toBe(1);
69
+ const body = actualCues[0];
70
+ expect(body).toEqual(makeContainer(bodyProperties));
71
+
72
+ // Div.
73
+ expect(body.nestedCues.length).toBe(1);
74
+ const div = body.nestedCues[0];
75
+ expect(div).toEqual(makeContainer(divProperties || bodyProperties));
76
+
77
+ // Cues.
78
+ expect(div.nestedCues).toEqual(expectedCues.map(mapExpected));
79
+ }
80
+ }
81
+ };
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ goog.require('shaka.test.TtmlUtils');
7
8
  goog.require('shaka.test.Util');
8
9
  goog.require('shaka.text.Mp4TtmlParser');
9
10
  goog.require('shaka.util.BufferUtils');
@@ -48,7 +49,14 @@ describe('Mp4TtmlParser', () => {
48
49
  parser.parseInit(ttmlInitSegment);
49
50
  const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
50
51
  const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time);
51
- expect(ret.length).toBe(20);
52
+ // Bodies.
53
+ expect(ret.length).toBe(2);
54
+ // Divs.
55
+ expect(ret[0].nestedCues.length).toBe(1);
56
+ expect(ret[1].nestedCues.length).toBe(1);
57
+ // Cues.
58
+ expect(ret[0].nestedCues[0].nestedCues.length).toBe(10);
59
+ expect(ret[1].nestedCues[0].nestedCues.length).toBe(10);
52
60
  });
53
61
 
54
62
  it('accounts for offset', () => {
@@ -159,22 +167,7 @@ describe('Mp4TtmlParser', () => {
159
167
  parser.parseInit(ttmlInitSegment);
160
168
  const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
161
169
  const result = parser.parseMedia(ttmlSegment, time);
162
- verifyHelper(cues, result);
170
+ shaka.test.TtmlUtils.verifyHelper(
171
+ cues, result, {startTime: 23, endTime: 53.5});
163
172
  });
164
-
165
- function verifyHelper(/** !Array */ expected, /** !Array */ actual) {
166
- const mapExpected = (cue) => {
167
- if (cue.region) {
168
- cue.region = jasmine.objectContaining(cue.region);
169
- }
170
-
171
- if (cue.nestedCues) {
172
- cue.nestedCues = cue.nestedCues.map(mapExpected);
173
- }
174
-
175
- return jasmine.objectContaining(cue);
176
- };
177
-
178
- expect(actual).toEqual(expected.map(mapExpected));
179
- }
180
173
  });