shaka-player 4.9.32 → 4.9.34

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 (56) hide show
  1. package/dist/deps.js +6 -5
  2. package/dist/locales.js +36 -36
  3. package/dist/shaka-player.compiled.d.ts +11 -3
  4. package/dist/shaka-player.compiled.debug.d.ts +11 -3
  5. package/dist/shaka-player.compiled.debug.externs.js +8 -5
  6. package/dist/shaka-player.compiled.debug.js +453 -440
  7. package/dist/shaka-player.compiled.debug.map +4 -4
  8. package/dist/shaka-player.compiled.externs.js +8 -5
  9. package/dist/shaka-player.compiled.js +1281 -1271
  10. package/dist/shaka-player.compiled.map +4 -4
  11. package/dist/shaka-player.ui.d.ts +11 -3
  12. package/dist/shaka-player.ui.debug.d.ts +11 -3
  13. package/dist/shaka-player.ui.debug.externs.js +8 -5
  14. package/dist/shaka-player.ui.debug.js +483 -470
  15. package/dist/shaka-player.ui.debug.map +4 -4
  16. package/dist/shaka-player.ui.externs.js +8 -5
  17. package/dist/shaka-player.ui.js +1478 -1468
  18. package/dist/shaka-player.ui.map +4 -4
  19. package/externs/managedmediasource.js +6 -0
  20. package/externs/shaka/codecs.js +21 -0
  21. package/externs/shaka/offline.js +4 -0
  22. package/lib/cast/cast_utils.js +1 -0
  23. package/lib/hls/hls_parser.js +26 -5
  24. package/lib/media/drm_engine.js +78 -18
  25. package/lib/media/media_source_capabilities.js +3 -2
  26. package/lib/media/media_source_engine.js +117 -36
  27. package/lib/media/segment_utils.js +57 -47
  28. package/lib/offline/download_info.js +5 -0
  29. package/lib/offline/download_manager.js +54 -0
  30. package/lib/offline/manifest_converter.js +1 -0
  31. package/lib/offline/storage.js +11 -2
  32. package/lib/player.js +89 -53
  33. package/lib/polyfill/media_capabilities.js +1 -1
  34. package/lib/text/simple_text_displayer.js +9 -1
  35. package/lib/text/ui_text_displayer.js +2 -14
  36. package/lib/text/vtt_text_parser.js +9 -4
  37. package/lib/transmuxer/aac_transmuxer.js +11 -3
  38. package/lib/transmuxer/ac3_transmuxer.js +11 -3
  39. package/lib/transmuxer/ec3_transmuxer.js +11 -3
  40. package/lib/transmuxer/h264.js +143 -70
  41. package/lib/transmuxer/mp3_transmuxer.js +11 -3
  42. package/lib/transmuxer/ts_transmuxer.js +68 -28
  43. package/lib/util/abortable_operation.js +3 -0
  44. package/lib/util/drm_utils.js +20 -0
  45. package/lib/util/functional.js +27 -0
  46. package/lib/util/mime_utils.js +2 -0
  47. package/lib/util/number_utils.js +37 -0
  48. package/lib/util/object_utils.js +83 -0
  49. package/lib/util/platform.js +48 -40
  50. package/lib/util/stream_utils.js +27 -106
  51. package/lib/util/ts_parser.js +111 -85
  52. package/package.json +2 -2
  53. package/ui/controls.js +4 -0
  54. package/ui/fast_forward_button.js +7 -0
  55. package/ui/play_button.js +1 -1
  56. package/ui/rewind_button.js +7 -0
@@ -22,3 +22,9 @@ function ManagedMediaSource() {}
22
22
  * @return {boolean}
23
23
  */
24
24
  ManagedMediaSource.isTypeSupported = function(type) {};
25
+
26
+ /**
27
+ * @constructor
28
+ * @extends {SourceBuffer}
29
+ */
30
+ function ManagedSourceBuffer() {}
@@ -27,6 +27,27 @@
27
27
  shaka.extern.MPEG_PES;
28
28
 
29
29
 
