shaka-player 4.2.11 → 4.2.13

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 (73) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. package/.github/workflows/appspot.yaml +39 -0
  3. package/.github/workflows/build-and-test.yaml +15 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +3 -2
  5. package/.github/workflows/update-issues.yaml +1 -0
  6. package/CHANGELOG.md +42 -0
  7. package/CONTRIBUTORS +1 -0
  8. package/demo/custom.js +1 -1
  9. package/dist/demo.compiled.debug.js +2 -2
  10. package/dist/demo.compiled.debug.map +2 -2
  11. package/dist/demo.compiled.js +2 -2
  12. package/dist/demo.compiled.map +2 -2
  13. package/dist/deps.js +3 -3
  14. package/dist/locales.js +33 -33
  15. package/dist/shaka-player.compiled.d.ts +22 -0
  16. package/dist/shaka-player.compiled.debug.d.ts +22 -0
  17. package/dist/shaka-player.compiled.debug.js +233 -232
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.js +738 -738
  20. package/dist/shaka-player.compiled.map +3 -3
  21. package/dist/shaka-player.ui.d.ts +22 -0
  22. package/dist/shaka-player.ui.debug.d.ts +22 -0
  23. package/dist/shaka-player.ui.debug.js +259 -258
  24. package/dist/shaka-player.ui.debug.map +3 -3
  25. package/dist/shaka-player.ui.js +917 -916
  26. package/dist/shaka-player.ui.map +4 -4
  27. package/docs/tutorials/drm-config.md +9 -6
  28. package/docs/tutorials/selenium-grid-config.md +60 -8
  29. package/externs/ima.js +34 -0
  30. package/karma.conf.js +50 -7
  31. package/lib/abr/simple_abr_manager.js +6 -6
  32. package/lib/cea/mp4_cea_parser.js +30 -14
  33. package/lib/dash/dash_parser.js +18 -3
  34. package/lib/hls/hls_parser.js +13 -2
  35. package/lib/media/drm_engine.js +14 -8
  36. package/lib/media/gap_jumping_controller.js +24 -2
  37. package/lib/media/media_source_engine.js +5 -3
  38. package/lib/media/playhead.js +11 -0
  39. package/lib/media/region_observer.js +19 -1
  40. package/lib/media/streaming_engine.js +7 -3
  41. package/lib/media/video_wrapper.js +6 -1
  42. package/lib/player.js +9 -8
  43. package/lib/polyfill/abort_controller.js +7 -0
  44. package/lib/polyfill/orientation.js +34 -7
  45. package/lib/text/vtt_text_parser.js +3 -1
  46. package/lib/util/manifest_parser_utils.js +5 -3
  47. package/lib/util/mp4_box_parsers.js +8 -3
  48. package/lib/util/platform.js +14 -0
  49. package/lib/util/stream_utils.js +70 -26
  50. package/package.json +2 -3
  51. package/test/cast/cast_utils_unit.js +4 -1
  52. package/test/cea/mp4_cea_parser_unit.js +26 -3
  53. package/test/dash/dash_parser_manifest_unit.js +120 -0
  54. package/test/hls/hls_parser_unit.js +222 -0
  55. package/test/media/drm_engine_integration.js +2 -2
  56. package/test/media/region_observer_unit.js +2 -1
  57. package/test/media/segment_index_unit.js +3 -3
  58. package/test/media/streaming_engine_integration.js +14 -11
  59. package/test/offline/offline_integration.js +6 -3
  60. package/test/player_external.js +19 -16
  61. package/test/player_integration.js +35 -48
  62. package/test/player_src_equals_integration.js +9 -5
  63. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  64. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  65. package/test/test/util/fake_demo_main.js +7 -3
  66. package/test/test/util/layout_tests.js +2 -2
  67. package/test/test/util/ui_utils.js +9 -2
  68. package/test/test/util/util.js +6 -27
  69. package/test/test/util/waiter.js +73 -3
  70. package/test/ui/ad_ui_unit.js +9 -9
  71. package/test/util/stream_utils_unit.js +46 -0
  72. package/ui/range_element.js +5 -0
  73. package/ui/resolution_selection.js +7 -1
