shaka-player 4.8.10 → 4.9.1

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 (66) hide show
  1. package/README.md +5 -0
  2. package/dist/controls.css +1 -1
  3. package/dist/controls.css.map +1 -1
  4. package/dist/deps.js +5 -5
  5. package/dist/locales.js +34 -34
  6. package/dist/shaka-player.compiled.d.ts +39 -18
  7. package/dist/shaka-player.compiled.debug.d.ts +39 -18
  8. package/dist/shaka-player.compiled.debug.externs.js +32 -12
  9. package/dist/shaka-player.compiled.debug.js +638 -579
  10. package/dist/shaka-player.compiled.debug.map +4 -4
  11. package/dist/shaka-player.compiled.externs.js +32 -12
  12. package/dist/shaka-player.compiled.js +1272 -1237
  13. package/dist/shaka-player.compiled.map +4 -4
  14. package/dist/shaka-player.ui.d.ts +39 -18
  15. package/dist/shaka-player.ui.debug.d.ts +39 -18
  16. package/dist/shaka-player.ui.debug.externs.js +43 -23
  17. package/dist/shaka-player.ui.debug.js +668 -609
  18. package/dist/shaka-player.ui.debug.map +4 -4
  19. package/dist/shaka-player.ui.externs.js +43 -23
  20. package/dist/shaka-player.ui.js +1487 -1451
  21. package/dist/shaka-player.ui.map +4 -4
  22. package/externs/shaka/manifest.js +11 -1
  23. package/externs/shaka/offline.js +3 -0
  24. package/externs/shaka/player.js +105 -12
  25. package/externs/tizen.js +1 -1
  26. package/lib/abr/simple_abr_manager.js +5 -0
  27. package/lib/ads/media_tailor_ad.js +11 -0
  28. package/lib/ads/media_tailor_ad_manager.js +37 -3
  29. package/lib/dash/dash_parser.js +655 -46
  30. package/lib/dash/mpd_utils.js +7 -8
  31. package/lib/dash/segment_template.js +65 -10
  32. package/lib/hls/hls_parser.js +92 -34
  33. package/lib/media/content_workarounds.js +4 -2
  34. package/lib/media/drm_engine.js +274 -40
  35. package/lib/media/manifest_filterer.js +2 -2
  36. package/lib/media/media_source_engine.js +66 -31
  37. package/lib/media/segment_utils.js +14 -3
  38. package/lib/mss/mss_parser.js +2 -0
  39. package/lib/net/http_plugin_utils.js +2 -1
  40. package/lib/net/networking_engine.js +14 -0
  41. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  42. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  43. package/lib/offline/manifest_converter.js +2 -0
  44. package/lib/offline/storage.js +1 -0
  45. package/lib/player.js +220 -14
  46. package/lib/polyfill/media_capabilities.js +10 -35
  47. package/lib/transmuxer/aac_transmuxer.js +4 -2
  48. package/lib/transmuxer/ac3_transmuxer.js +2 -1
  49. package/lib/transmuxer/ec3_transmuxer.js +2 -1
  50. package/lib/transmuxer/mp3_transmuxer.js +4 -2
  51. package/lib/transmuxer/mpeg_ts_transmuxer.js +2 -1
  52. package/lib/transmuxer/mss_transmuxer.js +4 -2
  53. package/lib/transmuxer/ts_transmuxer.js +44 -24
  54. package/lib/util/config_utils.js +74 -6
  55. package/lib/util/error.js +17 -8
  56. package/lib/util/mp4_box_parsers.js +23 -2
  57. package/lib/util/periods.js +51 -28
  58. package/lib/util/platform.js +3 -1
  59. package/lib/util/player_configuration.js +20 -8
  60. package/lib/util/stream_utils.js +62 -30
  61. package/lib/util/tXml.js +74 -6
  62. package/package.json +2 -2
  63. package/support.html +1 -0
  64. package/ui/controls.js +4 -0
  65. package/ui/less/ad_controls.less +2 -1
  66. package/ui/resolution_selection.js +12 -3
@@ -21,6 +21,7 @@ goog.require('shaka.util.Lazy');
21
21
  goog.require('shaka.util.ManifestParserUtils');
22
22
  goog.require('shaka.util.MapUtils');
23
23
  goog.require('shaka.util.MimeUtils');
24
+ goog.require('shaka.util.ObjectUtils');
24
25
  goog.require('shaka.util.Platform');
25
26
  goog.require('shaka.util.Pssh');
26
27
  goog.require('shaka.util.PublicPromise');
