shaka-player 4.2.11 → 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 (49) hide show
  1. package/.github/workflows/appspot.yaml +39 -0
  2. package/CHANGELOG.md +25 -0
  3. package/CONTRIBUTORS +1 -0
  4. package/demo/custom.js +1 -1
  5. package/dist/demo.compiled.debug.js +2 -2
  6. package/dist/demo.compiled.debug.map +2 -2
  7. package/dist/demo.compiled.js +2 -2
  8. package/dist/demo.compiled.map +2 -2
  9. package/dist/deps.js +2 -2
  10. package/dist/locales.js +30 -30
  11. package/dist/shaka-player.compiled.d.ts +22 -0
  12. package/dist/shaka-player.compiled.debug.d.ts +22 -0
  13. package/dist/shaka-player.compiled.debug.js +216 -214
  14. package/dist/shaka-player.compiled.debug.map +3 -3
  15. package/dist/shaka-player.compiled.js +735 -734
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +22 -0
  18. package/dist/shaka-player.ui.debug.d.ts +22 -0
  19. package/dist/shaka-player.ui.debug.js +241 -238
  20. package/dist/shaka-player.ui.debug.map +3 -3
  21. package/dist/shaka-player.ui.js +916 -915
  22. package/dist/shaka-player.ui.map +4 -4
  23. package/docs/tutorials/drm-config.md +9 -6
  24. package/externs/ima.js +34 -0
  25. package/lib/abr/simple_abr_manager.js +1 -1
  26. package/lib/cea/mp4_cea_parser.js +30 -14
  27. package/lib/dash/dash_parser.js +18 -3
  28. package/lib/hls/hls_parser.js +13 -2
  29. package/lib/media/drm_engine.js +14 -8
  30. package/lib/media/gap_jumping_controller.js +24 -2
  31. package/lib/media/media_source_engine.js +5 -3
  32. package/lib/media/playhead.js +11 -0
  33. package/lib/media/region_observer.js +19 -1
  34. package/lib/media/streaming_engine.js +7 -3
  35. package/lib/media/video_wrapper.js +6 -1
  36. package/lib/player.js +9 -4
  37. package/lib/polyfill/abort_controller.js +7 -0
  38. package/lib/text/vtt_text_parser.js +2 -1
  39. package/lib/util/manifest_parser_utils.js +1 -1
  40. package/lib/util/mp4_box_parsers.js +8 -3
  41. package/lib/util/stream_utils.js +1 -0
  42. package/package.json +2 -3
  43. package/test/cea/mp4_cea_parser_unit.js +26 -3
  44. package/test/dash/dash_parser_manifest_unit.js +120 -0
  45. package/test/hls/hls_parser_unit.js +150 -0
  46. package/test/media/region_observer_unit.js +2 -1
  47. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  48. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  49. 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
  ```
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,
@@ -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);
@@ -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);
@@ -1308,7 +1308,8 @@ shaka.media.StreamingEngine = class {
1308
1308
  const dataToAppend = remaining.subarray(0, offset);
1309
1309
  remaining = remaining.subarray(offset);
1310
1310
  await this.append_(
1311
- mediaState, presentationTime, stream, reference, dataToAppend);
1311
+ mediaState, presentationTime, stream, reference, dataToAppend,
1312
+ /* isChunkedData= */ true);
1312
1313
  }
1313
1314
  };
1314
1315
 
@@ -1696,10 +1697,12 @@ shaka.media.StreamingEngine = class {
1696
1697
  * @param {shaka.extern.Stream} stream
1697
1698
  * @param {!shaka.media.SegmentReference} reference
1698
1699
  * @param {BufferSource} segment
1700
+ * @param {boolean=} isChunkedData
1699
1701
  * @return {!Promise}
1700
1702
  * @private
1701
1703
  */
1702
- async append_(mediaState, presentationTime, stream, reference, segment) {
1704
+ async append_(mediaState, presentationTime, stream, reference, segment,
1705
+ isChunkedData = false) {
1703
1706
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1704
1707
 
1705
1708
  const hasClosedCaptions = stream.closedCaptions &&
@@ -1732,7 +1735,8 @@ shaka.media.StreamingEngine = class {
1732
1735
  reference,
1733
1736
  hasClosedCaptions,
1734
1737
  seeked,
1735
- adaptation);
1738
+ adaptation,
1739
+ isChunkedData);
1736
1740
  this.destroyer_.ensureNotDestroyed();
1737
1741
  shaka.log.v2(logPrefix, 'appended media segment');
1738
1742
  }
@@ -27,15 +27,19 @@ shaka.media.VideoWrapper = class {
27
27
  /**
28
28
  * @param {!HTMLMediaElement} video
29
29
  * @param {function()} onSeek Called when the video seeks.
30
+ * @param {function(number)} onStarted Called when the video has started.
30
31
  * @param {number} startTime The time to start at.
31
32
  */
32
- constructor(video, onSeek, startTime) {
33
+ constructor(video, onSeek, onStarted, startTime) {
33
34
  /** @private {HTMLMediaElement} */
34
35
  this.video_ = video;
35
36
 
36
37
  /** @private {function()} */
37
38
  this.onSeek_ = onSeek;
38
39
 
40
+ /** @private {function(number)} */
41
+ this.onStarted_ = onStarted;
42
+
39
43
  /** @private {number} */
40
44
  this.startTime_ = startTime;
41
45
 
@@ -165,6 +169,7 @@ shaka.media.VideoWrapper = class {
165
169
  this.started_ = true;
166
170
 
167
171
  this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
172
+ this.onStarted_(this.video_.currentTime);
168
173
  }
169
174
  };
170
175
 
package/lib/player.js CHANGED
@@ -1996,7 +1996,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1996
1996
  this.abrManager_.setMediaElement(mediaElement);
1997
1997
 
1998
1998
  this.playhead_ = this.createPlayhead(has.startTime);
1999
- this.playheadObservers_ = this.createPlayheadObserversForMSE_();
1999
+ this.playheadObservers_ =
2000
+ this.createPlayheadObserversForMSE_(has.startTimeOfLoad);
2000
2001
 
2001
2002
  // We need to start the buffer management code near the end because it will
2002
2003
  // set the initial buffering state and that depends on other components
@@ -2761,17 +2762,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2761
2762
  * Create the observers for MSE playback. These observers are responsible for
2762
2763
  * notifying the app and player of specific events during MSE playback.
2763
2764
  *
2765
+ * @param {number} startTimeOfLoad
2764
2766
  * @return {!shaka.media.PlayheadObserverManager}
2765
2767
  * @private
2766
2768
  */
2767
- createPlayheadObserversForMSE_() {
2769
+ createPlayheadObserversForMSE_(startTimeOfLoad) {
2768
2770
  goog.asserts.assert(this.manifest_, 'Must have manifest');
2769
2771
  goog.asserts.assert(this.regionTimeline_, 'Must have region timeline');
2770
2772
  goog.asserts.assert(this.video_, 'Must have video element');
2771
2773
 
2774
+ const startsPastZero = this.isLive() || startTimeOfLoad > 0;
2775
+
2772
2776
  // Create the region observer. This will allow us to notify the app when we
2773
2777
  // move in and out of timeline regions.
2774
- const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
2778
+ const regionObserver = new shaka.media.RegionObserver(
2779
+ this.regionTimeline_, startsPastZero);
2775
2780
 
2776
2781
  regionObserver.addEventListener('enter', (event) => {
2777
2782
  /** @type {shaka.extern.TimelineRegionInfo} */
@@ -6630,7 +6635,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6630
6635
  * @define {string} A version number taken from git at compile time.
6631
6636
  * @export
6632
6637
  */
6633
- shaka.Player.version = 'v4.2.11-uncompiled';
6638
+ shaka.Player.version = 'v4.2.12-uncompiled';
6634
6639
 
6635
6640
  // Initialize the deprecation system using the version string we just set
6636
6641
  // on the player.
@@ -86,6 +86,13 @@ class extends shaka.util.FakeEventTarget {
86
86
  return this.reason_;
87
87
  }
88
88
 
89
+ /** @return {void} */
90
+ throwIfAborted() {
91
+ if (this.aborted_) {
92
+ throw this.reason_;
93
+ }
94
+ }
95
+
89
96
  /**
90
97
  * @param {*} reason
91
98
  * @private
@@ -785,7 +785,8 @@ shaka.text.VttTextParser = class {
785
785
  } else if ((results = /^size:([\d.]+)%$/.exec(word))) {
786
786
  cue.size = Number(results[1]);
787
787
  } else if ((results =
788
- /^position:([\d.]+)%(?:,(line-left|line-right|center|start|end))?$/
788
+ // eslint-disable-next-line max-len
789
+ /^position:([\d.]+)%(?:,(line-left|line-right|middle|center|start|end))?$/
789
790
  .exec(word))) {
790
791
  cue.position = Number(results[1]);
791
792
  if (results[2]) {
@@ -168,7 +168,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
168
168
  /^hvc/,
169
169
  /^vp0?[89]/,
170
170
  /^av01/,
171
- /^dvhe/,
171
+ /^dvh/,
172
172
  ];
173
173
 
174
174
 
@@ -112,10 +112,11 @@ shaka.util.Mp4BoxParsers = class {
112
112
  static parseTRUN(reader, version, flags) {
113
113
  const sampleCount = reader.readUint32();
114
114
  const sampleData = [];
115
+ let dataOffset = null;
115
116
 
116
- // Skip "data_offset" if present.
117
+ // "data_offset"
117
118
  if (flags & 0x000001) {
118
- reader.skip(4);
119
+ dataOffset = reader.readUint32();
119
120
  }
120
121
 
121
122
  // Skip "first_sample_flags" if present.
@@ -159,6 +160,7 @@ shaka.util.Mp4BoxParsers = class {
159
160
  return {
160
161
  sampleCount,
161
162
  sampleData,
163
+ dataOffset,
162
164
  };
163
165
  }
164
166
 
@@ -252,13 +254,16 @@ shaka.util.ParsedTREXBox;
252
254
  /**
253
255
  * @typedef {{
254
256
  * sampleCount: number,
255
- * sampleData: !Array.<shaka.util.ParsedTRUNSample>
257
+ * sampleData: !Array.<shaka.util.ParsedTRUNSample>,
258
+ * dataOffset: ?number
256
259
  * }}
257
260
  *
258
261
  * @property {number} sampleCount
259
262
  * As per the spec: the number of samples being added in this run;
260
263
  * @property {!Array.<shaka.util.ParsedTRUNSample>} sampleData
261
264
  * An array of size <sampleCount> containing data for each sample
265
+ * @property {?number} dataOffset
266
+ * If specified via flags, this indicate the offset of the sample in bytes.
262
267
  *
263
268
  * @exportDoc
264
269
  */