@@ -59,15 +59,18 @@ If this is the only `<ContentProtection>` element in the manifest, Shaka will
59
59
  try all key systems it knows. (Based on keySystemsByURI in
60
60
  {@linksource shaka.extern.DashManifestConfiguration}.)
61
61
 
62
- Through `player.configure()`, you can change the dash key systems mapping by
62
+ Through `player.configure()`, you can update the dash key systems mapping by
63
63
  scheme URI:
64
+
64
65
  ```js
65
66
  player.configure({
66
- dash: {
67
- keySystemsByURI: {
68
- 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
69
- 'urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
70
- }
67
+ manifest: {
68
+ dash: {
69
+ keySystemsByURI: {
70
+ 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
71
+ 'urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
72
+ }
73
+ }
71
74
  }
72
75
  });
73
76
  ```
@@ -47,17 +47,20 @@ vars:
47
47
  FirefoxMac:
48
48
  browser: firefox
49
49
  os: Mac
50
- extra_config: *firefox_config
50
+ extra_configs:
51
+ - *firefox_config
51
52
 
52
53
  FirefoxWindows:
53
54
  browser: firefox
54
55
  os: Windows
55
- extra_config: *firefox_config
56
+ extra_configs:
57
+ - *firefox_config
56
58
 
57
59
  FirefoxLinux:
58
60
  browser: firefox
59
61
  os: Linux
60
- extra_config: *firefox_config
62
+ extra_configs:
63
+ - *firefox_config
61
64
  ```
62
65
 
63
66
  ### Browsers
@@ -75,8 +78,8 @@ browser made available to Karma. Within each of those keys are the following:
75
78
  and must match the string and case used in the Selenium node config.
76
79
  - `disabled` (optional): If true, this browser is disabled and will not be
77
80
  used unless explicitly requested.
78
- - `extra_config` (optional): A dictionary of extra configs which will be
79
- merged with the WebDriver launcher config in Karma.
81
+ - `extra_configs` (optional): An array of dictionaries of extra configs which
82
+ will be merged with the WebDriver launcher config in Karma.
80
83
 
81
84
  Examples of basic desktop browsers definitions:
82
85
 
@@ -96,9 +99,9 @@ Safari:
96
99
  SafariTP:
97
100
  browser: safari
98
101
  os: Mac
99
- extra_config:
100
- safari.options:
101
- technologyPreview: true
102
+ extra_configs:
103
+ - safari.options:
104
+ technologyPreview: true
102
105
 
103
106
  ChromeWindows:
104
107
  browser: chrome
@@ -120,3 +123,52 @@ FirefoxLinux:
120
123
  browser: firefox
121
124
  os: Linux
122
125
  ```
126
+
127
+ ### Composing configs
128
+
129
+ You can define variables for browser configs that can be composed together in
130
+ `extra_configs`. For example, below you will see some basic definitions for
131
+ Chrome arguments and parameters, and then an additional config with an argument
132
+ that is only needed for some platforms. `karma.conf.js` will merge those
133
+ argument lists intelligently when it loads the YAML config.
134
+
135
+ ```yaml
136
+ vars:
137
+ basic_chrome_config: &basic_chrome_config
138
+ goog:chromeOptions:
139
+ args:
140
+ # Normally, Chrome disallows autoplaying videos in many cases. Enable
141
+ # it for testing.
142
+ - "--autoplay-policy=no-user-gesture-required"
143
+
144
+ # Instruct chromedriver not to disable component updater.
145
+ excludeSwitches:
146
+ - "disable-component-update"
147
+
148
+ headless_chrome_config: &headless_chrome_config
149
+ goog:chromeOptions:
150
+ args:
151
+ # Run in headless mode.
152
+ - "--headless"
153
+
154
+ ChromeLinux:
155
+ browser: chrome
156
+ os: Mac
157
+ extra_configs:
158
+ - *basic_chrome_config
159
+
160
+ ChromeWindows:
161
+ browser: chrome
162
+ os: Mac
163
+ extra_configs:
164
+ - *basic_chrome_config
165
+
166
+ ChromeMac:
167
+ browser: chrome
168
+ os: Mac
169
+ # Take the parameters and arguments for basic_chrome_config, then append the
170
+ # arguments for headless_chrome_config.
171
+ extra_configs:
172
+ - *basic_chrome_config
173
+ - *headless_chrome_config
174
+ ```
package/externs/ima.js CHANGED
@@ -501,6 +501,14 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
501
501
  google.ima.dai.api.StreamRequest.prototype.format;