@@ -1492,6 +1493,13 @@ shaka.media.DrmEngine = class {
1492
1493
  request.initData = metadata.initData;
1493
1494
  request.initDataType = metadata.initDataType;
1494
1495
  }
1496
+ if (advancedConfig && advancedConfig.headers) {
1497
+ // Add these to the existing headers. Do not clobber them!
1498
+ // For PlayReady, there will already be headers in the request.
1499
+ for (const header in advancedConfig.headers) {
1500
+ request.headers[header] = advancedConfig.headers[header];
1501
+ }
1502
+ }
1495
1503
  // NOTE: allowCrossSiteCredentials can be set in a request filter.
1496
1504
 
1497
1505
  if (shaka.media.DrmEngine.isPlayReadyKeySystem(
@@ -1801,33 +1809,200 @@ shaka.media.DrmEngine = class {
1801
1809
  'com.widevine.alpha',
1802
1810
  'com.microsoft.playready',
1803
1811
  'com.microsoft.playready.recommendation',
1812
+ 'com.chromecast.playready',
1804
1813
  'com.apple.fps.1_0',
1805
1814
  'com.apple.fps',
1806
1815
  ];
1807
1816
 
1817
+ const widevineRobustness = [
1818
+ 'SW_SECURE_CRYPTO',
1819
+ 'SW_SECURE_DECODE',
1820
+ 'HW_SECURE_CRYPTO',
1821
+ 'HW_SECURE_DECODE',
1822
+ 'HW_SECURE_ALL',
1823
+ ];
1824
+
1825
+ const playreadyRobustness = [
1826
+ '150',
1827
+ '2000',
1828
+ '3000',
1829
+ ];
1830
+
1831
+ const testRobustness = {
1832
+ 'com.widevine.alpha': widevineRobustness,
1833
+ 'com.microsoft.playready': playreadyRobustness,
1834
+ 'com.microsoft.playready.recommendation': playreadyRobustness,
1835
+ 'com.chromecast.playready': playreadyRobustness,
1836
+ };
1837
+
1808
1838
  const basicVideoCapabilities = [
1809
1839
  {contentType: 'video/mp4; codecs="avc1.42E01E"'},
1810
1840
  {contentType: 'video/webm; codecs="vp8"'},
1811
1841
  ];
1812
1842
 
1813
- const basicConfig = {
1814
- initDataTypes: ['cenc'],
1815
- videoCapabilities: basicVideoCapabilities,
1816
- };
1817
- const offlineConfig = {
1843
+ const basicAudioCapabilities = [
1844
+ {contentType: 'audio/mp4; codecs="mp4a.40.2"'},
1845
+ {contentType: 'audio/webm; codecs="opus"'},
1846
+ ];
1847
+
1848
+ const basicConfigTemplate = {
1818
1849
  videoCapabilities: basicVideoCapabilities,
1819
- persistentState: 'required',
1820
- sessionTypes: ['persistent-license'],
1850
+ audioCapabilities: basicAudioCapabilities,
1851
+ initDataTypes: ['cenc', 'sinf', 'skd', 'keyids'],
1821
1852
  };
1822
1853
 
1823
- // Try the offline config first, then fall back to the basic config.
1824
- const configs = [offlineConfig, basicConfig];
1854
+ const testEncryptionSchemes = [
1855
+ null,
1856
+ 'cenc',
1857
+ 'cbcs',
1858
+ 'cbcs-1-9',
1859
+ ];
1825
1860
 
1826
1861
  /** @type {!Map.<string, ?shaka.extern.DrmSupportType>} */
1827
1862
  const support = new Map();
1828
1863
 
1829
- const testSystem = async (keySystem) => {
1864
+ /**
1865
+ * @param {string} keySystem
1866
+ * @param {MediaKeySystemAccess} access
1867
+ * @return {!Promise}
1868
+ */
1869
+ const processMediaKeySystemAccess = async (keySystem, access) => {
1870
+ try {
1871
+ await access.createMediaKeys();
1872
+ } catch (error) {
1873
+ // In some cases, we can get a successful access object but fail to
1874
+ // create a MediaKeys instance. When this happens, don't update the
1875
+ // support structure. If a previous test succeeded, we won't overwrite
1876
+ // any of the results.
1877
+ return;
1878
+ }
1879
+
1880
+ // If sessionTypes is missing, assume no support for persistent-license.
1881
+ const sessionTypes = access.getConfiguration().sessionTypes;
1882
+ let persistentState = sessionTypes ?
1883
+ sessionTypes.includes('persistent-license') : false;
1884
+
1885
+ // Tizen 3.0 doesn't support persistent licenses, but reports that it
1886
+ // does. It doesn't fail until you call update() with a license
1887
+ // response, which is way too late.
1888
+ // This is a work-around for #894.
1889
+ if (shaka.util.Platform.isTizen3()) {
1890
+ persistentState = false;
1891
+ }
1892
+
1893
+ const videoCapabilities = access.getConfiguration().videoCapabilities;
1894
+ const audioCapabilities = access.getConfiguration().audioCapabilities;
1895
+
1896
+ let supportValue = {
1897
+ persistentState,
1898
+ encryptionSchemes: [],
1899
+ videoRobustnessLevels: [],
1900
+ audioRobustnessLevels: [],
1901
+ };
1902
+ if (support.has(keySystem) && support.get(keySystem)) {
1903
+ // Update the existing non-null value.
1904
+ supportValue = support.get(keySystem);
1905
+ } else {
1906
+ // Set a new one.
1907
+ support.set(keySystem, supportValue);
1908
+ }
1909
+
1910
+ // If the returned config doesn't mention encryptionScheme, the field
1911
+ // is not supported. If installed, our polyfills should make sure this
1912
+ // doesn't happen.
1913
+ const returnedScheme = videoCapabilities[0].encryptionScheme;
1914
+ if (returnedScheme &&
1915
+ !supportValue.encryptionSchemes.includes(returnedScheme)) {
1916
+ supportValue.encryptionSchemes.push(returnedScheme);
1917
+ }
1918
+
1919
+ const videoRobustness = videoCapabilities[0].robustness;
1920
+ if (videoRobustness &&
1921
+ !supportValue.videoRobustnessLevels.includes(videoRobustness)) {
1922
+ supportValue.videoRobustnessLevels.push(videoRobustness);
1923
+ }
1924
+
1925
+ const audioRobustness = audioCapabilities[0].robustness;
1926
+ if (audioRobustness &&
1927
+ !supportValue.audioRobustnessLevels.includes(audioRobustness)) {
1928
+ supportValue.audioRobustnessLevels.push(audioRobustness);
1929
+ }
1930
+ };
1931
+
1932
+ const testSystemEme = async (keySystem, encryptionScheme,
1933
+ videoRobustness, audioRobustness) => {
1934
+ try {
1935
+ const basicConfig =
1936
+ shaka.util.ObjectUtils.cloneObject(basicConfigTemplate);
1937
+ for (const cap of basicConfig.videoCapabilities) {
1938
+ cap.encryptionScheme = encryptionScheme;
1939
+ cap.robustness = videoRobustness;
1940
+ }
1941
+ for (const cap of basicConfig.audioCapabilities) {
1942
+ cap.encryptionScheme = encryptionScheme;
1943
+ cap.robustness = audioRobustness;
1944
+ }
1945
+
1946
+ const offlineConfig = shaka.util.ObjectUtils.cloneObject(basicConfig);
1947
+ offlineConfig.persistentState = 'required';
1948
+ offlineConfig.sessionTypes = ['persistent-license'];
1949
+
1950
+ const configs = [offlineConfig, basicConfig];
1951
+
1952
+ const access = await navigator.requestMediaKeySystemAccess(
1953
+ keySystem, configs);
1954
+ await processMediaKeySystemAccess(keySystem, access);
1955
+ } catch (error) {} // Ignore errors.
1956
+ };
1957
+
1958
+ const testSystemMcap = async (keySystem, encryptionScheme,
1959
+ videoRobustness, audioRobustness) => {
1830
1960
  try {
1961
+ const decodingConfig = {
1962
+ type: 'media-source',
1963
+ video: {
1964
+ contentType: basicVideoCapabilities[0].contentType,
1965
+ width: 640,
1966
+ height: 480,
1967
+ bitrate: 1,
1968
+ framerate: 1,
1969
+ },
1970
+ audio: {
1971
+ contentType: basicAudioCapabilities[0].contentType,
1972
+ channels: 2,
1973
+ bitrate: 1,
1974
+ samplerate: 1,
1975
+ },
1976
+ keySystemConfiguration: {
1977
+ keySystem,
1978
+ video: {
1979
+ encryptionScheme,
1980
+ robustness: videoRobustness,
1981
+ },
1982
+ audio: {
1983
+ encryptionScheme,
1984
+ robustness: audioRobustness,
1985
+ },
1986
+ },
1987
+ };
1988
+
1989
+ const decodingInfo =
1990
+ await navigator.mediaCapabilities.decodingInfo(decodingConfig);
1991
+
1992
+ const access = decodingInfo.keySystemAccess;
1993
+ await processMediaKeySystemAccess(keySystem, access);
1994
+ } catch (error) {} // Ignore errors.
1995
+ };
1996
+
1997
+ // Initialize the support structure for each key system.
1998
+ for (const keySystem of testKeySystems) {
1999
+ support.set(keySystem, null);
2000
+ }
2001
+
2002
+ // Test each key system and encryption scheme.
2003
+ const tests = [];
2004
+ for (const encryptionScheme of testEncryptionSchemes) {
2005
+ for (const keySystem of testKeySystems) {
1831
2006
  // Our Polyfill will reject anything apart com.apple.fps key systems.
1832
2007
  // It seems the Safari modern EME API will allow to request a
1833
2008
  // MediaKeySystemAccess for the ClearKey CDM, create and update a key
@@ -1835,40 +2010,22 @@ shaka.media.DrmEngine = class {
1835
2010
  // Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
1836
2011
  if (keySystem === 'org.w3.clearkey' &&
1837
2012
  shaka.util.Platform.isSafari()) {
1838
- throw new Error('Unsupported keySystem');
1839
- }
1840
-
1841
- const access = await navigator.requestMediaKeySystemAccess(
1842
- keySystem, configs);
1843
-
1844
- // Edge doesn't return supported session types, but current versions
1845
- // do not support persistent-license. If sessionTypes is missing,
1846
- // assume no support for persistent-license.
1847
- // TODO: Polyfill Edge to return known supported session types.
1848
- // Edge bug: https://bit.ly/2IeKzho
1849
- const sessionTypes = access.getConfiguration().sessionTypes;
1850
- let persistentState = sessionTypes ?
1851
- sessionTypes.includes('persistent-license') : false;
1852
-
1853
- // Tizen 3.0 doesn't support persistent licenses, but reports that it
1854
- // does. It doesn't fail until you call update() with a license
1855
- // response, which is way too late.
1856
- // This is a work-around for #894.
1857
- if (shaka.util.Platform.isTizen3()) {
1858
- persistentState = false;
2013
+ continue;
1859
2014
  }
2015
+ tests.push(testSystemEme(keySystem, encryptionScheme, '', ''));
2016
+ tests.push(testSystemMcap(keySystem, encryptionScheme, '', ''));
2017
+ }
2018
+ }
1860
2019
 
1861
- support.set(keySystem, {persistentState: persistentState});
1862
- await access.createMediaKeys();
1863
- } catch (e) {
1864
- // Either the request failed or createMediaKeys failed.
1865
- // Either way, write null to the support object.
1866
- support.set(keySystem, null);
2020
+ for (const keySystem of testKeySystems) {
2021
+ for (const robustness of (testRobustness[keySystem] || [])) {
2022
+ tests.push(testSystemEme(keySystem, null, robustness, ''));
2023
+ tests.push(testSystemEme(keySystem, null, '', robustness));
2024
+ tests.push(testSystemMcap(keySystem, null, robustness, ''));
2025
+ tests.push(testSystemMcap(keySystem, null, '', robustness));
1867
2026
  }
1868
- };
2027
+ }
1869
2028
 
1870
- // Test each key system.
1871
- const tests = testKeySystems.map((keySystem) => testSystem(keySystem));
1872
2029
  await Promise.all(tests);
1873
2030
  return shaka.util.MapUtils.asObject(support);
1874
2031
  }
@@ -2562,6 +2719,73 @@ shaka.media.DrmEngine = class {
2562
2719
  this.newInitData('cenc', combinedData);
2563
2720
  return this.allSessionsLoaded_;
2564
2721
  }
2722
+
2723
+ /**
2724
+ * A method for generating a key for the MediaKeySystemAccessRequests cache.
2725
+ *
2726
+ * @param {string} videoCodec
2727
+ * @param {string} audioCodec
2728
+ * @param {string} keySystem
2729
+ * @return {string}
2730
+ * @private
2731
+ */
2732
+ static generateKeySystemCacheKey_(videoCodec, audioCodec, keySystem) {
2733
+ return `${videoCodec}#${audioCodec}#${keySystem}`;
2734
+ }
2735
+
2736
+ /**
2737
+ * Check does MediaKeySystemAccess cache contains something for following
2738
+ * attributes.
2739
+ *
2740
+ * @param {string} videoCodec
2741
+ * @param {string} audioCodec
2742
+ * @param {string} keySystem
2743
+ * @return {boolean}
2744
+ */
2745
+ static hasMediaKeySystemAccess(videoCodec, audioCodec, keySystem) {
2746
+ const DrmEngine = shaka.media.DrmEngine;
2747
+ const key = DrmEngine.generateKeySystemCacheKey_(
2748
+ videoCodec, audioCodec, keySystem);
2749
+ return DrmEngine.memoizedMediaKeySystemAccessRequests_.has(key);
2750
+ }
2751
+
2752
+ /**
2753
+ * Get MediaKeySystemAccess object for following attributes.
2754
+ *
2755
+ * @param {string} videoCodec
2756
+ * @param {string} audioCodec
2757
+ * @param {string} keySystem
2758
+ * @return {?MediaKeySystemAccess}
2759
+ */
2760
+ static getMediaKeySystemAccess(videoCodec, audioCodec, keySystem) {
2761
+ const DrmEngine = shaka.media.DrmEngine;
2762
+ const key = DrmEngine.generateKeySystemCacheKey_(
2763
+ videoCodec, audioCodec, keySystem);
2764
+ return DrmEngine.memoizedMediaKeySystemAccessRequests_.get(key) || null;
2765
+ }
2766
+
2767
+ /**
2768
+ * Store MediaKeySystemAccess object associated with specified attributes.
2769
+ *
2770
+ * @param {string} videoCodec
2771
+ * @param {string} audioCodec
2772
+ * @param {string} keySystem
2773
+ * @param {!MediaKeySystemAccess} mksa
2774
+ */
2775
+ static setMediaKeySystemAccess(videoCodec, audioCodec, keySystem, mksa) {
2776
+ const DrmEngine = shaka.media.DrmEngine;
2777
+ const key = DrmEngine.generateKeySystemCacheKey_(
2778
+ videoCodec, audioCodec, keySystem);
2779
+ return DrmEngine.memoizedMediaKeySystemAccessRequests_.set(key, mksa);
2780
+ }
2781
+
2782
+ /**
2783
+ * Clears underlying cache.
2784
+ */
2785
+ static clearMediaKeySystemAccessMap() {
2786
+ const DrmEngine = shaka.media.DrmEngine;
2787
+ DrmEngine.memoizedMediaKeySystemAccessRequests_.clear();
2788
+ }
2565
2789
  };
2566
2790
 
2567
2791
 
@@ -2655,3 +2879,13 @@ shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME = 0.5;
2655
2879
  */
2656
2880
  shaka.media.DrmEngine.DUMMY_KEY_ID = new shaka.util.Lazy(
2657
2881
  () => shaka.util.BufferUtils.toArrayBuffer(new Uint8Array([0])));
2882
+
2883
+
2884
+ /**
2885
+ * A cache that stores the MediaKeySystemAccess result of calling
2886
+ * `navigator.requestMediaKeySystemAccess` by a key combination of
2887
+ * video/audio codec and key system string.
2888
+ *
2889
+ * @private {!Map<string, !MediaKeySystemAccess>}
2890
+ */
2891
+ shaka.media.DrmEngine.memoizedMediaKeySystemAccessRequests_ = new Map();
@@ -19,7 +19,7 @@ goog.require('shaka.util.Error');
19
19
  shaka.media.ManifestFilterer = class {
20
20
  /**
21
21
  * @param {?shaka.extern.PlayerConfiguration} config
22
- * @param {{width: number, height: number}} maxHwRes
22
+ * @param {shaka.extern.Resolution} maxHwRes
23
23
  * @param {?shaka.media.DrmEngine} drmEngine
24
24
  */
25
25
  constructor(config, maxHwRes, drmEngine) {
@@ -28,7 +28,7 @@ shaka.media.ManifestFilterer = class {
28
28
  /** @private {!shaka.extern.PlayerConfiguration} */
29
29
  this.config_ = config;
30
30
 
31
- /** @private {{width: number, height: number}} */
31
+ /** @private {shaka.extern.Resolution} */
32
32
  this.maxHwRes_ = maxHwRes;
33
33
 
34
34
  /** @private {?shaka.media.DrmEngine} drmEngine */
@@ -569,7 +569,8 @@ shaka.media.MediaSourceEngine = class {
569
569
  shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
570
570
  exception,
571
571
  'The mediaSource_ status was ' + this.mediaSource_.readyState +
572
- ' expected \'open\'');
572
+ ' expected \'open\'',
573
+ null);
573
574
  }
574
575
 
575
576
  if (this.sequenceMode_) {
@@ -935,10 +936,12 @@ shaka.media.MediaSourceEngine = class {
935
936
  if (this.attemptTimestampOffsetCalculation_) {
936
937
  this.enqueueOperation_(
937
938
  contentType,
938
- () => this.abort_(contentType));
939
+ () => this.abort_(contentType),
940
+ null);
939
941
  this.enqueueOperation_(
940
942
  contentType,
941
- () => this.setTimestampOffset_(contentType, timestampOffset));
943
+ () => this.setTimestampOffset_(contentType, timestampOffset),
944
+ null);
942
945
  }
943
946
  }
944
947
  // Timestamps can only be reliably extracted from video, not audio.
@@ -983,7 +986,8 @@ shaka.media.MediaSourceEngine = class {
983
986
  }
984
987
 
985
988
  data = this.workAroundBrokenPlatforms_(
986
- data, reference ? reference.startTime : null, contentType);
989
+ data, reference ? reference.startTime : null, contentType,
990
+ reference ? reference.getUris()[0] : null);
987
991
 
988
992
  if (reference && this.sequenceMode_ && contentType != ContentType.TEXT) {
989
993
  // In sequence mode, for non-text streams, if we just cleared the buffer
@@ -995,10 +999,14 @@ shaka.media.MediaSourceEngine = class {
995
999
  // extended during unbuffered seeks or automatic adaptations; it is
996
1000
  // possible for the append state to be PARSING_MEDIA_SEGMENT from the
997
1001
  // previous SourceBuffer#appendBuffer() call.
998
- this.enqueueOperation_(contentType, () => this.abort_(contentType));
999
1002
  this.enqueueOperation_(
1000
1003
  contentType,
1001
- () => this.setTimestampOffset_(contentType, timestampOffset));
1004
+ () => this.abort_(contentType),
1005
+ null);
1006
+ this.enqueueOperation_(
1007
+ contentType,
1008
+ () => this.setTimestampOffset_(contentType, timestampOffset),
1009
+ null);
1002
1010
  }
1003
1011
  }
