shaka-player 2.5.4 → 2.5.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 (102) hide show
  1. package/.csslintrc +13 -0
  2. package/CHANGELOG.md +56 -0
  3. package/build/build.py +4 -1
  4. package/build/check.py +17 -0
  5. package/build/compiler.py +40 -0
  6. package/build/types/core +1 -0
  7. package/build/types/ui +4 -1
  8. package/demo/asset_card.js +41 -11
  9. package/demo/cast_receiver/receiver_app.css +19 -13
  10. package/demo/close_button.js +0 -6
  11. package/demo/common/asset.js +6 -1
  12. package/demo/common/assets.js +72 -14
  13. package/demo/demo.less +105 -16
  14. package/demo/index.html +8 -5
  15. package/demo/load.js +5 -7
  16. package/demo/main.js +81 -16
  17. package/dist/.csslintstamp +0 -0
  18. package/dist/controls.css +1 -1
  19. package/dist/controls.css.map +1 -1
  20. package/dist/demo.compiled.debug.js +46 -39
  21. package/dist/demo.compiled.debug.map +3 -3
  22. package/dist/demo.compiled.js +46 -39
  23. package/dist/demo.compiled.map +3 -3
  24. package/dist/demo.css +1 -1
  25. package/dist/demo.css.map +1 -1
  26. package/dist/deps.js +11 -7
  27. package/dist/receiver.compiled.debug.js +10 -6
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +10 -6
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.debug.externs.js +123 -92
  32. package/dist/shaka-player.compiled.debug.js +113 -105
  33. package/dist/shaka-player.compiled.debug.map +4 -4
  34. package/dist/shaka-player.compiled.externs.js +123 -92
  35. package/dist/shaka-player.compiled.js +551 -548
  36. package/dist/shaka-player.compiled.map +4 -4
  37. package/dist/shaka-player.ui.debug.externs.js +170 -101
  38. package/dist/shaka-player.ui.debug.js +187 -188
  39. package/dist/shaka-player.ui.debug.map +4 -4
  40. package/dist/shaka-player.ui.externs.js +170 -101
  41. package/dist/shaka-player.ui.js +651 -651
  42. package/dist/shaka-player.ui.map +3 -3
  43. package/docs/tutorials/drm-config.md +1 -0
  44. package/docs/tutorials/fairplay.md +77 -0
  45. package/docs/tutorials/index.json +1 -0
  46. package/docs/tutorials/ui-customization.md +3 -0
  47. package/docs/tutorials/ui.md +3 -1
  48. package/externs/mediasession.js +16 -1
  49. package/externs/shaka/net.js +5 -1
  50. package/externs/shaka/player.js +136 -90
  51. package/externs/webkitmediakeys.js +4 -0
  52. package/lib/dash/content_protection.js +33 -1
  53. package/lib/dash/dash_parser.js +12 -4
  54. package/lib/hls/hls_parser.js +20 -4
  55. package/lib/media/drm_engine.js +29 -2
  56. package/lib/media/media_source_engine.js +7 -3
  57. package/lib/media/playhead.js +8 -3
  58. package/lib/media/streaming_engine.js +24 -8
  59. package/lib/media/video_wrapper.js +8 -3
  60. package/lib/net/networking_engine.js +1 -0
  61. package/lib/player.js +3 -2
  62. package/lib/polyfill/patchedmediakeys_apple.js +2 -72
  63. package/lib/util/error.js +12 -0
  64. package/lib/util/fairplay_utils.js +122 -0
  65. package/lib/util/player_configuration.js +5 -0
  66. package/lib/util/pssh.js +41 -0
  67. package/package.json +4 -3
  68. package/shaka-player.uncompiled.js +5 -1
  69. package/test/dash/dash_parser_content_protection_unit.js +35 -1
  70. package/test/dash/dash_parser_manifest_unit.js +31 -0
  71. package/test/hls/hls_live_unit.js +7 -1
  72. package/test/hls/hls_parser_unit.js +50 -0
  73. package/test/media/media_source_engine_integration.js +12 -0
  74. package/test/media/playhead_unit.js +2 -0
  75. package/test/player_external.js +3 -2
  76. package/test/player_integration.js +75 -0
  77. package/test/test/externs/jasmine.js +6 -0
  78. package/test/test/util/test_scheme.js +32 -1
  79. package/test/test/util/ui_utils.js +41 -0
  80. package/test/ui/ui_unit.js +129 -3
  81. package/ui/audio_language_selection.js +17 -96
  82. package/ui/big_play_button.js +61 -0
  83. package/ui/controls.js +111 -86
  84. package/ui/controls.less +3 -5
  85. package/ui/externs/ui.js +14 -2
  86. package/ui/fullscreen_button.js +2 -52
  87. package/ui/less/buttons.less +8 -8
  88. package/ui/less/containers.less +21 -21
  89. package/ui/less/general.less +11 -0
  90. package/ui/less/other_elements.less +2 -1
  91. package/ui/less/overflow_menu.less +11 -10
  92. package/ui/less/range_elements.less +6 -0
  93. package/ui/less/spinner.less +2 -0
  94. package/ui/pip_button.js +1 -1
  95. package/ui/{play_pause_button.js → play_button.js} +17 -47
  96. package/ui/resolution_selection.js +41 -104
  97. package/ui/seek_bar.js +15 -17
  98. package/ui/settings_menu.js +113 -0
  99. package/ui/small_play_button.js +73 -0
  100. package/ui/text_displayer.js +2 -2
  101. package/ui/text_selection.js +39 -105
  102. package/ui/ui.js +12 -0
