shaka-player 4.2.10 → 4.2.12

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 (64) hide show
  1. package/.github/workflows/appspot.yaml +39 -0
  2. package/.github/workflows/demo-version-index.yaml +3 -3
  3. package/CHANGELOG.md +42 -0
  4. package/CONTRIBUTORS +1 -0
  5. package/README.md +3 -0
  6. package/demo/custom.js +1 -1
  7. package/demo/demo.less +1 -0
  8. package/dist/demo.compiled.debug.js +2 -2
  9. package/dist/demo.compiled.debug.map +2 -2
  10. package/dist/demo.compiled.js +2 -2
  11. package/dist/demo.compiled.map +2 -2
  12. package/dist/demo.css +1 -1
  13. package/dist/demo.css.map +1 -1
  14. package/dist/deps.js +2 -2
  15. package/dist/locales.js +31 -31
  16. package/dist/shaka-player.compiled.d.ts +30 -0
  17. package/dist/shaka-player.compiled.debug.d.ts +30 -0
  18. package/dist/shaka-player.compiled.debug.externs.js +9 -0
  19. package/dist/shaka-player.compiled.debug.js +250 -246
  20. package/dist/shaka-player.compiled.debug.map +3 -3
  21. package/dist/shaka-player.compiled.externs.js +9 -0
  22. package/dist/shaka-player.compiled.js +789 -787
  23. package/dist/shaka-player.compiled.map +4 -4
  24. package/dist/shaka-player.ui.d.ts +30 -0
  25. package/dist/shaka-player.ui.debug.d.ts +30 -0
  26. package/dist/shaka-player.ui.debug.externs.js +9 -0
  27. package/dist/shaka-player.ui.debug.js +290 -285
  28. package/dist/shaka-player.ui.debug.map +3 -3
  29. package/dist/shaka-player.ui.externs.js +9 -0
  30. package/dist/shaka-player.ui.js +943 -941
  31. package/dist/shaka-player.ui.map +4 -4
  32. package/docs/tutorials/drm-config.md +9 -6
  33. package/docs/tutorials/welcome.md +1 -1
  34. package/externs/ima.js +34 -0
  35. package/lib/abr/simple_abr_manager.js +1 -1
  36. package/lib/cea/mp4_cea_parser.js +30 -14
  37. package/lib/dash/dash_parser.js +18 -3
  38. package/lib/hls/hls_parser.js +19 -2
  39. package/lib/hls/manifest_text_parser.js +10 -1
  40. package/lib/media/drm_engine.js +23 -14
  41. package/lib/media/gap_jumping_controller.js +24 -2
  42. package/lib/media/media_source_engine.js +10 -6
  43. package/lib/media/playhead.js +11 -0
  44. package/lib/media/region_observer.js +19 -1
  45. package/lib/media/segment_reference.js +22 -0
  46. package/lib/media/streaming_engine.js +16 -7
  47. package/lib/media/video_wrapper.js +6 -1
  48. package/lib/player.js +13 -4
  49. package/lib/polyfill/abort_controller.js +7 -0
  50. package/lib/text/vtt_text_parser.js +2 -1
  51. package/lib/util/manifest_parser_utils.js +1 -1
  52. package/lib/util/mp4_box_parsers.js +8 -3
  53. package/lib/util/stream_utils.js +1 -0
  54. package/package.json +9 -4
  55. package/test/cea/mp4_cea_parser_unit.js +26 -3
  56. package/test/dash/dash_parser_manifest_unit.js +120 -0
  57. package/test/hls/hls_live_unit.js +3 -1
  58. package/test/hls/hls_parser_unit.js +150 -0
  59. package/test/media/media_source_engine_unit.js +31 -0
  60. package/test/media/region_observer_unit.js +2 -1
  61. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  62. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  63. package/test/test/util/layout_tests.js +4 -3
  64. 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
  ```
@@ -29,7 +29,7 @@ You can build Shaka on Linux, Windows, or Mac.
29
29
  To get the sources and compile the library, you will need:
30
30
  * {@link https://git-scm.com/downloads Git v1.9+}
31
31
  * {@link https://www.python.org/downloads/ Python v2.7 or v3.5+}
32
- * {@link https://java.com/en/download/ Java Runtime Environment v14+}
32
+ * {@link https://learn.microsoft.com/en-us/java/openjdk/download Java Runtime Environment v14+}
33
33
  * {@link https://nodejs.org/en/download/ NodeJS v14+}
34
34
  * A local web server, such as {@link https://httpd.apache.org/ Apache}
35
35
  * _NOTE: A local web server is required because browsers place restrictions
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
+ };
@@ -54,7 +54,7 @@ shaka.abr.SimpleAbrManager = class {
54
54
  // of connectivity changes.
55
55
  if (navigator.connection && navigator.connection.addEventListener) {
56
56
  this.onNetworkInformationChange_ = () => {
57
- if (this.config_.useNetworkInformation && this.enabled_) {
57
+ if (this.enabled_ && this.config_.useNetworkInformation) {
58
58
  this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
59
59
  if (this.config_) {
60
60
  this.bandwidthEstimator_.configure(this.config_.advanced);
@@ -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,
@@ -2131,6 +2138,9 @@ shaka.hls.HlsParser = class {
2131
2138
  /* tileDuration= */ null,
2132
2139
  /* syncTime= */ null,
2133
2140
  partialStatus);
2141
+ if (item.name == 'EXT-X-PRELOAD-HINT') {
2142
+ partial.markAsPreload();
2143
+ }
2134
2144
  partialSegmentRefs.push(partial);
2135
2145
  } // for-loop of hlsSegment.partialSegments
2136
2146
  }
@@ -2147,6 +2157,10 @@ shaka.hls.HlsParser = class {
2147
2157
  // We're interested in the duration part.
2148
2158
  const extinfValues = extinfTag.value.split(',');
2149
2159
  const duration = Number(extinfValues[0]);
2160
+ // Skip segments without duration
2161
+ if (duration == 0) {
2162
+ return null;
2163
+ }
2150
2164
  endTime = startTime + duration;
2151
2165
  } else {
2152
2166
  endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
@@ -2338,9 +2352,9 @@ shaka.hls.HlsParser = class {
2338
2352
  playlist.absoluteUri,
2339
2353
  type,
2340
2354
  hlsAes128Key);
2341
- previousReference = reference;
2342
2355
 
2343
2356
  if (reference) {
2357
+ previousReference = reference;
2344
2358
  reference.discontinuitySequence = discontinuitySequence;
2345
2359
 
2346
2360
  if (this.config_.hls.ignoreManifestProgramDateTime &&
@@ -2451,6 +2465,9 @@ shaka.hls.HlsParser = class {
2451
2465
  * @private
2452
2466
  */
2453
2467
  variableSubstitution_(uri, variables) {
2468
+ if (!variables.size) {
2469
+ return uri;
2470
+ }
2454
2471
  let newUri = String(uri).replace(/%7B/g, '{').replace(/%7D/g, '}');
2455
2472
 
2456
2473
  const uriVariables = newUri.match(/{\$\w*}/g);
@@ -158,7 +158,15 @@ shaka.hls.ManifestTextParser = class {
158
158
  partialSegmentTags.push(tag);
159
159
  } else if (tag.name == 'EXT-X-PRELOAD-HINT') {
160
160
  if (tag.getAttributeValue('TYPE') == 'PART') {
161
- partialSegmentTags.push(tag);
161
+ // Note: BYTERANGE-START without BYTERANGE-LENGTH is being
162
+ // ignored.
163
+ if (tag.getAttributeValue('BYTERANGE-START') != null) {
164
+ if (tag.getAttributeValue('BYTERANGE-LENGTH') != null) {
165
+ partialSegmentTags.push(tag);
166
+ }
167
+ } else {
168
+ partialSegmentTags.push(tag);
169
+ }
162
170
  } else if (tag.getAttributeValue('TYPE') == 'MAP') {
163
171
  // Rename the Preload Hint tag to be a Map tag.
164
172
  tag.setName('EXT-X-MAP');
@@ -300,6 +308,7 @@ shaka.hls.ManifestTextParser.MEDIA_PLAYLIST_TAGS = [
300
308
  'EXT-X-ENDLIST',
301
309
  'EXT-X-SERVER-CONTROL',
302
310
  'EXT-X-SKIP',
311
+ 'EXT-X-PART-INF',
303
312
  ];
304
313
 
305
314
 
@@ -133,6 +133,9 @@ shaka.media.DrmEngine = class {
133
133
 
134
134
  /** @private {Promise} */
135
135
  this.mediaKeysAttached_ = null;
136
+
137
+ /** @private {?shaka.extern.InitDataOverride} */
138
+ this.manifestInitData_ = null;
136
139
  }
137
140
 
138
141
  /** @override */
@@ -351,18 +354,24 @@ shaka.media.DrmEngine = class {
351
354
  shaka.media.DrmEngine.replaceDrmInfo_(variants, servers);
352
355
  }
353
356
 
354
- // Make sure all the drm infos are valid and filled in correctly.
357
+ /** @type {!Set<shaka.extern.DrmInfo>} */
358
+ const drmInfos = new Set();
355
359
  for (const variant of variants) {
356
- const drmInfos = this.getVariantDrmInfos_(variant);
357
- for (const info of drmInfos) {
358
- shaka.media.DrmEngine.fillInDrmInfoDefaults_(
359
- info,
360
- shaka.util.MapUtils.asMap(this.config_.servers),
361
- shaka.util.MapUtils.asMap(this.config_.advanced || {}),
362
- this.config_.keySystemsMapping);
360
+ const variantDrmInfos = this.getVariantDrmInfos_(variant);
361
+ for (const info of variantDrmInfos) {
362
+ drmInfos.add(info);
363
363
  }
364
364
  }
365
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
+
366
375
  /** @type {!Map.<string, MediaKeySystemConfiguration>} */
367
376
  let configsByKeySystem;
368
377
 
@@ -480,10 +489,10 @@ shaka.media.DrmEngine = class {
480
489
  () => this.closeOpenSessions_());
481
490
  }
482
491
 
483
- const manifestInitData = this.currentDrmInfo_ ?
484
- this.currentDrmInfo_.initData.find(
492
+ this.manifestInitData_ = this.currentDrmInfo_ ?
493
+ (this.currentDrmInfo_.initData.find(
485
494
  (initDataOverride) => initDataOverride.initData.length > 0,
486
- ) : null;
495
+ ) || null) : null;
487
496
 
488
497
  /**
489
498
  * We can attach media keys before the playback actually begins when:
@@ -491,7 +500,7 @@ shaka.media.DrmEngine = class {
491
500
  * - Some initData already has been generated (through the manifest)
492
501
  * - In case of an offline session
493
502
  */
494
- if (manifestInitData ||
503
+ if (this.manifestInitData_ ||
495
504
  this.currentDrmInfo_.keySystem !== 'com.apple.fps' ||
496
505
  this.offlineSessionIds_.length) {
497
506
  await this.attachMediaKeys_();
@@ -504,7 +513,7 @@ shaka.media.DrmEngine = class {
504
513
  // Also suppress 'encrypted' events when parsing in-band ppsh
505
514
  // from media segments because that serves the same purpose as the
506
515
  // 'encrypted' events.
507
- if (!manifestInitData && !this.offlineSessionIds_.length &&
516
+ if (!this.manifestInitData_ && !this.offlineSessionIds_.length &&
508
517
  !this.config_.parseInbandPsshEnabled) {
509
518
  this.eventManager_.listen(
510
519
  this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
@@ -2353,7 +2362,7 @@ shaka.media.DrmEngine = class {
2353
2362
  * @return {!Promise<void>}
2354
2363
  */
2355
2364
  parseInbandPssh(contentType, mediaSegment) {
2356
- if (!this.config_.parseInbandPsshEnabled) {
2365
+ if (!this.config_.parseInbandPsshEnabled || this.manifestInitData_) {
2357
2366
  return Promise.resolve();
2358
2367
  }
2359
2368
 
@@ -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));
@@ -99,15 +99,15 @@ shaka.media.MediaSourceEngine = class {
99
99
  /** @private {!shaka.util.PublicPromise} */
100
100
  this.mediaSourceOpen_ = new shaka.util.PublicPromise();
101
101
 
102
+ /** @private {string} */
103
+ this.url_ = '';
104
+
102
105
  /** @private {MediaSource} */
103
106
  this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
104
107
 
105
108
  /** @type {!shaka.util.Destroyer} */
106
109
  this.destroyer_ = new shaka.util.Destroyer(() => this.doDestroy_());
107
110
 
108
- /** @private {string} */
109
- this.url_ = '';
110
-
111
111
  /** @private {boolean} */
112
112
  this.sequenceMode_ = false;
113
113
 
@@ -144,6 +144,8 @@ shaka.media.MediaSourceEngine = class {
144
144
  * @private
145
145
  */
146
146
  onSourceOpen_(p) {
147
+ goog.asserts.assert(this.url_, 'Must have object URL');
148
+
147
149
  // Release the object URL that was previously created, to prevent memory
148
150
  // leak.
149
151
  // createObjectURL creates a strong reference to the MediaSource object
@@ -527,11 +529,13 @@ shaka.media.MediaSourceEngine = class {
527
529
  * @param {boolean=} seeked True if we just seeked
528
530
  * @param {boolean=} adaptation True if we just automatically switched active
529
531
  * variant(s).
532
+ * @param {boolean=} isChunkedData True if we add to the buffer from the
533
+ * partial read of the segment.
530
534
  * @return {!Promise}
531
535
  */
532
536
  async appendBuffer(
533
537
  contentType, data, reference, hasClosedCaptions, seeked = false,
534
- adaptation = false) {
538
+ adaptation = false, isChunkedData = false) {
535
539
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
536
540
 
537
541
  if (contentType == ContentType.TEXT) {
@@ -704,13 +708,13 @@ shaka.media.MediaSourceEngine = class {
704
708
  let bufferedBefore = null;
705
709
 
706
710
  await this.enqueueOperation_(contentType, () => {
707
- if (goog.DEBUG && reference) {
711
+ if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
708
712
  bufferedBefore = this.getBuffered_(contentType);
709
713
  }
710
714
  this.append_(contentType, data);
711
715
  });
712
716
 
713
- if (goog.DEBUG && reference) {
717
+ if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
714
718
  const bufferedAfter = this.getBuffered_(contentType);
715
719
  const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
716
720
  bufferedBefore, bufferedAfter);
@@ -251,6 +251,7 @@ shaka.media.MediaSourcePlayhead = class {
251
251
  this.videoWrapper_ = new shaka.media.VideoWrapper(
252
252
  mediaElement,
253
253
  () => this.onSeeking_(),
254
+ (realStartTime) => this.onStarted_(realStartTime),
254
255
  this.getStartTime_(startTime));
255
256
 
256
257
  /** @type {shaka.util.Timer} */
@@ -390,6 +391,16 @@ shaka.media.MediaSourcePlayhead = class {
390
391
  }
391
392
  }
392
393
 
394
+ /**
395
+ * Called when the video element has started up and is listening for new seeks
396
+ *
397
+ * @param {number} startTime
398
+ * @private
399
+ */
400
+ onStarted_(startTime) {
401
+ this.gapController_.onStarted(startTime);
402
+ }
403
+
393
404
  /**
394
405
  * Handles when a seek happens on the video.
395
406
  *
@@ -27,13 +27,24 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
27
27
  * destroy the timeline.
28
28
  *
29
29
  * @param {!shaka.media.RegionTimeline} timeline
30
+ * @param {boolean} startsPastZero
30
31
  */
31
- constructor(timeline) {
32
+ constructor(timeline, startsPastZero) {
32
33
  super();
33
34
 
34
35
  /** @private {shaka.media.RegionTimeline} */
35
36
  this.timeline_ = timeline;
36
37
 
38
+ /**
39
+ * Whether the asset is expected to start at a time beyond 0 seconds.
40
+ * For example, if the asset is a live stream.
41
+ * If true, we will not start polling for regions until the playhead has
42
+ * moved past 0 seconds, to avoid bad behaviors where the current time is
43
+ * briefly 0 before we have enough data to play.
44
+ * @private {boolean}
45
+ */
46
+ this.startsPastZero_ = startsPastZero;
47
+
37
48
  /**
38
49
  * A mapping between a region and where we previously were relative to it.
39
50
  * When the value here differs from what we calculate, it means we moved and
@@ -121,6 +132,13 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
121
132
  /** @override */
122
133
  poll(positionInSeconds, wasSeeking) {
123
134
  const RegionObserver = shaka.media.RegionObserver;
135
+ if (this.startsPastZero_ && positionInSeconds == 0) {
136
+ // Don't start checking regions until the timeline has begun moving.
137
+ return;
138
+ }
139
+ // Now that we have seen the playhead go past 0, it's okay if it goes
140
+ // back there (e.g. seeking back to the start).
141
+ this.startsPastZero_ = false;
124
142
 
125
143
  for (const region of this.timeline_.regions()) {
126
144
  const previousPosition = this.oldPosition_.get(region);
@@ -238,6 +238,9 @@ shaka.media.SegmentReference = class {
238
238
  /** @type {shaka.media.SegmentReference.Status} */
239
239
  this.status = status;
240
240
 
241
+ /** @type {boolean} */
242
+ this.preload = false;
243
+
241
244
  /** @type {?shaka.extern.HlsAes128Key} */
242
245
  this.hlsAes128Key = hlsAes128Key;
243
246
 
@@ -358,6 +361,25 @@ shaka.media.SegmentReference = class {
358
361
  this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
359
362
  }
360
363
 
364
+ /**
365
+ * Mark the reference as preload.
366
+ *
367
+ * @export
368
+ */
369
+ markAsPreload() {
370
+ this.preload = true;
371
+ }
372
+
373
+ /**
374
+ * Returns true if the segment is preloaded.
375
+ *
376
+ * @return {boolean}
377
+ * @export
378
+ */
379
+ isPreload() {
380
+ return this.preload;
381
+ }
382
+
361
383
  /**
362
384
  * Offset the segment reference by a fixed amount.
363
385
  *