1004
1012
 
@@ -1009,7 +1017,7 @@ shaka.media.MediaSourceEngine = class {
1009
1017
  bufferedBefore = this.getBuffered_(contentType);
1010
1018
  }
1011
1019
  this.append_(contentType, data, timestampOffset);
1012
- });
1020
+ }, reference ? reference.getUris()[0] : null);
1013
1021
 
1014
1022
  if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
1015
1023
  const bufferedAfter = this.getBuffered_(contentType);
@@ -1067,11 +1075,13 @@ shaka.media.MediaSourceEngine = class {
1067
1075
  } else {
1068
1076
  await this.enqueueOperation_(
1069
1077
  contentType,
1070
- () => this.remove_(contentType, startTime, endTime));
1078
+ () => this.remove_(contentType, startTime, endTime),
1079
+ null);
1071
1080
  if (this.needSplitMuxedContent_) {
1072
1081
  await this.enqueueOperation_(
1073
1082
  ContentType.AUDIO,
1074
- () => this.remove_(ContentType.AUDIO, startTime, endTime));
1083
+ () => this.remove_(ContentType.AUDIO, startTime, endTime),
1084
+ null);
1075
1085
  }
1076
1086
  }
1077
1087
  }
@@ -1093,12 +1103,14 @@ shaka.media.MediaSourceEngine = class {
1093
1103
  // Note that not all platforms allow clearing to Infinity.
1094
1104
  await this.enqueueOperation_(
1095
1105
  contentType,
1096
- () => this.remove_(contentType, 0, this.mediaSource_.duration));
1106
+ () => this.remove_(contentType, 0, this.mediaSource_.duration),
1107
+ null);
1097
1108
  if (this.needSplitMuxedContent_) {
1098
1109
  await this.enqueueOperation_(
1099
1110
  ContentType.AUDIO,
1100
1111
  () => this.remove_(
1101
- ContentType.AUDIO, 0, this.mediaSource_.duration));
1112
+ ContentType.AUDIO, 0, this.mediaSource_.duration),
1113
+ null);
1102
1114
  }
