shaka-player 4.7.12 → 4.7.14

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 (46) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/dist/deps.js +3 -3
  4. package/dist/locales.js +34 -34
  5. package/dist/shaka-player.compiled.d.ts +3 -2
  6. package/dist/shaka-player.compiled.debug.d.ts +3 -2
  7. package/dist/shaka-player.compiled.debug.externs.js +4 -0
  8. package/dist/shaka-player.compiled.debug.js +265 -257
  9. package/dist/shaka-player.compiled.debug.map +3 -3
  10. package/dist/shaka-player.compiled.externs.js +4 -0
  11. package/dist/shaka-player.compiled.js +1150 -1140
  12. package/dist/shaka-player.compiled.map +4 -4
  13. package/dist/shaka-player.ui.d.ts +3 -2
  14. package/dist/shaka-player.ui.debug.d.ts +3 -2
  15. package/dist/shaka-player.ui.debug.externs.js +4 -0
  16. package/dist/shaka-player.ui.debug.js +394 -386
  17. package/dist/shaka-player.ui.debug.map +3 -3
  18. package/dist/shaka-player.ui.externs.js +4 -0
  19. package/dist/shaka-player.ui.js +1306 -1296
  20. package/dist/shaka-player.ui.map +4 -4
  21. package/externs/shaka/manifest.js +11 -1
  22. package/externs/shaka/offline.js +7 -1
  23. package/lib/ads/client_side_ad_manager.js +4 -2
  24. package/lib/dash/dash_parser.js +10 -3
  25. package/lib/dash/segment_base.js +8 -1
  26. package/lib/dash/segment_list.js +24 -18
  27. package/lib/dash/segment_template.js +45 -3
  28. package/lib/hls/hls_parser.js +18 -1
  29. package/lib/media/drm_engine.js +17 -3
  30. package/lib/media/media_source_engine.js +87 -27
  31. package/lib/media/segment_index.js +31 -20
  32. package/lib/media/segment_reference.js +12 -0
  33. package/lib/media/streaming_engine.js +10 -28
  34. package/lib/mss/mss_parser.js +4 -0
  35. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  36. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  37. package/lib/offline/manifest_converter.js +10 -3
  38. package/lib/offline/storage.js +2 -0
  39. package/lib/player.js +57 -17
  40. package/lib/text/ui_text_displayer.js +10 -1
  41. package/lib/util/mime_utils.js +10 -5
  42. package/lib/util/periods.js +5 -37
  43. package/lib/util/platform.js +31 -9
  44. package/lib/util/stream_utils.js +340 -273
  45. package/package.json +1 -1
  46. package/ui/resolution_selection.js +1 -0
@@ -400,7 +400,8 @@ shaka.extern.FetchCryptoKeysFunction;
400
400
  * undefined),
401
401
  * mssPrivateData: (shaka.extern.MssPrivateData|undefined),
402
402
  * external: boolean,
403
- * fastSwitching: boolean
403
+ * fastSwitching: boolean,
404
+ * fullMimeTypes: !Set.<string>
404
405
  * }}
405
406
  *
406
407
  * @description
@@ -430,10 +431,14 @@ shaka.extern.FetchCryptoKeysFunction;
430
431
  * @property {string} mimeType
431
432
  * <i>Required.</i> <br>
432
433
  * The Stream's MIME type, e.g., 'audio/mp4', 'video/webm', or 'text/vtt'.
434
+ * In the case of a stream that adapts between different periods with
435
+ * different MIME types, this represents only the first period.
433
436
  * @property {string} codecs
434
437
  * <i>Defaults to '' (i.e., unknown / not needed).</i> <br>
435
438
  * The Stream's codecs, e.g., 'avc1.4d4015' or 'vp9', which must be
436
439
  * compatible with the Stream's MIME type. <br>