30
+ /**
31
+ * @typedef {{
32
+ * data: !Uint8Array,
33
+ * frame: boolean,
34
+ * isKeyframe: boolean,
35
+ * pts: ?number,
36
+ * dts: ?number,
37
+ * nalus: !Array.<!shaka.extern.VideoNalu>
38
+ * }}
39
+ *
40
+ * @summary VideoSample.
41
+ * @property {!Uint8Array} data
42
+ * @property {boolean} frame
43
+ * @property {boolean} isKeyframe
44
+ * @property {?number} pts
45
+ * @property {?number} dts
46
+ * @property {!Array.<!shaka.extern.VideoNalu>} nalus
47
+ */
48
+ shaka.extern.VideoSample;
49
+
50
+
30
51
  /**
31
52
  * @typedef {{
32
53
  * data: !Uint8Array,
@@ -141,6 +141,7 @@ shaka.extern.ManifestDB;
141
141
  * spatialAudio: boolean,
142
142
  * closedCaptions: Map.<string, string>,
143
143
  * tilesLayout: (string|undefined),
144
+ * mssPrivateData: (shaka.extern.MssPrivateData|undefined),
144
145
  * external: boolean,
145
146
  * fastSwitching: boolean
146
147
  * }}
@@ -213,6 +214,9 @@ shaka.extern.ManifestDB;
213
214
  * The value is a grid-item-dimension consisting of two positive decimal
214
215
  * integers in the format: column-x-row ('4x3'). It describes the arrangement
215
216
  * of Images in a Grid. The minimum valid LAYOUT is '1x1'.
217
+ * @property {(shaka.extern.MssPrivateData|undefined)} mssPrivateData
218
+ * <i>Microsoft Smooth Streaming only.</i> <br>
219
+ * Private MSS data that is necessary to be able to do transmuxing.
216
220
  * @property {boolean} external
217
221
  * Indicate if the stream was added externally.
218
222
  * Eg: external text tracks.
@@ -326,6 +326,7 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
326
326
  'seekRange': 1,
327
327
  'getLoadMode': 10,
328
328
  'getManifestType': 10,
329
+ 'isEnded': 1,
329
330
  };
330
331
 
331
332
 
@@ -1012,8 +1012,8 @@ shaka.hls.HlsParser = class {
1012
1012
  }
1013
1013
 
1014
1014
  if (extension == 'ts' || contentMimeType == 'video/mp2t') {
1015
- const basicInfo =
1016
- shaka.media.SegmentUtils.getBasicInfoFromTs(response.data);
1015
+ const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromTs(
1016
+ response.data, this.config_.disableAudio, this.config_.disableVideo);
1017
1017
  if (basicInfo) {
1018
1018
  return basicInfo;
1019
1019
  }
@@ -2582,12 +2582,23 @@ shaka.hls.HlsParser = class {
2582
2582
  }
2583
2583
  }
2584
2584
  this.notifySegmentsForStreams_(streamInfos.map((s) => s.stream));
2585
- const liveWithNoProgramaDateTime =
2585
+
2586
+ const activeStreamInfos = Array.from(this.uriToStreamInfosMap_.values())
2587
+ .filter((s) => s.stream.segmentIndex);
2588
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
2589
+ const hasAudio =
2590
+ activeStreamInfos.some((s) => s.stream.type == ContentType.AUDIO);
2591
+ const hasVideo =
2592
+ activeStreamInfos.some((s) => s.stream.type == ContentType.VIDEO);
2593
+
2594
+ const liveWithNoProgramDateTime =
2586
2595
  this.isLive_() && !this.usesProgramDateTime_;
2596
+ const vodWithOnlyAudioOrVideo = !this.isLive_() &&
2597
+ this.usesProgramDateTime_ && !(hasAudio && hasVideo);
2587
2598
  if (this.config_.hls.ignoreManifestProgramDateTime ||
2588
- liveWithNoProgramaDateTime) {
2599
+ liveWithNoProgramDateTime || vodWithOnlyAudioOrVideo) {
2589
2600
  this.syncStreamsWithSequenceNumber_(
2590
- streamInfos, liveWithNoProgramaDateTime);
2601
+ streamInfos, liveWithNoProgramDateTime);
2591
2602
  } else {
2592
2603
  this.syncStreamsWithProgramDateTime_(streamInfos);
2593
2604
  if (this.config_.hls.ignoreManifestProgramDateTimeForTypes.length > 0) {
@@ -2682,6 +2693,16 @@ shaka.hls.HlsParser = class {
2682
2693
 
2683
2694
  if (!mimeType) {
2684
2695
  mimeType = await this.guessMimeType_(type, codecs, segments);
2696
+
2697
+ // Some manifests don't say what text codec they use, this is a problem
2698
+ // if the cmft extension is used because we identify the mimeType as
2699
+ // application/mp4. In this case if we don't detect initialization
2700
+ // segments, we assume that the mimeType is text/vtt.
2701
+ if (type == shaka.util.ManifestParserUtils.ContentType.TEXT &&
2702
+ !codecs && mimeType == 'application/mp4' &&
2703
+ segments[0] && !segments[0].initSegmentReference) {
2704
+ mimeType = 'text/vtt';
2705
+ }
2685
2706
  }
2686
2707
 
2687
2708
  const {drmInfos, keyIds, encrypted, aesEncrypted} =
@@ -15,6 +15,7 @@ goog.require('shaka.util.DrmUtils');
15
15
  goog.require('shaka.util.Error');
16
16
  goog.require('shaka.util.EventManager');
17
17
  goog.require('shaka.util.FakeEvent');
18
+ goog.require('shaka.util.Functional');
18
19
  goog.require('shaka.util.IDestroyable');
19
20
  goog.require('shaka.util.Iterables');
20
21
  goog.require('shaka.util.ManifestParserUtils');
@@ -1419,6 +1420,11 @@ shaka.media.DrmEngine = class {
1419
1420
  }
1420
1421
  // NOTE: allowCrossSiteCredentials can be set in a request filter.
1421
1422
 
1423
+ if (shaka.util.DrmUtils.isClearKeySystem(
1424
+ this.currentDrmInfo_.keySystem)) {
1425
+ this.fixClearKeyRequest_(request, this.currentDrmInfo_);
1426
+ }
1427
+
1422
1428
  if (shaka.util.DrmUtils.isPlayReadyKeySystem(
1423
1429
  this.currentDrmInfo_.keySystem)) {
1424
1430
  this.unpackPlayReadyRequest_(request);
@@ -1569,6 +1575,31 @@ shaka.media.DrmEngine = class {
1569
1575
  /** @type{string} */(shaka.util.TXml.getTextContents(challenge)));