1103
1115
  }
1104
1116
  }
@@ -1129,11 +1141,13 @@ shaka.media.MediaSourceEngine = class {
1129
1141
  }
1130
1142
  await this.enqueueOperation_(
1131
1143
  contentType,
1132
- () => this.flush_(contentType));
1144
+ () => this.flush_(contentType),
1145
+ null);
1133
1146
  if (this.needSplitMuxedContent_) {
1134
1147
  await this.enqueueOperation_(
1135
1148
  ContentType.AUDIO,
1136
- () => this.flush_(ContentType.AUDIO));
1149
+ () => this.flush_(ContentType.AUDIO),
1150
+ null);
1137
1151
  }
1138
1152
  }
1139
1153
 
@@ -1188,33 +1202,39 @@ shaka.media.MediaSourceEngine = class {
1188
1202
  // set it.
1189
1203
  operations.push(this.enqueueOperation_(
1190
1204
  contentType,
1191
- () => this.abort_(contentType)));
1205
+ () => this.abort_(contentType),
1206
+ null));
1192
1207
  if (this.needSplitMuxedContent_) {
1193
1208
  operations.push(this.enqueueOperation_(
1194
1209
  ContentType.AUDIO,
1195
- () => this.abort_(ContentType.AUDIO)));
1210
+ () => this.abort_(ContentType.AUDIO),
1211
+ null));
1196
1212
  }