440
+ * In the case of a stream that adapts between different periods with
441
+ * different codecs, this represents only the first period.
437
442
  * See {@link https://tools.ietf.org/html/rfc6381}
438
443
  * @property {(number|undefined)} frameRate
439
444
  * <i>Video streams only.</i> <br>
@@ -535,6 +540,11 @@ shaka.extern.FetchCryptoKeysFunction;
535
540
  * Eg: external text tracks.
536
541
  * @property {boolean} fastSwitching
537
542
  * Indicate if the stream should be used for fast switching.
543
+ * @property {!Set.<string>} fullMimeTypes
544
+ * A set of full MIME types (e.g. MIME types plus codecs information), that
545
+ * represents the types used in each period of the original manifest.
546
+ * Meant for being used by compatibility checking, such as with
547
+ * MediaSource.isTypeSupported.
538
548
  *
539
549
  * @exportDoc
540
550
  */
@@ -230,7 +230,9 @@ shaka.extern.StreamDB;
230
230
  * tilesLayout: ?string,
231
231
  * pendingSegmentRefId: (string|undefined),
232
232
  * pendingInitSegmentRefId: (string|undefined),
233
- * dataKey: number
233
+ * dataKey: number,
234
+ * mimeType: ?string,
235
+ * codecs: ?string
234
236
  * }}
235
237
  *
236
238
  * @property {?number} initSegmentKey
@@ -263,6 +265,10 @@ shaka.extern.StreamDB;
263
265
  * downloaded.
264
266
  * @property {number} dataKey
265
267
  * The key to the data in storage.
268
+ * @property {?string} mimeType
269
+ * The mimeType of the segment.
270
+ * @property {?string} codecs
271
+ * The codecs of the segment.
266
272
  */
267
273
  shaka.extern.SegmentDB;
268
274
 
@@ -533,7 +533,9 @@ shaka.ads.ClientSideAdManager = class {
533
533
  shaka.ads.AdManager.AD_STARTED, data));
534
534
  if (this.ad_.isLinear()) {
535
535
  this.adContainer_.setAttribute('ad-active', 'true');
536
- this.video_.pause();
536
+ if (!this.config_.customPlayheadTracker) {
537
+ this.video_.pause();
538
+ }
537
539
  if (this.video_.muted) {
538
540
  this.ad_.setInitialMuted(this.video_.volume);
539
541
  } else {
@@ -551,7 +553,7 @@ shaka.ads.ClientSideAdManager = class {
551
553
  (new Map()).set('originalEvent', e)));
552
554
  if (this.ad_ && this.ad_.isLinear()) {
553
555
  this.adContainer_.removeAttribute('ad-active');
554
- if (!this.video_.ended) {
556
+ if (!this.config_.customPlayheadTracker && !this.video_.ended) {
555
557
  this.video_.play();
556
558
  }
557
559
  }
@@ -1485,8 +1485,13 @@ shaka.dash.DashParser = class {
1485
1485
  const getBaseUris = context.representation.getBaseUris;
1486
1486
  streamInfo = {
1487
1487
  generateSegmentIndex: () => {
1488
- return Promise.resolve(shaka.media.SegmentIndex.forSingleSegment(
1489
- periodStart, duration, getBaseUris()));
1488
+ const segmentIndex = shaka.media.SegmentIndex.forSingleSegment(
1489
+ periodStart, duration, getBaseUris());
1490
+ segmentIndex.forEachTopLevelReference((ref) => {
1491
+ ref.mimeType = context.representation.mimeType;
1492
+ ref.codecs = context.representation.codecs;
1493
+ });
1494
+ return Promise.resolve(segmentIndex);
1490
1495
  },
1491
1496
  };
1492
1497
  }
@@ -1585,7 +1590,7 @@ shaka.dash.DashParser = class {
1585
1590
  },
1586
1591
  segmentIndex: null,
1587
1592
  mimeType: context.representation.mimeType,
1588
- codecs: context.representation.codecs,
1593
+ codecs,
1589
1594
  frameRate: context.representation.frameRate,
1590
1595
  pixelAspectRatio: context.representation.pixelAspectRatio,
1591
1596
  bandwidth: context.bandwidth,
@@ -1615,6 +1620,8 @@ shaka.dash.DashParser = class {
1615
1620
  accessibilityPurpose,
1616
1621
  external: false,
1617
1622
  fastSwitching: false,
1623
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
1624
+ context.representation.mimeType, context.representation.codecs)]),
1618
1625
  };
1619
1626
  }
1620
1627
 