1570
1576
  }
1571
1577
 
1578
+ /**
1579
+ * Some old ClearKey CDMs don't include the type in the body request.
1580
+ *
1581
+ * @param {shaka.extern.Request} request
1582
+ * @param {shaka.extern.DrmInfo} drmInfo
1583
+ * @private
1584
+ */
1585
+ fixClearKeyRequest_(request, drmInfo) {
1586
+ try {
1587
+ const body = shaka.util.StringUtils.fromBytesAutoDetect(request.body);
1588
+ if (body) {
1589
+ const licenseBody =
1590
+ /** @type {shaka.media.DrmEngine.ClearKeyLicenceRequestFormat} */ (
1591
+ JSON.parse(body));
1592
+ if (!licenseBody.type) {
1593
+ licenseBody.type = drmInfo.sessionType;
1594
+ request.body =
1595
+ shaka.util.StringUtils.toUTF8(JSON.stringify(licenseBody));
1596
+ }
1597
+ }
1598
+ } catch (e) {
1599
+ shaka.log.info('Error unpacking ClearKey license', e);
1600
+ }
1601
+ }
1602
+
1572
1603
  /**
1573
1604
  * @param {!Event} event
1574
1605
  * @private
@@ -1849,9 +1880,16 @@ shaka.media.DrmEngine = class {
1849
1880
  offlineConfig.sessionTypes = ['persistent-license'];
1850
1881
 
1851
1882
  const configs = [offlineConfig, basicConfig];
1852
-
1853
- const access = await navigator.requestMediaKeySystemAccess(
1854
- keySystem, configs);
1883
+ // On some (Android) WebView environments,
1884
+ // requestMediaKeySystemAccess will
1885
+ // not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
1886
+ // is not set. This is a workaround for that issue.
1887
+ const TIMEOUT_FOR_CHECKACCESS_IN_SECONDS = 1;
1888
+ const access =
1889
+ await shaka.util.Functional.promiseWithTimeout(
1890
+ TIMEOUT_FOR_CHECKACCESS_IN_SECONDS,
1891
+ navigator.requestMediaKeySystemAccess(keySystem, configs),
1892
+ );
1855
1893
  await processMediaKeySystemAccess(keySystem, access);
1856
1894
  } catch (error) {} // Ignore errors.
1857
1895
  };
@@ -1886,13 +1924,22 @@ shaka.media.DrmEngine = class {
1886
1924
  },
1887
1925
  },
1888
1926
  };
1889
-
1927
+ // On some (Android) WebView environments, decodingInfo will
1928
+ // not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
1929
+ // is not set. This is a workaround for that issue.
1930
+ const TIMEOUT_FOR_DECODING_INFO_IN_SECONDS = 1;
1890
1931
  const decodingInfo =
1891
- await navigator.mediaCapabilities.decodingInfo(decodingConfig);
1932
+ await shaka.util.Functional.promiseWithTimeout(
1933
+ TIMEOUT_FOR_DECODING_INFO_IN_SECONDS,
1934
+ navigator.mediaCapabilities.decodingInfo(decodingConfig),
1935
+ );
1892
1936
 
1893
1937
  const access = decodingInfo.keySystemAccess;
1894
1938
  await processMediaKeySystemAccess(keySystem, access);
1895
- } catch (error) {} // Ignore errors.
1939
+ } catch (error) {
1940
+ // Ignore errors.
1941
+ shaka.log.v2('Failed to probe support for', keySystem, error);
1942
+ }
1896
1943
  };
1897
1944
 
1898
1945
  // Initialize the support structure for each key system.
@@ -2388,26 +2435,24 @@ shaka.media.DrmEngine = class {
2388
2435
  // The order of preference for drmInfo:
2389
2436
  // 1. Clear Key config, used for debugging, should override everything else.
2390
2437
  // (The application can still specify a clearkey license server.)
2391
- // 2. Application-configured servers, if any are present, should override
2392
- // anything from the manifest. Nuance: if key system A is in the
2393
- // manifest and key system B is in the player config, only B will be
2394
- // used, not A.
2438
+ // 2. Application-configured servers, if present, override
2439
+ // anything from the manifest.
2395
2440
  // 3. Manifest-provided license servers are only used if nothing else is
2396
2441
  // specified.
2397
2442
  // This is important because it allows the application a clear way to
2398
- // indicate which DRM systems should be used on platforms with multiple DRM
2399
- // systems.
2400
- // The only way to get license servers from the manifest is not to specify
2401
- // any in your player config.
2443
+ // indicate which DRM systems should be ignored on platforms with multiple
2444
+ // DRM systems.
2445
+ // Alternatively, use config.preferredKeySystems to specify the preferred
2446
+ // key system.
2402
2447
 
2403
2448
  if (originalKeySystem == 'org.w3.clearkey' && drmInfo.licenseServerUri) {
2404
2449
  // Preference 1: Clear Key with pre-configured keys will have a data URI
2405
2450
  // assigned as its license server. Don't change anything.
2406
2451
  return;
2407
- } else if (servers.size) {
2408
- // Preference 2: If anything is configured at the application level,
2409
- // override whatever was in the manifest.
2410
- const server = servers.get(originalKeySystem) || '';
2452
+ } else if (servers.size && servers.get(originalKeySystem)) {
2453
+ // Preference 2: If a license server for this keySystem is configured at
2454
+ // the application level, override whatever was in the manifest.
2455
+ const server = servers.get(originalKeySystem);
2411
2456
 
2412
2457
  drmInfo.licenseServerUri = server;
2413
2458
  } else {
@@ -2568,6 +2613,21 @@ shaka.media.DrmEngine.SessionMetaData;
2568
2613
  */