1197
1213
  }
1198
1214
  if (!ignoreTimestampOffset) {
1199
1215
  operations.push(this.enqueueOperation_(
1200
1216
  contentType,
1201
- () => this.setTimestampOffset_(contentType, timestampOffset)));
1217
+ () => this.setTimestampOffset_(contentType, timestampOffset),
1218
+ null));
1202
1219
  if (this.needSplitMuxedContent_) {
1203
1220
  operations.push(this.enqueueOperation_(
1204
1221
  ContentType.AUDIO,
1205
1222
  () => this.setTimestampOffset_(
1206
- ContentType.AUDIO, timestampOffset)));
1223
+ ContentType.AUDIO, timestampOffset),
1224
+ null));
1207
1225
  }
1208
1226
  }
1209
1227
  operations.push(this.enqueueOperation_(
1210
1228
  contentType,
1211
1229
  () => this.setAppendWindow_(
1212
- contentType, appendWindowStart, appendWindowEnd)));
1230
+ contentType, appendWindowStart, appendWindowEnd),
1231
+ null));
1213
1232
  if (this.needSplitMuxedContent_) {
1214
1233
  operations.push(this.enqueueOperation_(
1215
1234
  ContentType.AUDIO,
1216
1235
  () => this.setAppendWindow_(
1217
- ContentType.AUDIO, appendWindowStart, appendWindowEnd)));
1236
+ ContentType.AUDIO, appendWindowStart, appendWindowEnd),
1237
+ null));
1218
1238
  }