@@ -62,7 +62,7 @@ shaka.dash.SegmentBase = class {
62
62
 
63
63
  const getUris = () => resolvedUris;
64
64
  const qualityInfo = shaka.dash.SegmentBase.createQualityInfo(context);
65
- return new shaka.media.InitSegmentReference(
65
+ const ref = new shaka.media.InitSegmentReference(
66
66
  getUris,
67
67
  startByte,
68
68
  endByte,
@@ -70,6 +70,9 @@ shaka.dash.SegmentBase = class {
70
70
  /* timescale= */ null,
71
71
  /* segmentData= */ null,
72
72
  aes128Key);
73
+ ref.codecs = context.representation.codecs;
74
+ ref.mimeType = context.representation.mimeType;
75
+ return ref;
73
76
  }
74
77
 
75
78
  /**
@@ -184,6 +187,10 @@ shaka.dash.SegmentBase = class {
184
187
  indexData, initData, uris, initSegmentReference, timestampOffset,
185
188
  appendWindowStart, appendWindowEnd);
186
189
  }
190
+ for (const ref of references) {
191
+ ref.codecs = context.representation.codecs;
192
+ ref.mimeType = context.representation.mimeType;
193
+ }
187
194
 
188
195
  presentationTimeline.notifySegments(references);
189
196
 
@@ -60,7 +60,8 @@ shaka.dash.SegmentList = class {
60
60
  const references = SegmentList.createSegmentReferences_(
61
61
  context.periodInfo.start, context.periodInfo.duration,
62
62
  info.startNumber, context.representation.getBaseUris, info,
63
- initSegmentReference, aes128Key);
63
+ initSegmentReference, aes128Key, context.representation.mimeType,
64
+ context.representation.codecs);
64
65
 
65
66
  const isNew = !segmentIndex;
66
67
  if (segmentIndex) {
@@ -199,12 +200,14 @@ shaka.dash.SegmentList = class {
199
200
  * @param {shaka.dash.SegmentList.SegmentListInfo} info
200
201
  * @param {shaka.media.InitSegmentReference} initSegmentReference
201
202
  * @param {shaka.extern.aes128Key|undefined} aes128Key
203
+ * @param {string} mimeType
204
+ * @param {string} codecs
202
205
  * @return {!Array.<!shaka.media.SegmentReference>}
203
206
  * @private
204
207
  */
205
208
  static createSegmentReferences_(
206
209
  periodStart, periodDuration, startNumber, getBaseUris, info,
207
- initSegmentReference, aes128Key) {
210
+ initSegmentReference, aes128Key, mimeType, codecs) {
208
211
  const ManifestParserUtils = shaka.util.ManifestParserUtils;
209
212
 
210
213
  let max = info.mediaSegments.length;
@@ -251,22 +254,25 @@ shaka.dash.SegmentList = class {
251
254
  }
252
255
  return uris;
253
256
  };
254
- references.push(
255
- new shaka.media.SegmentReference(
256
- periodStart + startTime,
257
- periodStart + endTime,
258
- getUris,
259
- segment.start,
260
- segment.end,
261
- initSegmentReference,
262
- timestampOffset,
263
- appendWindowStart, appendWindowEnd,
264
- /* partialReferences= */ [],
265
- /* tilesLayout= */ '',
266
- /* tileDuration= */ null,
267
- /* syncTime= */ null,
268
- shaka.media.SegmentReference.Status.AVAILABLE,
269
- aes128Key));
257
+
258
+ const ref = new shaka.media.SegmentReference(
259
+ periodStart + startTime,
260
+ periodStart + endTime,
261
+ getUris,
262
+ segment.start,
263
+ segment.end,
264
+ initSegmentReference,
265
+ timestampOffset,
266
+ appendWindowStart, appendWindowEnd,
267
+ /* partialReferences= */ [],
268
+ /* tilesLayout= */ '',
269
+ /* tileDuration= */ null,
270
+ /* syncTime= */ null,
271
+ shaka.media.SegmentReference.Status.AVAILABLE,
272
+ aes128Key);
273
+ ref.codecs = codecs;
274
+ ref.mimeType = mimeType;
275
+ references.push(ref);
270
276
  prevEndTime = endTime;
271
277
  }
272
278
 
@@ -213,6 +213,8 @@ shaka.dash.SegmentTemplate = class {
213
213
  timeline: segmentInfo.timeline,
214
214
  mediaTemplate: media,
215
215
  indexTemplate: index,
216
+ mimeType: context.representation.mimeType,
217
+ codecs: context.representation.codecs,
216
218
  };
217
219
  }
218
220
 
@@ -392,6 +394,11 @@ shaka.dash.SegmentTemplate = class {
392
394
  Math.ceil(availablePeriodTimes[1] / segmentDuration) - 1,
393
395
  ];
394
396
 
397
+ // For Low Latency we can request the partial current position.
398
+ if (context.representation.availabilityTimeOffset) {
399
+ availablePeriodPositions[1]++;
400
+ }
401
+
395
402
  // In absolute positions.