package/lib/util/pssh.js CHANGED
@@ -104,3 +104,44 @@ shaka.util.Pssh.prototype.parseBox_ = function(box) {
104
104
  shaka.log.warning('Mismatch between box size and data size!');
105
105
  }
106
106
  };
107
+
108
+
109
+ /**
110
+ * Creates a pssh blob from the given system ID and data.
111
+ *
112
+ * @param {!Uint8Array} data
113
+ * @param {!Uint8Array} systemId
114
+ * @return {!Uint8Array}
115
+ */
116
+ shaka.util.Pssh.createPssh = function(data, systemId) {
117
+ const dataLength = data.length;
118
+ const psshSize = 0x4 + 0x4 + 0x4 + systemId.length + 0x4 + dataLength;
119
+
120
+ /** @type {!ArrayBuffer} */
121
+ const psshBoxBuffer = new ArrayBuffer(psshSize);
122
+
123
+ /** @type {!Uint8Array} */
124
+ const psshBox = new Uint8Array(psshBoxBuffer);
125
+
126
+ /** @type {!DataView} */
127
+ const psshData = new DataView(psshBoxBuffer);
128
+
129
+ let byteCursor = 0;
130
+ psshData.setUint32(byteCursor, psshSize);
131
+ byteCursor += 0x4;
132
+ psshData.setUint32(byteCursor, 0x70737368); // 'pssh'
133
+ byteCursor += 0x4;
134
+ psshData.setUint32(byteCursor, 0); // flags
135
+ byteCursor += 0x4;
136
+ psshBox.set(systemId, byteCursor);
137
+ byteCursor += systemId.length;
138
+ psshData.setUint32(byteCursor, dataLength);
139
+ byteCursor += 0x4;
140
+ psshBox.set(data, byteCursor);
141
+ byteCursor += dataLength;
142
+
143
+ goog.asserts.assert(byteCursor === psshSize,
144
+ 'MS PRO invalid length.');
145
+
146
+ return psshBox;
147
+ };
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": "2.5.4",
4
+ "version": "2.5.5",
5
5
  "homepage": "https://github.com/google/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -39,7 +39,7 @@
39
39
  "karma-safari-launcher": "^1.0.0",
40
40
  "karma-sourcemap-loader": "^0.3.7",