502
502
 
503
503
 
504
+ /**
505
+ * @type {Object.<
506
+ * google.ima.dai.api.OmidVerificationVendor,
507
+ * (google.ima.dai.api.OmidAccessMode | undefined)>}
508
+ */
509
+ google.ima.dai.api.StreamRequest.prototype.omidAccessModeRules;
510
+
511
+
504
512
  /**
505
513
  * @enum {string}
506
514
  */
@@ -587,3 +595,29 @@ google.ima.dai.api.StreamEvent.Type = {
587
595
  SKIPPED: 'skip',
588
596
  VIDEO_CLICKED: 'videoClicked',
589
597
  };
598
+
599
+
600
+ /**
601
+ * @enum {number}
602
+ */
603
+ google.ima.dai.api.OmidVerificationVendor = {
604
+ OTHER: 1,
605
+ MOAT: 2,
606
+ DOUBLEVERIFY: 3,
607
+ INTEGRAL_AD_SCIENCE: 4,
608
+ PIXELATE: 5,
609
+ NIELSEN: 6,
610
+ COMSCORE: 7,
611
+ MEETRICS: 8,
612
+ GOOGLE: 9,
613
+ };
614
+
615
+
616
+ /**
617
+ * @enum {string}
618
+ */
619
+ google.ima.dai.api.OmidAccessMode = {
620
+ FULL: 'full',
621
+ DOMAIN: 'domain',
622
+ LIMITED: 'limited',
623
+ };
package/karma.conf.js CHANGED
@@ -7,9 +7,11 @@
7
7
  // Karma configuration
8
8
  // Install required modules by running "npm install"
9
9
 
10
- const Jimp = require('jimp');
10
+ // lodash is an indirect dependency, depended on by Karma
11
+ const _ = require('lodash');
11
12
  const fs = require('fs');
12
13
  const glob = require('glob');
14
+ const Jimp = require('jimp');
13
15
  const path = require('path');
14
16
  const rimraf = require('rimraf');
15
17
  const {ssim} = require('ssim.js');
@@ -17,6 +19,32 @@ const util = require('karma/common/util');
17
19
  const which = require('which');
18
20
  const yaml = require('js-yaml');
19
21
 
22
+ /**
23
+ * Like Object.assign, but recursive and doesn't clobber objects and arrays.
24
+ * If two arrays are merged, they are concatenated.
25
+ * Ex:
26
+ * mergeConfigs({ foo: 'bar', args: [1, 2, 3] },
27
+ * { baz: 'blah', args: [4, 5, 6] })
28
+ * => { foo: 'bar', baz: 'blah', args: [1, 2, 3, 4, 5, 6] }
29
+ *
30
+ * @param {Object} first
31
+ * @param {Object} second
32
+ * @return {Object}
33
+ */
34
+ function mergeConfigs(first, second) {
35
+ return _.mergeWith(
36
+ first,
37
+ second,
38
+ (firstValue, secondValue) => {
39
+ // Merge arrays by concatenation.
40
+ if (Array.isArray(firstValue)) {
41
+ return firstValue.concat(secondValue);
42
+ }
43
+ // Use lodash's default merge behavior for everything else.
44
+ return undefined;
45
+ });
46
+ }
47
+
20
48
  /**
21
49
  * @param {Object} config
22
50
  */
@@ -78,7 +106,7 @@ module.exports = (config) => {
78
106
  }
79
107
 
80
108
  // Add standard WebDriver configs.