396
403
  const availablePresentationPositions =
397
404
  availablePeriodPositions.map((x) => x + startNumber);
@@ -463,6 +470,8 @@ shaka.dash.SegmentTemplate = class {
463
470
  /* syncTime= */ null,
464
471
  shaka.media.SegmentReference.Status.AVAILABLE,
465
472
  aes128Key);
473
+ ref.codecs = context.representation.codecs;
474
+ ref.mimeType = context.representation.mimeType;
466
475
  // This is necessary information for thumbnail streams:
467
476
  ref.trueEndTime = trueSegmentEnd;
468
477
  return ref;
@@ -552,7 +561,7 @@ shaka.dash.SegmentTemplate = class {
552
561
  return resolvedUris;
553
562
  };
554
563
  const qualityInfo = shaka.dash.SegmentBase.createQualityInfo(context);
555
- return new shaka.media.InitSegmentReference(
564
+ const ref = new shaka.media.InitSegmentReference(
556
565
  getUris,
557
566
  /* startByte= */ 0,
558
567
  /* endByte= */ null,
@@ -560,6 +569,9 @@ shaka.dash.SegmentTemplate = class {
560
569
  /* timescale= */ null,
561
570
  /* segmentData= */ null,
562
571
  aes128Key);
572
+ ref.codecs = context.representation.codecs;
573
+ ref.mimeType = context.representation.mimeType;
574
+ return ref;
563
575
  }
564
576
  };
565
577
 
@@ -689,7 +701,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
689
701
  */
690
702
  appendTemplateInfo(info, periodStart, periodEnd, shouldFit,
691
703
  initSegmentReference) {
692
- this.initSegmentReference_ = initSegmentReference;
704
+ this.updateInitSegmentReference(initSegmentReference);
693
705
  if (!this.templateInfo_) {
694
706
  this.templateInfo_ = info;
695
707
  this.periodStart_ = periodStart;
@@ -720,6 +732,24 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
720
732
  }
721
733
  }
722
734
 
735
+ /**
736
+ * Updates the init segment reference and propagates the update to all
737
+ * references.
738
+ * @param {shaka.media.InitSegmentReference} initSegmentReference
739
+ */
740
+ updateInitSegmentReference(initSegmentReference) {
741
+ if (this.initSegmentReference_ === initSegmentReference) {
742
+ return;
743
+ }
744
+
745
+ this.initSegmentReference_ = initSegmentReference;
746
+ for (const reference of this.references) {
747
+ if (reference) {
748
+ reference.updateInitSegmentReference(initSegmentReference);
749
+ }
750
+ }
751
+ }
752
+
723
753
  /**
724
754
  *
725
755
  * @param {number} time
@@ -835,6 +865,8 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
835
865
  this.periodEnd_ !== Infinity) {
836
866
  segmentEnd = this.periodEnd_;
837
867
  }
868
+ const codecs = this.templateInfo_.codecs;
869
+ const mimeType = this.templateInfo_.mimeType;
838
870
 
839
871
  const partialSegmentRefs = [];
840
872
 
@@ -877,6 +909,8 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
877
909
  /* syncTime= */ null,
878
910
  shaka.media.SegmentReference.Status.AVAILABLE,
879
911
  this.aes128Key_);
912
+ partial.codecs = codecs;
913
+ partial.mimeType = mimeType;
880
914
  if (this.segmentSequenceCadence_ == 0) {
881
915
  if (i > 0) {
882
916
  partial.markAsNonIndependent();
@@ -920,6 +954,8 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
920
954
  shaka.media.SegmentReference.Status.AVAILABLE,
921
955
  this.aes128Key_,
922
956
  /* allPartialSegments= */ range.partialSegments > 0);
957
+ ref.codecs = codecs;
958
+ ref.mimeType = mimeType;
923
959
  ref.trueEndTime = trueSegmentEnd;
924
960
  this.references[correctedPosition] = ref;
925
961
  }
@@ -955,7 +991,9 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
955
991
  * unscaledPresentationTimeOffset: number,
956
992
  * timeline: Array.<shaka.media.PresentationTimeline.TimeRange>,
957
993
  * mediaTemplate: ?string,
958
- * indexTemplate: ?string
994
+ * indexTemplate: ?string,
995
+ * mimeType: string,
996
+ * codecs: string
959
997
  * }}
960
998
  *
961
999
  * @description
@@ -977,5 +1015,9 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
977
1015
  * The media URI template, if given.