41
41
  "karma-spec-reporter": "0.0.32",
42
- "karma-webdriver-launcher": "^1.0.5",
42
+ "karma-webdriver-launcher": "^1.0.7",
43
43
  "less": "^3.9.0",
44
44
  "less-plugin-clean-css": "^1.5.1",
45
45
  "material-design-lite": "^1.3.0",
@@ -48,8 +48,9 @@
48
48
  "promise-polyfill": "<8.1.1",
49
49
  "rimraf": "^2.6.3",
50
50
  "sprintf-js": "^1.1.2",
51
+ "stylelint": "github:theodab/stylelint",
52
+ "stylelint-config-standard": "^18.3.0",
51
53
  "tippy.js": "^4.3.1",
52
- "wd": "^1.11.2",
53
54
  "which": "^1.3.1"
54
55
  },
55
56
  "main": "dist/shaka-player.compiled.js",
@@ -64,8 +64,11 @@ goog.require('shaka.text.TextEngine');
64
64
  goog.require('shaka.text.TtmlTextParser');
65
65
  goog.require('shaka.text.VttTextParser');
66
66
  goog.require('shaka.ui.Controls');
67
+ goog.require('shaka.ui.PlayButton');
68
+ goog.require('shaka.ui.SettingsMenu');
67
69
  goog.require('shaka.ui.OverflowMenu');
68
70
  goog.require('shaka.ui.AudioLanguageSelection');
71
+ goog.require('shaka.ui.BigPlayButton');
69
72
  goog.require('shaka.ui.CastButton');
70
73
  goog.require('shaka.ui.Element');
71
74
  goog.require('shaka.ui.FastForwardButton');
@@ -74,7 +77,7 @@ goog.require('shaka.ui.Localization');
74
77
  goog.require('shaka.ui.MuteButton');
75
78
  goog.require('shaka.ui.Overlay');
76
79
  goog.require('shaka.ui.PipButton');
77
- goog.require('shaka.ui.PlayPauseButton');
80
+ goog.require('shaka.ui.SmallPlayButton');
78
81
  goog.require('shaka.ui.PresentationTimeTracker');
79
82
  goog.require('shaka.ui.ResolutionSelection');
80
83
  goog.require('shaka.ui.RewindButton');
@@ -83,4 +86,5 @@ goog.require('shaka.ui.TextSelection');
83
86
  goog.require('shaka.ui.VolumeBar');
84
87
  goog.require('shaka.util.Dom');
85
88
  goog.require('shaka.util.Error');
89
+ goog.require('shaka.util.FairPlayUtils');
86
90
  goog.require('shaka.util.Iterables');