1219
1239
 
1220
1240
  await Promise.all(operations);
@@ -1251,19 +1271,23 @@ shaka.media.MediaSourceEngine = class {
1251
1271
  // set it.
1252
1272
  this.enqueueOperation_(
1253
1273
  contentType,
1254
- () => this.abort_(contentType));
1274
+ () => this.abort_(contentType),
1275
+ null);
1255
1276
  if (this.needSplitMuxedContent_) {
1256
1277
  this.enqueueOperation_(
1257
1278
  ContentType.AUDIO,
1258
- () => this.abort_(ContentType.AUDIO));
1279
+ () => this.abort_(ContentType.AUDIO),
1280
+ null);
1259
1281
  }
1260
1282
  await this.enqueueOperation_(
1261
1283
  contentType,
1262
- () => this.setTimestampOffset_(contentType, timestampOffset));
1284
+ () => this.setTimestampOffset_(contentType, timestampOffset),
1285
+ null);
1263
1286
  if (this.needSplitMuxedContent_) {
1264
1287
  await this.enqueueOperation_(
1265
1288
  ContentType.AUDIO,
1266
- () => this.setTimestampOffset_(ContentType.AUDIO, timestampOffset));
1289
+ () => this.setTimestampOffset_(ContentType.AUDIO, timestampOffset),
1290
+ null);
1267
1291
  }