2569
2614
  shaka.media.DrmEngine.PlayerInterface;
2570
2615
 
2616
+ /**
2617
+ * @typedef {{
2618
+ * kids: !Array.<string>,
2619
+ * type: string
2620
+ * }}
2621
+ *
2622
+ * @property {!Array.<string>} kids
2623
+ * An array of key IDs. Each element of the array is the base64url encoding of
2624
+ * the octet sequence containing the key ID value.
2625
+ * @property {string} type
2626
+ * The requested MediaKeySessionType.
2627
+ * @see https://www.w3.org/TR/encrypted-media/#clear-key-request-format
2628
+ */
2629
+ shaka.media.DrmEngine.ClearKeyLicenceRequestFormat;
2630
+
2571
2631
  /**
2572
2632
  * The amount of time, in seconds, we wait to consider a session closed.
2573
2633
  * This allows us to work around Chrome bug https://crbug.com/1108158.
@@ -40,9 +40,10 @@ shaka.media.Capabilities = class {
40
40
  * @return {boolean}
41
41
  */
42
42
  static isChangeTypeSupported() {
43
- return !!window.SourceBuffer &&
43
+ const sourceBuffer = window.ManagedSourceBuffer || window.SourceBuffer;
44
+ return !!sourceBuffer &&
44
45
  // eslint-disable-next-line no-restricted-syntax
45
- !!SourceBuffer.prototype && !!SourceBuffer.prototype.changeType;
46
+ !!sourceBuffer.prototype && !!sourceBuffer.prototype.changeType;
46
47
  }