81
- Object.assign(launcher, {
109
+ mergeConfigs(launcher, {
82
110
  base: 'WebDriver',
83
111
  config: {hostname: gridHostname, port: gridPort},
84
112
  pseudoActivityInterval: 20000,
@@ -87,8 +115,10 @@ module.exports = (config) => {
87
115
  version: metadata.version,
88
116
  });
89
117
 
90
- if (metadata.extra_config) {
91
- Object.assign(launcher, metadata.extra_config);
118
+ if (metadata.extra_configs) {
119
+ for (const config of metadata.extra_configs) {
120
+ mergeConfigs(launcher, config);
121
+ }
92
122
  }
93
123
  }
94
124
 
@@ -603,15 +633,27 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
603
633
  }
604
634
 
605
635
  /**
636
+ * @param {karma.Launcher.Browser.spec} spec
606
637
  * @param {wd.remote} webDriverClient A WebDriver client, an object from the
607
638
  * "wd" package, created by "wd.remote()".
608
639
  * @return {!Promise.<!Buffer>} A Buffer containing a PNG screenshot
609
640
  */
610
- function getScreenshot(webDriverClient) {
641
+ function getScreenshot(spec, webDriverClient) {
611
642
  return new Promise((resolve, reject) => {
612
643
  webDriverClient.takeScreenshot((error, pngBase64) => {
613
644
  if (error) {
614
645
  reject(error);
646
+ } else if (pngBase64.error) {
647
+ // In some failure cases, pngBase64 is an object with "error",
648
+ // "message", and "stacktrace" fields. This happens, for example,
649
+ // with a timeout from the screenshot command. This is not an
650
+ // expected situation, so log it. The extra newlines keep this from
651
+ // being overwritten on the terminal when running tests against many
652
+ // browsers at once.
653
+ console.log('\n\nUnexpected screenshot failure:\n' +
654
+ ` Error: ${JSON.stringify(pngBase64)}\n` +
655
+ ` WebDriver spec: ${JSON.stringify(spec)}\n\n\n`);
656
+ reject(pngBase64);
615
657
  } else {
616
658
  // Convert the screenshot to a binary buffer.
617
659
  resolve(Buffer.from(pngBase64, 'base64'));
@@ -635,7 +677,8 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
635
677
  }
636
678
 
637
679
  /** @type {!Buffer} */
638
- const fullPageScreenshotData = await getScreenshot(webDriverClient);
680
+ const fullPageScreenshotData =
681
+ await getScreenshot(browser.spec, webDriverClient);
639
682
 
640
683
  // Crop the screenshot to the dimensions specified in the test.
641
684
  // Jimp is picky about types, so convert these strings to numbers.
@@ -754,7 +797,7 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
754
797
  // The result is cached for the sake of performance.
755
798
  if (webDriverClient.canTakeScreenshot === undefined) {
756
799
  try {
757
- await getScreenshot(webDriverClient);
800
+ await getScreenshot(browser.spec, webDriverClient);
758
801
  webDriverClient.canTakeScreenshot = true;
759
802
  } catch (error) {
760
803
  webDriverClient.canTakeScreenshot = false;
@@ -48,13 +48,16 @@ shaka.abr.SimpleAbrManager = class {
48
48
  /** @private {shaka.abr.EwmaBandwidthEstimator} */
49
49
  this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
50
50
 
51
+ /** @private {?function():void} */
52
+ this.onNetworkInformationChange_ = null;
53
+
51
54
  // Some browsers implement the Network Information API, which allows
52
55
  // retrieving information about a user's network connection. We listen
53
56
  // to the change event to be able to make quick changes in case the type
54
57
  // of connectivity changes.
55
58
  if (navigator.connection && navigator.connection.addEventListener) {
56
59
  this.onNetworkInformationChange_ = () => {
57
- if (this.config_.useNetworkInformation && this.enabled_) {
60
+ if (this.enabled_ && this.config_.useNetworkInformation) {
58
61
  this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
59
62
  if (this.config_) {
60
63
  this.bandwidthEstimator_.configure(this.config_.advanced);
@@ -98,9 +101,6 @@ shaka.abr.SimpleAbrManager = class {
98
101
  /** @private {ResizeObserver} */
99
102
  this.resizeObserver_ = null;
100
103
 
101
- /** @private {?function():void} */
102
- this.onNetworkInformationChange_ = null;
103
-
104
104
  /** @private {shaka.util.Timer} */
105
105
  this.resizeObserverTimer_ = new shaka.util.Timer(() => {
106
106
  if (this.config_.restrictToElementSize) {
@@ -175,8 +175,8 @@ shaka.abr.SimpleAbrManager = class {
175
175
  if (this.resizeObserver_ && this.config_.restrictToElementSize) {
176
176
  const devicePixelRatio =
177
177
  this.config_.ignoreDevicePixelRatio ? 1 : window.devicePixelRatio;
178
- maxHeight = this.mediaElement_.clientWidth * devicePixelRatio;
179
- maxWidth = this.mediaElement_.clientHeight * devicePixelRatio;
178
+ maxHeight = this.mediaElement_.clientHeight * devicePixelRatio;
179
+ maxWidth = this.mediaElement_.clientWidth * devicePixelRatio;
180
180
  }
181
181
 
182
182
  // Get sorted Variants.
@@ -9,6 +9,7 @@ goog.provide('shaka.cea.Mp4CeaParser');
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.cea.ICeaParser');
11
11
  goog.require('shaka.cea.SeiProcessor');
12
+ goog.require('shaka.log');
12
13
  goog.require('shaka.util.DataViewReader');
13
14
  goog.require('shaka.util.Error');
14
15
  goog.require('shaka.util.Mp4Parser');
@@ -122,12 +123,19 @@ shaka.cea.Mp4CeaParser = class {
122
123
  // Fields that are found in MOOF boxes
123
124
  let defaultSampleDuration = this.defaultSampleDuration_;
124
125
  let defaultSampleSize = this.defaultSampleSize_;
125
- let sampleData = [];
126
+ let moofOffset = 0;
127
+ /** @type {!Array<shaka.util.ParsedTRUNBox>} */
128
+ let parsedTRUNs = [];
126
129
  let baseMediaDecodeTime = null;
127
130
  let timescale = shaka.cea.ICeaParser.DEFAULT_TIMESCALE_VALUE;
128
131
 
129
132
  new Mp4Parser()
130
- .box('moof', Mp4Parser.children)
133
+ .box('moof', (box) => {
134
+ moofOffset = box.start;
135
+ // trun box parsing is reset on each moof.
136
+ parsedTRUNs = [];
137
+ Mp4Parser.children(box);
138
+ })
131
139
  .box('traf', Mp4Parser.children)
132
140
  .fullBox('trun', (box) => {
133
141
  goog.asserts.assert(
@@ -136,10 +144,8 @@ shaka.cea.Mp4CeaParser = class {
136
144
 
137
145
  const parsedTRUN = shaka.util.Mp4BoxParsers.parseTRUN(
138
146
  box.reader, box.version, box.flags);
139
-
140
- sampleData = parsedTRUN.sampleData;
147
+ parsedTRUNs.push(parsedTRUN);
141
148
  })
142
-
143
149
  .fullBox('tfhd', (box) => {
144
150
  goog.asserts.assert(
145
151
  box.flags != null,
@@ -163,7 +169,6 @@ shaka.cea.Mp4CeaParser = class {
163
169
  timescale = this.trackIdToTimescale_.get(trackId);
164
170
  }
165
171
  })
166
-
167
172
  .fullBox('tfdt', (box) => {
168
173
  goog.asserts.assert(
169
174
  box.version != null,
@@ -176,15 +181,18 @@ shaka.cea.Mp4CeaParser = class {
176
181
  })
177
182
  .box('mdat', (box) => {
178
183
  if (baseMediaDecodeTime === null) {
179
- // This field should have been populated by
180
- // the Base Media Decode time in the TFDT box
184
+ // This field should have been populated by the Base Media Decode
185
+ // Time in the tfdt box.
186
+ shaka.log.alwaysWarn(
187
+ 'Unable to find base media decode time for CEA captions!');
181
188
  throw new shaka.util.Error(
182
189
  shaka.util.Error.Severity.CRITICAL,
183
190
  shaka.util.Error.Category.TEXT,
184
191
  shaka.util.Error.Code.INVALID_MP4_CEA);
185
192
  }
193
+ const offset = moofOffset - box.start - 8;
186
194
  this.parseMdat_(box.reader, baseMediaDecodeTime, timescale,
187
- defaultSampleDuration, defaultSampleSize, sampleData,
195
+ defaultSampleDuration, defaultSampleSize, offset, parsedTRUNs,
188
196
  captionPackets);
189
197
  })
190
198
  .parse(mediaSegment, /* partialOkay= */ false);
@@ -199,12 +207,13 @@ shaka.cea.Mp4CeaParser = class {
199
207
  * @param {number} timescale
200
208
  * @param {number} defaultSampleDuration
201
209
  * @param {number} defaultSampleSize
202
- * @param {!Array<shaka.util.ParsedTRUNSample>} sampleData
210
+ * @param {number} offset
211
+ * @param {!Array<shaka.util.ParsedTRUNBox>} parsedTRUNs
203
212
  * @param {!Array<!shaka.cea.ICeaParser.CaptionPacket>} captionPackets
204
213
  * @private
205
214
  */
206
215
  parseMdat_(reader, time, timescale, defaultSampleDuration,
207
- defaultSampleSize, sampleData, captionPackets) {
216
+ defaultSampleSize, offset, parsedTRUNs, captionPackets) {
208
217
  let sampleIndex = 0;
209
218
 
210
219
  // The fields in each ParsedTRUNSample contained in the sampleData
@@ -213,17 +222,24 @@ shaka.cea.Mp4CeaParser = class {
213
222
  // composition time offset, we default to 0.
214
223
  let sampleSize = defaultSampleSize;
215
224
 
225
+ // Combine all sample data. This assumes that the samples described across
226
+ // multiple trun boxes are still continuous in the mdat box.
227
+ const sampleDatas = parsedTRUNs.map((t) => t.sampleData);
228
+ const sampleData = [].concat(...sampleDatas);
229
+
216
230
  if (sampleData.length) {
217
231
  sampleSize = sampleData[0].sampleSize || defaultSampleSize;
218
232
  }
219
233
 
234
+ reader.skip(offset + parsedTRUNs[0].dataOffset);
235
+
220
236
  while (reader.hasMoreData()) {
221
237
  const naluSize = reader.readUint32();
222
238
  const naluType = reader.readUint8() & 0x1F;
223
239
  if (naluType == shaka.cea.ICeaParser.NALU_TYPE_SEI) {
224
240
  let timeOffset = 0;
225
241
 
226
- if (sampleData.length > sampleIndex) {
242
+ if (sampleIndex < sampleData.length) {
227
243
  timeOffset = sampleData[sampleIndex].sampleCompositionTimeOffset || 0;
228
244
  }
229
245
 
@@ -246,7 +262,7 @@ shaka.cea.Mp4CeaParser = class {
246
262
  }
247
263
  sampleSize -= (naluSize + 4);
248
264
  if (sampleSize == 0) {
249
- if (sampleData.length > sampleIndex) {
265
+ if (sampleIndex < sampleData.length) {
250
266
  time += sampleData[sampleIndex].sampleDuration ||
251
267
  defaultSampleDuration;
252
268
  } else {
@@ -255,7 +271,7 @@ shaka.cea.Mp4CeaParser = class {
255
271
 
256
272
  sampleIndex++;
257
273
 
258
- if (sampleData.length > sampleIndex) {
274
+ if (sampleIndex < sampleData.length) {
259
275
  sampleSize = sampleData[sampleIndex].sampleSize || defaultSampleSize;
260
276
  } else {
261
277
  sampleSize = defaultSampleSize;
@@ -886,7 +886,19 @@ shaka.dash.DashParser = class {
886
886
 
887
887
  // Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
888
888
  let videoRange;
889
- const videoRangeScheme = 'urn:mpeg:mpegB:cicp:TransferCharacteristics';
889
+
890
+ // Ref. https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf
891
+ // If signaled, a Supplemental or Essential Property descriptor
892
+ // shall be used, with the schemeIdUri set to
893
+ // urn:mpeg:mpegB:cicp:<Parameter> as defined in
894
+ // ISO/IEC 23001-8 [49] and <Parameter> one of the
895
+ // following: ColourPrimaries, TransferCharacteristics,
896
+ // or MatrixCoefficients.
897
+ const scheme = 'urn:mpeg:mpegB:cicp';
898
+ const transferCharacteristicsScheme = `${scheme}:TransferCharacteristics`;
899
+ const colourPrimariesScheme = `${scheme}:ColourPrimaries`;
900
+ const matrixCoefficientsScheme = `${scheme}:MatrixCoefficients`;
901
+
890
902
  const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
891
903
  switch (cicp) {
892
904
  case 1:
@@ -912,10 +924,13 @@ shaka.dash.DashParser = class {
912
924
  const schemeId = prop.getAttribute('schemeIdUri');
913
925
  if (schemeId == 'http://dashif.org/guidelines/trickmode') {
914
926
  trickModeFor = prop.getAttribute('value');
915
- } else if (schemeId == videoRangeScheme) {
927
+ } else if (schemeId == transferCharacteristicsScheme) {
916
928
  videoRange = getVideoRangeFromTransferCharacteristicCICP(
917
929
  parseInt(prop.getAttribute('value'), 10),
918
930
  );
931
+ } else if (schemeId == colourPrimariesScheme ||
932
+ schemeId == matrixCoefficientsScheme) {
933
+ continue;
919
934
  } else {
920
935
  unrecognizedEssentialProperty = true;
921
936
  }
@@ -925,7 +940,7 @@ shaka.dash.DashParser = class {
925
940
  XmlUtils.findChildren(elem, 'SupplementalProperty');
926
941
  for (const prop of supplementalProperties) {
927
942
  const schemeId = prop.getAttribute('schemeIdUri');
928
- if (schemeId == videoRangeScheme) {
943
+ if (schemeId == transferCharacteristicsScheme) {
929
944
  videoRange = getVideoRangeFromTransferCharacteristicCICP(
930
945
  parseInt(prop.getAttribute('value'), 10),
931
946
  );
@@ -1713,6 +1713,13 @@ shaka.hls.HlsParser = class {
1713
1713
  }
1714
1714
  }
1715
1715
 
1716
+ // If there are no roles, and we have defaulted to the subtitle "kind" for
1717
+ // this track, add the implied subtitle role.
1718
+ if (!roles.length &&
1719
+ kind === shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE) {
1720
+ roles.push(shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE);
1721
+ }
1722
+
1716
1723
  const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
1717
1724
  playlist.tags, 'EXT-X-SERVER-CONTROL');
1718
1725
  const canSkipSegments = serverControlTag ?
@@ -1742,7 +1749,7 @@ shaka.hls.HlsParser = class {
1742
1749
  width: undefined,
1743
1750
  height: undefined,
1744
1751
  bandwidth: undefined,
1745
- roles: roles,
1752
+ roles,
1746
1753
  forced: forced,
1747
1754
  channelsCount,
1748
1755
  audioSamplingRate: null,
@@ -2150,6 +2157,10 @@ shaka.hls.HlsParser = class {
2150
2157
  // We're interested in the duration part.
2151
2158
  const extinfValues = extinfTag.value.split(',');
2152
2159
  const duration = Number(extinfValues[0]);
2160
+ // Skip segments without duration
2161
+ if (duration == 0) {
2162
+ return null;
2163
+ }
2153
2164
  endTime = startTime + duration;
2154
2165
  } else {
2155
2166
  endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
@@ -2341,9 +2352,9 @@ shaka.hls.HlsParser = class {
2341
2352
  playlist.absoluteUri,
2342
2353
  type,
2343
2354
  hlsAes128Key);
2344
- previousReference = reference;
2345
2355
 
2346
2356
  if (reference) {
2357
+ previousReference = reference;
2347
2358
  reference.discontinuitySequence = discontinuitySequence;
2348
2359
 
2349
2360
  if (this.config_.hls.ignoreManifestProgramDateTime &&
@@ -354,18 +354,24 @@ shaka.media.DrmEngine = class {
354
354
  shaka.media.DrmEngine.replaceDrmInfo_(variants, servers);
355
355
  }
356
356
 
357
- // Make sure all the drm infos are valid and filled in correctly.
357
+ /** @type {!Set<shaka.extern.DrmInfo>} */
358
+ const drmInfos = new Set();
358
359
  for (const variant of variants) {
359
- const drmInfos = this.getVariantDrmInfos_(variant);
360
- for (const info of drmInfos) {
361
- shaka.media.DrmEngine.fillInDrmInfoDefaults_(
362
- info,
363
- shaka.util.MapUtils.asMap(this.config_.servers),
364
- shaka.util.MapUtils.asMap(this.config_.advanced || {}),
365
- this.config_.keySystemsMapping);
360
+ const variantDrmInfos = this.getVariantDrmInfos_(variant);
361
+ for (const info of variantDrmInfos) {
362
+ drmInfos.add(info);
366
363
  }
367
364
  }
368
365
 
366
+ for (const info of drmInfos) {
367
+ shaka.media.DrmEngine.fillInDrmInfoDefaults_(
368
+ info,
369
+ shaka.util.MapUtils.asMap(this.config_.servers),
370
+ shaka.util.MapUtils.asMap(this.config_.advanced || {}),
371
+ this.config_.keySystemsMapping);
372
+ }
373
+
374
+
369
375
  /** @type {!Map.<string, MediaKeySystemConfiguration>} */
370
376
  let configsByKeySystem;
371
377
 
@@ -58,6 +58,9 @@ shaka.media.GapJumpingController = class {
58
58
  /** @private {number} */
59
59
  this.prevReadyState_ = video.readyState;
60
60
 
61
+ /** @private {number} */
62
+ this.startTime_ = 0;
63
+
61
64
  /** @private {number} */
62
65
  this.gapsJumped_ = 0;
63
66
 
@@ -123,6 +126,18 @@ shaka.media.GapJumpingController = class {
123
126
  this.onPollGapJump_();
124
127
  }
125
128
 
129
+ /**
130
+ * Called when playback has started and the video element is
131
+ * listening for seeks.
132
+ *
133
+ * @param {number} startTime
134
+ */
135
+ onStarted(startTime) {
136
+ if (this.video_.seeking && !this.seekingEventReceived_) {
137
+ this.seekingEventReceived_ = true;
138
+ this.startTime_ = startTime;
139
+ }
140
+ }
126
141
 
127
142
  /** Called when a seek has started. */
128
143
  onSeeking() {
@@ -164,8 +179,8 @@ shaka.media.GapJumpingController = class {
164
179
  // while paused on a livestream. We make an exception for time 0, since we
165
180
  // may be _required_ to seek on startup before play can begin, but only if
166
181
  // autoplay is enabled.
167
- if (this.video_.paused && (this.video_.currentTime != 0 ||
168
- (!this.video_.autoplay && this.video_.currentTime == 0))) {
182
+ if (this.video_.paused && (this.video_.currentTime != this.startTime_ ||
183
+ (!this.video_.autoplay && this.video_.currentTime == this.startTime_))) {
169
184
  return;
170
185
  }
171
186
 
@@ -228,6 +243,13 @@ shaka.media.GapJumpingController = class {
228
243
  }
229
244
 
230
245
  this.video_.currentTime = jumpTo;
246
+ // This accounts for the possibility that we jump a gap at the start
247
+ // position but we jump _into_ another gap. By setting the start
248
+ // position to the new jumpTo we ensure that the check above will
249
+ // pass even though the video is still paused.
250
+ if (currentTime == this.startTime_) {
251
+ this.startTime_ = jumpTo;
252
+ }
231
253
  this.gapsJumped_++;
232
254
  this.onEvent_(
233
255
  new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
@@ -529,11 +529,13 @@ shaka.media.MediaSourceEngine = class {
529
529
  * @param {boolean=} seeked True if we just seeked
530
530
  * @param {boolean=} adaptation True if we just automatically switched active
531
531
  * variant(s).
532
+ * @param {boolean=} isChunkedData True if we add to the buffer from the
533
+ * partial read of the segment.
532
534
  * @return {!Promise}
533
535
  */
534
536
  async appendBuffer(
535
537
  contentType, data, reference, hasClosedCaptions, seeked = false,
536
- adaptation = false) {
538
+ adaptation = false, isChunkedData = false) {
537
539
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
538
540
 
539
541
  if (contentType == ContentType.TEXT) {
@@ -706,13 +708,13 @@ shaka.media.MediaSourceEngine = class {
706
708
  let bufferedBefore = null;
707
709
 
708
710
  await this.enqueueOperation_(contentType, () => {
709
- if (goog.DEBUG && reference) {
711
+ if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
710
712
  bufferedBefore = this.getBuffered_(contentType);
711
713
  }
712
714
  this.append_(contentType, data);
713
715
  });
714
716
 
715
- if (goog.DEBUG && reference && !reference.isPreload()) {
717
+ if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
716
718
  const bufferedAfter = this.getBuffered_(contentType);
717
719
  const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
718
720
  bufferedBefore, bufferedAfter);