@@ -68,7 +68,8 @@ describe('DashParser ContentProtection', function() {
68
68
  adaptationSetLines, representation1Lines, representation2Lines) {
69
69
  let template = [
70
70
  '<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011"',
71
- ' xmlns:cenc="urn:mpeg:cenc:2013">',
71
+ ' xmlns:cenc="urn:mpeg:cenc:2013"',
72
+ ' xmlns:mspr="urn:microsoft:playready">',
72
73
  ' <Period duration="PT30S">',
73
74
  ' <SegmentTemplate media="s.mp4" duration="2" />',
74
75
  ' <AdaptationSet mimeType="video/mp4" codecs="avc1.4d401f">',
@@ -298,6 +299,39 @@ describe('DashParser ContentProtection', function() {
298
299
  await testDashParser(source, expected);
299
300
  });
300
301
 
302
+ it('extracts embedded PSSHs with mspr:pro', async () => {
303
+ const source = buildManifestText([
304
+ // AdaptationSet lines
305
+ '<ContentProtection',
306
+ ' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
307
+ ' <mspr:pro>UGxheXJlYWR5</mspr:pro>',
308
+ '</ContentProtection>',
309
+ ], [], []);
310
+ const expected = buildExpectedManifest([
311
+ buildDrmInfo('com.microsoft.playready', [], [
312
+ 'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=',
313
+ ]),
314
+ ]);
315
+ await testDashParser(source, expected);
316
+ });
317
+
318
+ it('extracts embedded PSSHs and prefer cenc:pssh over mspr:pro', async () => {
319
+ const source = buildManifestText([
320
+ // AdaptationSet lines
321
+ '<ContentProtection',
322
+ ' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
323
+ ' <cenc:pssh>bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5</cenc:pssh>',
324
+ ' <mspr:pro>ZmFrZSBQbGF5cmVhZHkgUFJP</mspr:pro>',
325
+ '</ContentProtection>',
326
+ ], [], []);
327
+ const expected = buildExpectedManifest([
328
+ buildDrmInfo('com.microsoft.playready', [], [
329
+ 'bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5',
330
+ ]),
331
+ ]);
332
+ await testDashParser(source, expected);
333
+ });
334
+
301
335
  it('assumes all known key systems for generic CENC', async () => {
302
336
  let source = buildManifestText([
303
337
  // AdaptationSet lines
@@ -1244,4 +1244,35 @@ describe('DashParser Manifest', function() {
1244
1244
  expect(presentationDelay).not.toBeNaN();
1245
1245
  expect(presentationDelay).toBe(config.dash.defaultPresentationDelay);
1246
1246
  });
1247
+
1248
+ it('converts Accessibility element to "kind"', async () => {
1249
+ const manifestText = [
1250
+ '<MPD minBufferTime="PT75S">',
1251
+ ' <Period id="1" duration="PT30S">',
1252
+ ' <AdaptationSet id="1" contentType="text">',
1253
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1254
+ ' value="captions" />',
1255
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1256
+ ' value="foo" />',
1257
+ ' <Accessibility schemeIdUri="foobar" value="bar" />',
1258
+ ' <Representation id="text-en" mimeType="text/webvtt">',
1259
+ ' <BaseURL>t-en.vtt</BaseURL>',
1260
+ ' </Representation>',
1261
+ ' </AdaptationSet>',
1262
+ ' <AdaptationSet id="1" mimeType="video/mp4">',
1263
+ ' <Representation id="video-sd" width="640" height="480">',
1264
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1265
+ ' <SegmentBase indexRange="100-200" />',
1266
+ ' </Representation>',
1267
+ ' </AdaptationSet>',
1268
+ ' </Period>',
1269
+ '</MPD>',
1270
+ ].join('\n');
1271
+
1272
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1273
+ const manifest = await parser.start('dummy://foo', playerInterface);
1274
+ const textStream = manifest.periods[0].textStreams[0];
1275
+ expect(textStream.roles).toEqual(['captions', 'foo']);
1276
+ expect(textStream.kind).toBe('caption');
1277
+ });
1247
1278
  });
@@ -273,12 +273,18 @@ describe('HlsParser live', function() {
273
273
  });
274
274
 
275
275
  it('updates all streams', function(done) {
276
+ const masterlist = [
277
+ '#EXTM3U\n',
278
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",AUDIO="aud1",',
279
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
280
+ 'video\n',
281
+ ].join('');
276
282
  const audio = [
277
283
  '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
278
284
  'URI="audio"\n',
279
285
  ].join('');
280
286
 
281
- let masterWithAudio = master + audio;
287
+ let masterWithAudio = masterlist + audio;
282
288
  let ref1 = ManifestParser.makeReference('test:/main.mp4',
283
289
  0, 2, 4);
284
290
  let ref2 = ManifestParser.makeReference('test:/main2.mp4',
@@ -1107,6 +1107,56 @@ describe('HlsParser', function() {
1107
1107
  .catch(fail).then(done);
1108
1108
  });
1109
1109
 
1110
+ it('drops failed text streams when configured to', async () => {
1111
+ const master = [
1112
+ '#EXTM3U\n',
1113
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1114
+ 'URI="text"\n',
1115
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
1116
+ 'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
1117
+ 'video\n',
1118
+ ].join('');
1119
+
1120
+ const media = [
1121
+ '#EXTM3U\n',
1122
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1123
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1124
+ '#EXTINF:5,\n',
1125
+ '#EXT-X-BYTERANGE:121090@616\n',
1126
+ 'main.mp4',
1127
+ ].join('');
1128
+
1129
+ const textMedia = [
1130
+ '#EXTM3U\n',
1131
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1132
+ '#EXTINF:5,\n',
1133
+ '#EXT-X-BYTERANGE:121090@616\n',
1134
+ 'main.foo',
1135
+ ].join('');
1136
+
1137
+ /* eslint-disable indent */
1138
+ const manifest = new shaka.test.ManifestGenerator()
1139
+ .anyTimeline()
1140
+ .addPeriod(0)
1141
+ .addPartialVariant()
1142
+ .addPartialStream(ContentType.VIDEO)
1143
+ .build();
1144
+ /* eslint-enable indent */
1145
+
1146
+ fakeNetEngine
1147
+ .setResponseText('test:/master', master)
1148
+ .setResponseText('test:/audio', media)
1149
+ .setResponseText('test:/video', media)
1150
+ .setResponseText('test:/text', textMedia)
1151
+ .setResponseText('test:/main.foo', '')
1152
+ .setResponseValue('test:/init.mp4', initSegmentData)
1153
+ .setResponseValue('test:/main.mp4', segmentData);
1154
+
1155
+ config.hls.ignoreTextStreamFailures = true;
1156
+ const actual = await parser.start('test:/master', playerInterface);
1157
+ expect(actual).toEqual(manifest);
1158
+ });
1159
+
1110
1160
  it('parses video described by a media tag', async () => {
1111
1161
  const master = [
1112
1162
  '#EXTM3U\n',
@@ -193,6 +193,18 @@ describe('MediaSourceEngine', function() {
193
193
  expect(mediaSource.duration).toBeCloseTo(30);
194
194
  });
195
195
 
196
+ it('does not throw if endOfStrem called more than once', async () => {
197
+ const initObject = new Map();
198
+ initObject.set(ContentType.VIDEO, getFakeStream(metadata.video));
199
+ await mediaSourceEngine.init(initObject, false);
200
+ await mediaSourceEngine.setDuration(presentationDuration);
201
+ await appendInit(ContentType.VIDEO);
202
+ await append(ContentType.VIDEO, 1);
203
+ // Call endOfStream twice. There should be no exception.
204
+ await mediaSourceEngine.endOfStream();
205
+ await mediaSourceEngine.endOfStream();
206
+ });
207
+
196
208
  it('queues operations', function(done) {
197
209
  let resolutionOrder = [];
198
210
  let requests = [];
@@ -212,6 +212,7 @@ describe('Playhead', function() {
212
212
 
213
213
  expect(video.addEventListener).toHaveBeenCalledWith(
214
214
  'seeking', jasmine.any(Function), jasmine.anything());
215
+ video.on['seeking']();
215
216
 
216
217
  expect(playhead.getTime()).toBe(5);
217
218
  expect(video.currentTime).toBe(5);
@@ -239,6 +240,7 @@ describe('Playhead', function() {
239
240
  Util.spyFunc(onSeek),
240
241
  Util.spyFunc(onEvent));
241
242
 
243
+ video.on['seeking']();
242
244
  expect(playhead.getTime()).toBe(5);
243
245
  expect(video.currentTime).toBe(5);
244
246
 
@@ -89,7 +89,7 @@ describe('Player', () => {
89
89
 
90
90
  let wit = asset.focus ? fit : it;
91
91
  wit(testName, async () => {
92
- if (asset.drm.length &&
92
+ if (!asset.isClear() &&
93
93
  !asset.drm.some((keySystem) => support.drm[keySystem])) {
94
94
  pending('None of the required key systems are supported.');
95
95
  }
@@ -102,7 +102,8 @@ describe('Player', () => {
102
102
  if (asset.features.includes(Feature.MP4)) {
103
103
  mimeTypes.push('video/mp4');
104
104
  }
105
- if (!mimeTypes.some((type) => support.media[type])) {
105
+ if (mimeTypes.length &&
106
+ !mimeTypes.some((type) => support.media[type])) {
106
107
  pending('None of the required MIME types are supported.');
107
108
  }
108
109
  }
@@ -464,6 +464,81 @@ describe('Player', function() {
464
464
  }).catch(fail).then(done);
465
465
  });
466
466
  });
467
+
468
+ describe('tracks', () => {
469
+ // This is a regression test for b/138941217, in which tracks briefly
470
+ // vanished during the loading process. On Chromecast devices, where the
471
+ // timing is very different from on desktop, this could occur such that
472
+ // there were no tracks after load() is resolved.
473
+ // This is an integration test so that we can check the behavior of the
474
+ // Player against actual platform behavior on all supported platforms.
475
+ it('remain available at every stage of loading', async () => {
476
+ let tracksFound = false;
477
+
478
+ /**
479
+ * @param {string} when When the check takes place.
480
+ *
481
+ * Will fail the test if tracks disappear after they first become
482
+ * available.
483
+ */
484
+ const checkTracks = (when) => {
485
+ // If tracks have already been found, expect them to still be found.
486
+ const tracksNow = player.getVariantTracks().length != 0;
487
+ if (tracksFound) {
488
+ expect(tracksNow).toBe(true);
489
+ } else {
490
+ // If tracks are now found, they should not, at any point during
491
+ // the loading process, disappear again.
492
+ if (tracksNow) {
493
+ tracksFound = true;
494
+ }
495
+ }
496
+ shaka.log.debug(
497
+ 'checkTracks', when,
498
+ 'tracksFound=', tracksFound,
499
+ 'tracksNow=', tracksNow);
500
+ };
501
+
502
+ /** @param {Event} event */
503
+ const checkOnEvent = (event) => {
504
+ checkTracks(event.type + ' event');
505
+ };
506
+
507
+ // On each of these events, we will notice when tracks first appear, and
508
+ // verify that they never disappear at any point in the loading sequence.
509
+ eventManager.listen(video, 'canplay', checkOnEvent);
510
+ eventManager.listen(video, 'canplaythrough', checkOnEvent);
511
+ eventManager.listen(video, 'durationchange', checkOnEvent);
512
+ eventManager.listen(video, 'emptied', checkOnEvent);
513
+ eventManager.listen(video, 'loadeddata', checkOnEvent);
514
+ eventManager.listen(video, 'loadedmetadata', checkOnEvent);
515
+ eventManager.listen(video, 'loadstart', checkOnEvent);
516
+ eventManager.listen(video, 'pause', checkOnEvent);
517
+ eventManager.listen(video, 'play', checkOnEvent);
518
+ eventManager.listen(video, 'playing', checkOnEvent);
519
+ eventManager.listen(video, 'seeked', checkOnEvent);
520
+ eventManager.listen(video, 'seeking', checkOnEvent);
521
+ eventManager.listen(video, 'stalled', checkOnEvent);
522
+ eventManager.listen(video, 'waiting', checkOnEvent);
523
+ eventManager.listen(player, 'trackschanged', checkOnEvent);
524
+
525
+ const waiter = (new shaka.test.Waiter(eventManager)).timeoutAfter(10);
526
+ const canPlayThrough = waiter.waitForEvent(video, 'canplaythrough');
527
+
528
+ // Important: use a stream that starts somewhere other than zero, so that
529
+ // the video element's time is initially different from the start time of
530
+ // playback, and there is no content at time zero.
531
+ await player.load('test:sintel_start_at_3_compiled', 5);
532
+ shaka.log.debug('load resolved');
533
+
534
+ // When load is resolved(), tracks should definitely exist.
535
+ expect(tracksFound).toBe(true);
536
+
537
+ // Let the test keep running until we can play through. In the original
538
+ // bug, tracks would disappear _after_ load() on some platforms.
539
+ await canPlayThrough;
540
+ });
541
+ });
467
542
  });
468
543
 
469
544
  // TODO(vaage): Try to group the stat tests together.
@@ -187,6 +187,12 @@ jasmine.Matchers.prototype.toHaveBeenCalledOnceMore = function() {};
187
187
  */
188
188
  jasmine.Matchers.prototype.toHaveBeenCalledOnceMoreWith = function(args) {};
189
189
 
190
+ /**
191
+ * @param {string} message
192
+ * @return {!jasmine.Matchers}
193
+ */
194
+ jasmine.Matchers.prototype.withContext = function(message) {};
195
+
190
196
 
191
197
  /** @type {!jasmine.MatchersAsync} */
192
198
  jasmine.MatchersAsync.prototype.not;
@@ -126,6 +126,37 @@ shaka.test.TestScheme.DATA = {
126
126
  duration: 30,
127
127
  },
128
128
 
129
+ // Like 'sintel' above, but with a non-zero period start time.
130
+ // This helps expose edge cases around startup and live streams.
131
+ 'sintel_start_at_3': {
132
+ periodStart: 3,
133
+ video: {
134
+ initSegmentUri: '/base/test/test/assets/sintel-video-init.mp4',
135
+ mdhdOffset: 0x1ba,
136
+ segmentUri: '/base/test/test/assets/sintel-video-segment.mp4',
137
+ tfdtOffset: 0x38,
138
+ segmentDuration: 10,
139
+ presentationTimeOffset: 0,
140
+ mimeType: 'video/mp4',
141
+ codecs: 'avc1.42c01e',
142
+ },
143
+ audio: {
144
+ initSegmentUri: '/base/test/test/assets/sintel-audio-init.mp4',
145
+ mdhdOffset: 0x1b6,
146
+ segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
147
+ tfdtOffset: 0x3c,
148
+ segmentDuration: 10.005,
149
+ presentationTimeOffset: 0,
150
+ mimeType: 'audio/mp4',
151
+ codecs: 'mp4a.40.2',
152
+ },
153
+ text: {
154
+ uri: '/base/test/test/assets/text-clip.vtt',
155
+ mimeType: 'text/vtt',
156
+ },
157
+ duration: 30,
158
+ },
159
+
129
160
  // Like 'sintel' above, but with languages and delayed setup.
130
161
  // These extra features help expose some edge cases.
131
162
  'sintel_realistic': {
@@ -477,7 +508,7 @@ shaka.test.TestScheme.createManifests = function(shaka, suffix) {
477
508
 
478
509
  let gen = new windowShaka.test.ManifestGenerator(shaka)
479
510
  .setPresentationDuration(data.duration)
480
- .addPeriod(/* startTime= */ 0)
511
+ .addPeriod(/* startTime= */ data.periodStart || 0)
481
512
  .addVariant(0);
482
513
 
483
514
  if (data[ContentType.VIDEO]) {
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2016 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+
19
+ goog.provide('shaka.test.UiUtils');
20
+
21
+
22
+ shaka.test.UiUtils = class {
23
+ /**
24
+ * Simulates a native event (e.g. 'click') on the given element.
25
+ *
26
+ * @param {EventTarget} target
27
+ * @param {string} name
28
+ */
29
+ static simulateEvent(target, name) {
30
+ const type = {
31
+ 'click': 'MouseEvent',
32
+ 'dblclick': 'MouseEvent',
33
+ }[name] || 'CustomEvent';
34
+
35
+ // Note we can't use the MouseEvent constructor since it isn't supported on
36
+ // IE11.
37
+ const event = document.createEvent(type);
38
+ event.initEvent(name, true, true);
39
+ target.dispatchEvent(event);
40
+ }
41
+ };