978
1016
  * @property {?string} indexTemplate
979
1017
  * The index URI template, if given.
1018
+ * @property {string} mimeType
1019
+ * The mimeType.
1020
+ * @property {string} codecs
1021
+ * The codecs.
980
1022
  */
981
1023
  shaka.dash.SegmentTemplate.SegmentTemplateInfo;
@@ -1224,6 +1224,7 @@ shaka.hls.HlsParser = class {
1224
1224
  textStreamInfo.stream.mimeType =
1225
1225
  this.guessMimeTypeBeforeLoading_(type, codecs) ||
1226
1226
  this.guessMimeTypeFallback_(type);
1227
+ this.setFullTypeForStream_(textStreamInfo.stream);
1227
1228
  }
1228
1229
  }
1229
1230
  }
@@ -1233,6 +1234,15 @@ shaka.hls.HlsParser = class {
1233
1234
  return textStreams.filter((s) => s);
1234
1235
  }
1235
1236
 
1237
+ /**
1238
+ * @param {!shaka.extern.Stream} stream
1239
+ * @private
1240
+ */
1241
+ setFullTypeForStream_(stream) {
1242
+ stream.fullMimeTypes = new Set([shaka.util.MimeUtils.getFullType(
1243
+ stream.mimeType, stream.codecs)]);
1244
+ }
1245
+
1236
1246
  /**
1237
1247
  * @param {!Array.<!shaka.hls.Tag>} imageTags from the playlist.
1238
1248
  * @param {!Array.<!shaka.hls.Tag>} iFrameTags from the playlist.
@@ -2266,6 +2276,7 @@ shaka.hls.HlsParser = class {
2266
2276
  stream.mimeType = realStream.mimeType;
2267
2277
  stream.bandwidth = stream.bandwidth || realStream.bandwidth;
2268
2278
  stream.codecs = stream.codecs || realStream.codecs;
2279
+ this.setFullTypeForStream_(stream);
2269
2280
 
2270
2281
  // Since we lazy-loaded this content, the player may need to create new
2271
2282
  // sessions for the DRM info in this stream.
@@ -2280,6 +2291,7 @@ shaka.hls.HlsParser = class {
2280
2291
  // To aid manifest filtering, assume before loading that all video
2281
2292
  // renditions have the same MIME type. (And likewise for audio.)
2282
2293
  otherStreamInfo.stream.mimeType = realStream.mimeType;
2294
+ this.setFullTypeForStream_(otherStreamInfo.stream);
2283
2295
  }
2284
2296
  }
2285
2297
  }
@@ -2288,6 +2300,7 @@ shaka.hls.HlsParser = class {
2288
2300
  const firstSegment = realStream.segmentIndex.get(0);
2289
2301
  if (firstSegment && firstSegment.initSegmentReference) {
2290
2302
  stream.mimeType = 'application/mp4';
2303
+ this.setFullTypeForStream_(stream);
2291
2304
  }
2292
2305
  }
2293
2306
 
@@ -2537,6 +2550,7 @@ shaka.hls.HlsParser = class {
2537
2550
  stream.drmInfos = drmInfos;
2538
2551
  stream.keyIds = keyIds;
2539
2552
  stream.mimeType = mimeType;
2553
+ this.setFullTypeForStream_(stream);
2540
2554
 
2541
2555
  const mediaSequenceToStartTime = this.isLive_() ?
2542
2556
  this.mediaSequenceToStartTimeByType_.get(type) : new Map();
@@ -2692,7 +2706,7 @@ shaka.hls.HlsParser = class {
2692
2706
  roles.push(shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE);
2693
2707
  }
2694
2708
 
2695
- return {
2709
+ const stream = {
2696
2710
  id: this.globalId_++,
2697
2711
  originalId: name,
2698
2712
  groupId: null,
@@ -2730,7 +2744,10 @@ shaka.hls.HlsParser = class {
2730
2744
  accessibilityPurpose: accessibilityPurpose,
2731
2745
  external: false,
2732
2746
  fastSwitching: false,
2747
+ fullMimeTypes: new Set(),
2733
2748
  };
2749
+ this.setFullTypeForStream_(stream);
2750
+ return stream;
2734
2751
  }
2735
2752
 
2736
2753
  /**
@@ -1551,6 +1551,9 @@ shaka.media.DrmEngine = class {
1551
1551
  const req = this.playerInterface_.netEngine.request(requestType, request);
1552
1552
  response = await req.promise;
1553
1553
  } catch (error) {
1554
+ if (this.destroyer_.destroyed()) {
1555
+ return;
1556
+ }
1554
1557
  // Request failed!
1555
1558
  goog.asserts.assert(error instanceof shaka.util.Error,
1556
1559
  'Wrong NetworkingEngine error type!');
@@ -1559,9 +1562,20 @@ shaka.media.DrmEngine = class {
1559
1562
  shaka.util.Error.Category.DRM,
1560
1563
  shaka.util.Error.Code.LICENSE_REQUEST_FAILED,
1561
1564
  error);
1562
- this.onError_(shakaErr);
1563
- if (metadata && metadata.updatePromise) {
1564
- metadata.updatePromise.reject(shakaErr);
1565
+ if (this.activeSessions_.size == 1) {
1566
+ this.onError_(shakaErr);
1567
+ if (metadata && metadata.updatePromise) {
1568
+ metadata.updatePromise.reject(shakaErr);
1569
+ }
1570
+ } else {
1571
+ if (metadata && metadata.updatePromise) {
1572
+ metadata.updatePromise.reject(shakaErr);
1573
+ }
1574
+ this.activeSessions_.delete(session);
1575
+ if (this.areAllSessionsLoaded_()) {
1576
+ this.allSessionsLoaded_.resolve();
1577
+ this.keyStatusTimer_.tickAfter(/* seconds= */ 0.1);
1578
+ }
1565
1579
  }