1268
1292
  }
1269
1293
 
@@ -1306,6 +1330,7 @@ shaka.media.MediaSourceEngine = class {
1306
1330
  const dummyOperation = {
1307
1331
  start: () => {},
1308
1332
  p: new shaka.util.PublicPromise(),
1333
+ uri: null,
1309
1334
  };
1310
1335
  this.queues_[contentType].unshift(dummyOperation);
1311
1336
  }
@@ -1465,7 +1490,7 @@ shaka.media.MediaSourceEngine = class {
1465
1490
  shaka.util.Error.Severity.CRITICAL,
1466
1491
  shaka.util.Error.Category.MEDIA,
1467
1492
  shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_FAILED,
1468
- code));
1493
+ code, operation.uri));
1469
1494
  // Do not pop from queue. An 'updateend' event will fire next, and to
1470
1495
  // avoid synchronizing these two event handlers, we will allow that one to
1471
1496
  // pop from the queue as normal. Note that because the operation has
@@ -1497,14 +1522,16 @@ shaka.media.MediaSourceEngine = class {
1497
1522
  *
1498
1523
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1499
1524
  * @param {function()} start
1525
+ * @param {?string} uri
1500
1526
  * @return {!Promise}
1501
1527
  * @private
1502
1528
  */
1503
- enqueueOperation_(contentType, start) {
1529
+ enqueueOperation_(contentType, start, uri) {
1504
1530
  this.destroyer_.ensureNotDestroyed();
1505
1531
  const operation = {
1506
1532
  start: start,
1507
1533
  p: new shaka.util.PublicPromise(),
1534
+ uri,
1508
1535
  };
1509
1536
  this.queues_[contentType].push(operation);
1510
1537
 
@@ -1536,6 +1563,7 @@ shaka.media.MediaSourceEngine = class {
1536
1563
  const operation = {
1537
1564
  start: () => ready.resolve(),
1538
1565
  p: ready,
1566
+ uri: null,
1539
1567
  };
1540
1568
 
1541
1569
  this.queues_[contentType].push(operation);
@@ -1593,7 +1621,8 @@ shaka.media.MediaSourceEngine = class {
1593
1621
  shaka.util.Error.Category.MEDIA,
1594
1622
  shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
1595
1623
  exception,
1596
- this.video_.error || 'No error in the media element');
1624
+ this.video_.error || 'No error in the media element',
1625
+ null);
1597
1626
  } finally {
1598
1627
  // Unblock the queues.
1599
1628
  for (const contentType in this.sourceBuffers_) {
@@ -1637,7 +1666,8 @@ shaka.media.MediaSourceEngine = class {
1637
1666
  shaka.util.Error.Category.MEDIA,
1638
1667
  shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
1639
1668
  exception,
1640
- this.video_.error || 'No error in the media element'));
1669
+ this.video_.error || 'No error in the media element',
1670
+ next.uri));
1641
1671
  }