47
48
  };
48
49
 
@@ -270,6 +270,11 @@ shaka.media.MediaSourceEngine = class {
270
270
  for (const ref of stream.segmentIndex) {
271
271
  const mimeType = ref.mimeType || stream.mimeType || '';
272
272
  let codecs = ref.codecs || stream.codecs || '';
273
+ // Optimization for the case where the codecs and mimetype of the stream
274
+ // match the reference.
275
+ if (mimeType == stream.mimeType && codecs == stream.codecs) {
276
+ continue;
277
+ }
273
278
  // Don't check the same combination of mimetype + codecs twice.
274
279
  const combo = mimeType + ':' + codecs;
275
280
  if (seenCombos.has(combo)) {
@@ -484,6 +489,13 @@ shaka.media.MediaSourceEngine = class {
484
489
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
485
490
 
486
491
  await this.mediaSourceOpen_;
492
+ if (this.ended() || this.closed()) {
493
+ shaka.log.alwaysError('Expected MediaSource to be open during init(); ' +
494
+ 'reopening the media source.');
495
+ this.mediaSourceOpen_ = new shaka.util.PublicPromise();
496
+ this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
497
+ await this.mediaSourceOpen_;
498
+ }
487
499
 
488
500
  this.sequenceMode_ = sequenceMode;
489
501
  this.manifestType_ = manifestType;
@@ -652,6 +664,17 @@ shaka.media.MediaSourceEngine = class {
652
664
  return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true;
653
665
  }
654
666
 
667
+ /**
668
+ * @return {boolean} True if the MediaSource is in an "closed" state, or if
669
+ * the object has been destroyed.
670
+ */
671
+ closed() {
672
+ if (this.reloadingMediaSource_) {
673
+ return false;
674
+ }
675
+ return this.mediaSource_ ? this.mediaSource_.readyState == 'closed' : true;
676
+ }
677
+
655
678
  /**
656
679
  * Gets the first timestamp in buffer for the given content type.
657
680
  *
@@ -808,7 +831,7 @@ shaka.media.MediaSourceEngine = class {
808
831
  return frame.description ===
809
832
  'com.apple.streaming.transportStreamTimestamp';
810
833
  });
811
- if (metadataTimestamp && metadataTimestamp.data) {
834
+ if (metadataTimestamp) {
812
835
  timestamp = Math.round(metadataTimestamp.data) / 1000;
813
836
  }
814
837
  /** @private {shaka.extern.ID3Metadata} */
@@ -891,8 +914,7 @@ shaka.media.MediaSourceEngine = class {
891
914
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
892
915
 
893
916
  if (contentType == ContentType.TEXT) {
894
- if (this.manifestType_ == shaka.media.ManifestParser.HLS &&
895
- reference.discontinuitySequence >= 0) {
917
+ if (this.manifestType_ == shaka.media.ManifestParser.HLS) {
896
918
  // This won't be known until the first video segment is appended.
897
919
  const offset = await this.textSequenceModeOffset_;
898
920
  this.textEngine_.setTimestampOffset(offset);
@@ -962,7 +984,16 @@ shaka.media.MediaSourceEngine = class {
962
984
  this.audioCompensation_.resolve(compensation);
963
985
  }
964
986
  }
965
- const calculatedTimestampOffset = reference.startTime - timestamp;
987
+ let realTimestamp = timestamp;
988
+ const RAW_FORMATS = shaka.util.MimeUtils.RAW_FORMATS;
989
+ // For formats without containers and using segments mode, we need to
990
+ // adjust TimestampOffset relative to 0 because segments do not have
991
+ // any timestamp information.
992
+ if (!this.sequenceMode_ &&
993
+ RAW_FORMATS.includes(this.sourceBufferTypes_[contentType])) {
994
+ realTimestamp = 0;
995
+ }
996
+ const calculatedTimestampOffset = reference.startTime - realTimestamp;
966
997
  const timestampOffsetDifference =
967
998
  Math.abs(timestampOffset - calculatedTimestampOffset);
968
999
  if ((timestampOffsetDifference >= 0.001 || seeked || adaptation) &&
@@ -1319,6 +1350,13 @@ shaka.media.MediaSourceEngine = class {
1319
1350
  return;
1320
1351
  }
1321
1352
 
1353
+ // Avoid changing timestampOffset when the difference is less than 100 ms
1354
+ // from the end of the current buffer.
1355
+ const bufferEnd = this.bufferEnd(contentType);
1356
+ if (bufferEnd && Math.abs(bufferEnd - timestampOffset) < 0.1) {
1357
+ return;
1358
+ }
1359
+
1322
1360
  // Queue an abort() to help MSE splice together overlapping segments.
1323
1361
  // We set appendWindowEnd when we change periods in DASH content, and the
1324
1362
  // period transition may result in overlap.
@@ -1360,7 +1398,7 @@ shaka.media.MediaSourceEngine = class {
1360
1398
  // don't call it again. Also do not call if readyState is
1361
1399
  // 'closed' (not attached to video element) since it is not a
1362
1400
  // valid operation.
1363
- if (this.ended() || this.mediaSource_.readyState === 'closed') {
1401
+ if (this.ended() || this.closed()) {
1364
1402
  return;
1365
1403
  }
1366
1404
  // Tizen won't let us pass undefined, but it will let us omit the
@@ -2040,32 +2078,32 @@ shaka.media.MediaSourceEngine = class {
2040
2078
  }
2041
2079
 
2042
2080
  /**
2043
- * Codec switch if necessary, this will not resolve until the codec
2044
- * switch is over.
2045
2081
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2046
2082
  * @param {string} mimeType
2047
2083
  * @param {string} codecs
2048
- * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
2049
- * shaka.extern.Stream>} streamsByType
2050
- * @return {!Promise.<boolean>} true if there was a codec switch,
2051
- * false otherwise.
2084
+ * @return {{transmuxer: ?shaka.extern.Transmuxer,
2085
+ * transmuxerMuxed: boolean, basicType: string, codec: string,
2086
+ * mimeType: string}}
2052
2087
  * @private
2053
2088
  */
2054
- async codecSwitchIfNecessary_(contentType, mimeType, codecs, streamsByType) {
2089
+ getRealInfo_(contentType, mimeType, codecs) {
2055
2090
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
2056
- if (contentType == ContentType.TEXT) {
2057
- return false;
2058
- }
2059
2091
  const MimeUtils = shaka.util.MimeUtils;
2060
- const currentCodec = MimeUtils.getNormalizedCodec(
2061
- MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
2062
- const currentBasicType = MimeUtils.getBasicType(
2063
- this.sourceBufferTypes_[contentType]);
2064
-
2065
2092
  /** @type {?shaka.extern.Transmuxer} */
2066
2093
  let transmuxer;
2067
2094
  let transmuxerMuxed = false;
2068
- let newMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
2095
+ const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2096
+ ContentType.AUDIO, (codecs || '').split(','));
2097
+ const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2098
+ ContentType.VIDEO, (codecs || '').split(','));
2099
+ let codec = videoCodec;
2100
+ if (contentType == ContentType.AUDIO) {
2101
+ codec = audioCodec;
2102
+ }
2103
+ if (!codec) {
2104
+ codec = codecs;
2105
+ }
2106
+ let newMimeType = shaka.util.MimeUtils.getFullType(mimeType, codec);
2069
2107
  let needTransmux = this.config_.forceTransmux;
2070
2108
  if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
2071
2109
  (!this.sequenceMode_ &&
@@ -2075,36 +2113,79 @@ shaka.media.MediaSourceEngine = class {
2075
2113
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
2076
2114
  if (needTransmux) {
2077
2115
  const newMimeTypeWithAllCodecs =
2078
- shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codecs);
2116
+ shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codec);
2079
2117
  const transmuxerPlugin =
2080
2118
  TransmuxerEngine.findTransmuxer(newMimeTypeWithAllCodecs);
2081
2119
  if (transmuxerPlugin) {
2082
2120
  transmuxer = transmuxerPlugin();
2083
- const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2084
- ContentType.AUDIO, (codecs || '').split(','));
2085
- const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2086
- ContentType.VIDEO, (codecs || '').split(','));
2087
2121
  if (audioCodec && videoCodec) {
2088
2122
  transmuxerMuxed = true;
2089
- let codec = videoCodec;
2090
- if (contentType == ContentType.AUDIO) {
2091
- codec = audioCodec;
2092
- }
2093
- newMimeType = transmuxer.convertCodecs(contentType,
2094
- shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codec));
2095
- } else {
2096
- newMimeType =
2097
- transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
2098
2123
  }
2124
+ newMimeType =
2125
+ transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
2099
2126
  }
2100
2127
  }
2101
2128
 
2102
2129
  const newCodec = MimeUtils.getNormalizedCodec(
2103
2130
  MimeUtils.getCodecs(newMimeType));
2104
2131
  const newBasicType = MimeUtils.getBasicType(newMimeType);
2132
+ return {
2133
+ transmuxer,
2134
+ transmuxerMuxed,
2135
+ basicType: newBasicType,
2136
+ codec: newCodec,
2137
+ mimeType: newMimeType,
2138
+ };
2139
+ }
2140
+
2141
+ /**
2142
+ * Codec switch if necessary, this will not resolve until the codec
2143
+ * switch is over.
2144
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2145
+ * @param {string} mimeType
2146
+ * @param {string} codecs
2147
+ * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
2148
+ * shaka.extern.Stream>} streamsByType
2149
+ * @return {!Promise.<boolean>} true if there was a codec switch,
2150
+ * false otherwise.
2151
+ * @private
2152
+ */
2153
+ async codecSwitchIfNecessary_(contentType, mimeType, codecs, streamsByType) {
2154
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
2155
+ if (contentType == ContentType.TEXT) {
2156
+ return false;
2157
+ }
2158
+ const MimeUtils = shaka.util.MimeUtils;
2159
+ const currentCodec = MimeUtils.getNormalizedCodec(
2160
+ MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
2161
+ const currentBasicType = MimeUtils.getBasicType(
2162
+ this.sourceBufferTypes_[contentType]);
2163
+
2164
+ const realInfo = this.getRealInfo_(contentType, mimeType, codecs);
2165
+ const transmuxer = realInfo.transmuxer;
2166
+ const transmuxerMuxed = realInfo.transmuxerMuxed;
2167
+ const newBasicType = realInfo.basicType;
2168
+ const newCodec = realInfo.codec;
2169
+ const newMimeType = realInfo.mimeType;
2170
+
2171
+ let muxedContentCheck = true;
2172
+ if (transmuxerMuxed) {
2173
+ const muxedRealInfo =
2174
+ this.getRealInfo_(ContentType.AUDIO, mimeType, codecs);
2175
+ const muxedCurrentCodec = MimeUtils.getNormalizedCodec(
2176
+ MimeUtils.getCodecs(this.sourceBufferTypes_[ContentType.AUDIO]));
2177
+ const muxedCurrentBasicType = MimeUtils.getBasicType(
2178
+ this.sourceBufferTypes_[ContentType.AUDIO]);
2179
+ muxedContentCheck = muxedCurrentCodec == muxedRealInfo.codec &&
2180
+ muxedCurrentBasicType == muxedRealInfo.basicType;
2181
+ if (muxedRealInfo.transmuxer) {
2182
+ muxedRealInfo.transmuxer.destroy();
2183
+ }
2184
+ }
2105
2185
 
2106
2186
  // Current/new codecs base and basic type match then no need to switch
2107
- if (currentCodec === newCodec && currentBasicType === newBasicType) {
2187
+ if (currentCodec === newCodec && currentBasicType === newBasicType &&
2188
+ muxedContentCheck) {
2108
2189
  if (this.transmuxers_[contentType] && !transmuxer) {
2109
2190
  this.transmuxers_[contentType].destroy();
2110
2191
  delete this.transmuxers_[contentType];
@@ -47,9 +47,11 @@ shaka.media.SegmentUtils = class {
47
47
 
48
48
  /**
49
49
  * @param {!BufferSource} data
50
+ * @param {boolean=} disableAudio
51
+ * @param {boolean=} disableVideo
50
52
  * @return {?shaka.media.SegmentUtils.BasicInfo}
51
53
  */
52
- static getBasicInfoFromTs(data) {
54
+ static getBasicInfoFromTs(data, disableAudio = false, disableVideo = false) {
53
55
  const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
54
56
  const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
55
57
  const tsCodecs = tsParser.getCodecs();
@@ -57,50 +59,54 @@ shaka.media.SegmentUtils = class {
57
59
  const codecs = [];
58
60
  let hasAudio = false;
59
61
  let hasVideo = false;
60
- switch (tsCodecs.audio) {
61
- case 'aac':
62
- case 'aac-loas':
63
- codecs.push('mp4a.40.2');
64
- hasAudio = true;
65
- break;
66
- case 'mp3':
67
- codecs.push('mp4a.40.34');
68
- hasAudio = true;
69
- break;
70
- case 'ac3':
71
- codecs.push('ac-3');
72
- hasAudio = true;
73
- break;
74
- case 'ec3':
75
- codecs.push('ec-3');
76
- hasAudio = true;
77
- break;
78
- case 'opus':
79
- codecs.push('opus');
80
- hasAudio = true;
81
- break;
62
+ if (!disableAudio) {
63
+ switch (tsCodecs.audio) {
64
+ case 'aac':
65
+ case 'aac-loas':
66
+ codecs.push('mp4a.40.2');
67
+ hasAudio = true;
68
+ break;
69
+ case 'mp3':
70
+ codecs.push('mp4a.40.34');
71
+ hasAudio = true;
72
+ break;
73
+ case 'ac3':
74
+ codecs.push('ac-3');
75
+ hasAudio = true;
76
+ break;
77
+ case 'ec3':
78
+ codecs.push('ec-3');
79
+ hasAudio = true;
80
+ break;
81
+ case 'opus':
82
+ codecs.push('opus');
83
+ hasAudio = true;
84
+ break;
85
+ }
82
86
  }
83
- switch (tsCodecs.video) {
84
- case 'avc':
85
- if (videoInfo.codec) {
86
- codecs.push(videoInfo.codec);
87
- } else {
88
- codecs.push('avc1.42E01E');
89
- }
90
- hasVideo = true;
91
- break;
92
- case 'hvc':
93
- if (videoInfo.codec) {
94
- codecs.push(videoInfo.codec);
95
- } else {
96
- codecs.push('hvc1.1.6.L93.90');
97
- }
98
- hasVideo = true;
99
- break;
100
- case 'av1':
101
- codecs.push('av01.0.01M.08');
102
- hasVideo = true;
103
- break;
87
+ if (!disableVideo) {
88
+ switch (tsCodecs.video) {
89
+ case 'avc':
90
+ if (videoInfo.codec) {
91
+ codecs.push(videoInfo.codec);
92
+ } else {
93
+ codecs.push('avc1.42E01E');
94
+ }
95
+ hasVideo = true;
96
+ break;
97
+ case 'hvc':
98
+ if (videoInfo.codec) {
99
+ codecs.push(videoInfo.codec);
100
+ } else {
101
+ codecs.push('hvc1.1.6.L93.90');
102
+ }
103
+ hasVideo = true;
104
+ break;
105
+ case 'av1':
106
+ codecs.push('av01.0.01M.08');
107
+ hasVideo = true;
108
+ break;
109
+ }
104
110
  }
105
111
  if (!codecs.length) {
106
112
  return null;
@@ -381,9 +387,13 @@ shaka.media.SegmentUtils = class {
381
387
  if (initData) {
382
388
  captionParser.init(initData);
383
389
  }
384
- captionParser.parseFrom(data);
385
- for (const stream of captionParser.getStreams()) {
386
- closedCaptions.set(stream, stream);
390
+ try {
391
+ captionParser.parseFrom(data);
392
+ for (const stream of captionParser.getStreams()) {
393
+ closedCaptions.set(stream, stream);
394
+ }
395
+ } catch (e) {
396
+ shaka.log.debug('Error detecting CC streams', e);
387
397
  }
388
398
  captionParser.reset();
389
399
  }