1566
1580
  return;
1567
1581
  }
@@ -31,6 +31,7 @@ goog.require('shaka.util.Mp4BoxParsers');
31
31
  goog.require('shaka.util.Mp4Parser');
32
32
  goog.require('shaka.util.Platform');
33
33
  goog.require('shaka.util.PublicPromise');
34
+ goog.require('shaka.util.StreamUtils');
34
35
  goog.require('shaka.util.TsParser');
35
36
  goog.require('shaka.lcevc.Dec');
36
37
 
@@ -237,18 +238,61 @@ shaka.media.MediaSourceEngine = class {
237
238
  * Checks if a certain type is supported.
238
239
  *
239
240
  * @param {shaka.extern.Stream} stream
240
- * @return {boolean}
241
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
242
+ * @return {!Promise.<boolean>}
241
243
  */
242
- static isStreamSupported(stream) {
244
+ static async isStreamSupported(stream, contentType) {
245
+ if (stream.createSegmentIndex) {
246
+ await stream.createSegmentIndex();
247
+ }
248
+ if (!stream.segmentIndex) {
249
+ return false;
250
+ }
251
+ if (stream.segmentIndex.isEmpty()) {
252
+ return true;
253
+ }
243
254
  const MimeUtils = shaka.util.MimeUtils;
244
- const fullMimeType = MimeUtils.getFullType(stream.mimeType, stream.codecs);
245
- const extendedMimeType = MimeUtils.getExtendedType(stream);
246
- const fullMimeTypeWithAllCodecs = MimeUtils.getFullTypeWithAllCodecs(
247
- stream.mimeType, stream.codecs);
248
255
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
249
- return shaka.text.TextEngine.isTypeSupported(fullMimeType) ||
250
- shaka.media.Capabilities.isTypeSupported(extendedMimeType) ||
251
- TransmuxerEngine.isSupported(fullMimeTypeWithAllCodecs, stream.type);
256
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
257
+ const StreamUtils = shaka.util.StreamUtils;
258
+ const seenCombos = new Set();
259
+ // Check each combination of mimeType and codecs within the segment index.
260
+ // Unfortunately we cannot use fullMimeTypes, as we ALSO need to check the
261
+ // getFullTypeWithAllCodecs (for the sake of the transmuxer) and we have no
262
+ // way of going from a full mimeType to a full mimeType with all codecs.
263
+ // As this function is only called in debug mode, a little inefficiency is
264
+ // acceptable.
265
+ for (const ref of stream.segmentIndex) {
266
+ const mimeType = ref.mimeType || stream.mimeType || '';
267
+ let codecs = ref.codecs || stream.codecs || '';
268
+ // Don't check the same combination of mimetype + codecs twice.
269
+ const combo = mimeType + ':' + codecs;
270
+ if (seenCombos.has(combo)) {
271
+ continue;
272
+ }
273
+ seenCombos.add(combo);
274
+ if (contentType == ContentType.TEXT) {
275
+ const fullMimeType = MimeUtils.getFullType(mimeType, codecs);
276
+ if (!shaka.text.TextEngine.isTypeSupported(fullMimeType)) {
277
+ return false;
278
+ }
279
+ } else {
280
+ if (contentType == ContentType.VIDEO) {
281
+ codecs = StreamUtils.getCorrectVideoCodecs(codecs);
282
+ } else if (contentType == ContentType.AUDIO) {
283
+ codecs = StreamUtils.getCorrectAudioCodecs(codecs);
284
+ }
285
+ const extendedMimeType = MimeUtils.getExtendedType(
286
+ stream, mimeType, codecs);
287
+ const fullMimeType = MimeUtils.getFullTypeWithAllCodecs(
288
+ mimeType, codecs);
289
+ if (!shaka.media.Capabilities.isTypeSupported(extendedMimeType) &&
290
+ !TransmuxerEngine.isSupported(fullMimeType, stream.type)) {
291
+ return false;
292
+ }
293
+ }
294
+ }
295
+ return true;
252
296
  }
253
297
 
254
298
  /**
@@ -451,7 +495,8 @@ shaka.media.MediaSourceEngine = class {
451
495
 
452
496
  for (const contentType of streamsByType.keys()) {
453
497
  const stream = streamsByType.get(contentType);
454
- this.initSourceBuffer_(contentType, stream, stream.codecs);
498
+ // eslint-disable-next-line no-await-in-loop
499
+ await this.initSourceBuffer_(contentType, stream, stream.codecs);
455
500
  if (this.needSplitMuxedContent_) {
456
501
  this.queues_[ContentType.AUDIO] = [];
457
502
  this.queues_[ContentType.VIDEO] = [];
@@ -467,13 +512,15 @@ shaka.media.MediaSourceEngine = class {
467
512
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
468
513
  * @param {shaka.extern.Stream} stream
469
514
  * @param {string} codecs
515
+ * @return {!Promise}
470
516
  * @private
471
517
  */
472
- initSourceBuffer_(contentType, stream, codecs) {
518
+ async initSourceBuffer_(contentType, stream, codecs) {
473
519
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
474
520
 
475
521
  goog.asserts.assert(
476
- shaka.media.MediaSourceEngine.isStreamSupported(stream),
522
+ await shaka.media.MediaSourceEngine.isStreamSupported(
523
+ stream, contentType),
477
524
  'Type negotiation should happen before MediaSourceEngine.init!');
478
525
 
479
526
  let mimeType = shaka.util.MimeUtils.getFullType(
@@ -490,7 +537,6 @@ shaka.media.MediaSourceEngine = class {
490
537
  const mimeTypeWithAllCodecs =
491
538
  shaka.util.MimeUtils.getFullTypeWithAllCodecs(
492
539
  stream.mimeType, codecs);
493
- const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
494
540
  if (needTransmux) {
495
541
  const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
496
542
  ContentType.AUDIO, (codecs || '').split(','));
@@ -498,12 +544,12 @@ shaka.media.MediaSourceEngine = class {
498
544
  ContentType.VIDEO, (codecs || '').split(','));
499
545
  if (audioCodec && videoCodec) {
500
546
  this.needSplitMuxedContent_ = true;
501
- this.initSourceBuffer_(ContentType.AUDIO, stream, audioCodec);
502
- this.initSourceBuffer_(ContentType.VIDEO, stream, videoCodec);
547
+ await this.initSourceBuffer_(ContentType.AUDIO, stream, audioCodec);
548
+ await this.initSourceBuffer_(ContentType.VIDEO, stream, videoCodec);
503
549
  return;
504
550
  }
505
- const transmuxerPlugin =
506
- TransmuxerEngine.findTransmuxer(mimeTypeWithAllCodecs);
551
+ const transmuxerPlugin = shaka.transmuxer.TransmuxerEngine
552
+ .findTransmuxer(mimeTypeWithAllCodecs);
507
553
  if (transmuxerPlugin) {
508
554
  const transmuxer = transmuxerPlugin();
509
555
  this.transmuxers_[contentType] = transmuxer;
@@ -1793,16 +1839,22 @@ shaka.media.MediaSourceEngine = class {
1793
1839
 
1794
1840
  const totalOfBuffers = streamsByType.size;
1795
1841
  let numberOfSourceBufferAdded = 0;
1796
- this.eventManager_.listen(sourceBuffers, 'addsourcebuffer', (event) => {
1842
+ const onSourceBufferAdded = () => {
1797
1843
  numberOfSourceBufferAdded++;
1798
1844
  if (numberOfSourceBufferAdded === totalOfBuffers) {
1799
1845
  sourceBufferAdded.resolve();
1846
+ this.eventManager_.unlisten(sourceBuffers, 'addsourcebuffer',
1847
+ onSourceBufferAdded);
1800
1848
  }
1801
- });
1849
+ };
1850
+
1851
+ this.eventManager_.listen(sourceBuffers, 'addsourcebuffer',
1852
+ onSourceBufferAdded);
1802
1853
 
1803
1854
  for (const contentType of streamsByType.keys()) {
1804
1855
  const stream = streamsByType.get(contentType);
1805
- this.initSourceBuffer_(contentType, stream, stream.codecs);
1856
+ // eslint-disable-next-line no-await-in-loop
1857
+ await this.initSourceBuffer_(contentType, stream, stream.codecs);
1806
1858
  if (this.needSplitMuxedContent_) {
1807
1859
  this.queues_[ContentType.AUDIO] = [];
1808
1860
  this.queues_[ContentType.VIDEO] = [];
@@ -1821,7 +1873,12 @@ shaka.media.MediaSourceEngine = class {
1821
1873
  this.destroyer_.ensureNotDestroyed();
1822
1874
 
1823
1875
  this.eventManager_.listenOnce(this.video_, 'canplaythrough', () => {
1824
- this.destroyer_.ensureNotDestroyed();
1876
+ // Don't use ensureNotDestroyed() from this event listener, because
1877
+ // that results in an uncaught exception. Instead, just check the
1878
+ // flag.
1879
+ if (this.destroyer_.destroyed()) {
1880
+ return;
1881
+ }
1825
1882
 
1826
1883
  this.video_.autoplay = previousAutoPlayState;
1827
1884
  if (!previousPausedState) {
@@ -1935,10 +1992,12 @@ shaka.media.MediaSourceEngine = class {
1935
1992
  *
1936
1993
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1937
1994
  * @param {shaka.extern.Stream} stream
1995
+ * @param {string} refMimeType
1996
+ * @param {string} refCodecs
1938
1997
  * @return {boolean}
1939
1998
  * @private
1940
1999
  */
1941
- isCodecSwitchNecessary_(contentType, stream) {
2000
+ isCodecSwitchNecessary_(contentType, stream, refMimeType, refCodecs) {
1942
2001
  if (contentType == shaka.util.ManifestParserUtils.ContentType.TEXT) {
1943
2002
  return false;
1944
2003
  }
@@ -1948,8 +2007,7 @@ shaka.media.MediaSourceEngine = class {
1948
2007
  const currentBasicType = MimeUtils.getBasicType(
1949
2008
  this.sourceBufferTypes_[contentType]);
1950
2009
 
1951
- let newMimeType = shaka.util.MimeUtils.getFullType(
1952
- stream.mimeType, stream.codecs);
2010
+ let newMimeType = shaka.util.MimeUtils.getFullType(refMimeType, refCodecs);
1953
2011
  let needTransmux = this.config_.forceTransmux;
1954
2012
  if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
1955
2013
  (!this.sequenceMode_ &&
@@ -1958,7 +2016,7 @@ shaka.media.MediaSourceEngine = class {
1958
2016
  }
1959
2017
  const newMimeTypeWithAllCodecs =
1960
2018
  shaka.util.MimeUtils.getFullTypeWithAllCodecs(
1961
- stream.mimeType, stream.codecs);
2019
+ refMimeType, refCodecs);
1962
2020
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
1963
2021
  if (needTransmux) {
1964
2022
  const transmuxerPlugin =
@@ -1984,10 +2042,12 @@ shaka.media.MediaSourceEngine = class {
1984
2042
  *
1985
2043
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1986
2044
  * @param {shaka.extern.Stream} stream
2045
+ * @param {string} mimeType
2046
+ * @param {string} codecs
1987
2047
  * @return {boolean}
1988
2048
  */
1989
- isResetMediaSourceNecessary(contentType, stream) {
1990
- if (!this.isCodecSwitchNecessary_(contentType, stream)) {
2049
+ isResetMediaSourceNecessary(contentType, stream, mimeType, codecs) {
2050
+ if (!this.isCodecSwitchNecessary_(contentType, stream, mimeType, codecs)) {
1991
2051
  return false;
1992
2052
  }
1993
2053