1642
1672
  this.popFromQueue_(contentType);
1643
1673
  }
@@ -1684,10 +1714,11 @@ shaka.media.MediaSourceEngine = class {
1684
1714
  * @param {!BufferSource} segment
1685
1715
  * @param {?number} startTime
1686
1716
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1717
+ * @param {?string} uri
1687
1718
  * @return {!BufferSource}
1688
1719
  * @private
1689
1720
  */
1690
- workAroundBrokenPlatforms_(segment, startTime, contentType) {
1721
+ workAroundBrokenPlatforms_(segment, startTime, contentType, uri) {
1691
1722
  const isInitSegment = startTime == null;
1692
1723
  const encryptionExpected = this.expectedEncryption_[contentType];
1693
1724
 
@@ -1708,7 +1739,7 @@ shaka.media.MediaSourceEngine = class {
1708
1739
  shaka.util.MimeUtils.getContainerType(
1709
1740
  this.sourceBufferTypes_[contentType]) == 'mp4') {
1710
1741
  shaka.log.debug('Forcing fake encryption information in init segment.');
1711
- segment = shaka.media.ContentWorkarounds.fakeEncryption(segment);
1742
+ segment = shaka.media.ContentWorkarounds.fakeEncryption(segment, uri);
1712
1743
  }
1713
1744
 
1714
1745
  return segment;
@@ -1761,7 +1792,8 @@ shaka.media.MediaSourceEngine = class {
1761
1792
  changeType(contentType, mimeType, transmuxer) {
1762
1793
  return this.enqueueOperation_(
1763
1794
  contentType,
1764
- () => this.change_(contentType, mimeType, transmuxer));
1795
+ () => this.change_(contentType, mimeType, transmuxer),
1796
+ null);
1765
1797
  }
1766
1798
 
1767
1799
  /**
@@ -2096,7 +2128,8 @@ shaka.media.MediaSourceEngine.createObjectURL = window.URL.createObjectURL;
2096
2128
  /**
2097
2129
  * @typedef {{
2098
2130
  * start: function(),
2099
- * p: !shaka.util.PublicPromise
2131
+ * p: !shaka.util.PublicPromise,
2132
+ * uri: ?string
2100
2133
  * }}
2101
2134
  *
2102
2135
  * @summary An operation in queue.
@@ -2104,6 +2137,8 @@ shaka.media.MediaSourceEngine.createObjectURL = window.URL.createObjectURL;
2104
2137
  * The function which starts the operation.
2105
2138
  * @property {!shaka.util.PublicPromise} p
2106
2139
  * The PublicPromise which is associated with this operation.
2140
+ * @property {?string} uri
2141
+ * A segment URI (if any) associated with this operation.
2107
2142
  */
2108
2143
  shaka.media.MediaSourceEngine.Operation;
2109
2144