shaka-player 4.10.0 → 4.10.2

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 (40) hide show
  1. package/dist/deps.js +9 -9
  2. package/dist/locales.js +36 -36
  3. package/dist/shaka-player.compiled.d.ts +2 -2
  4. package/dist/shaka-player.compiled.debug.d.ts +2 -2
  5. package/dist/shaka-player.compiled.debug.externs.js +1 -1
  6. package/dist/shaka-player.compiled.debug.js +140 -139
  7. package/dist/shaka-player.compiled.debug.map +4 -4
  8. package/dist/shaka-player.compiled.externs.js +1 -1
  9. package/dist/shaka-player.compiled.js +1169 -1163
  10. package/dist/shaka-player.compiled.map +4 -4
  11. package/dist/shaka-player.ui.d.ts +2 -2
  12. package/dist/shaka-player.ui.debug.d.ts +2 -2
  13. package/dist/shaka-player.ui.debug.externs.js +1 -1
  14. package/dist/shaka-player.ui.debug.js +167 -165
  15. package/dist/shaka-player.ui.debug.map +4 -4
  16. package/dist/shaka-player.ui.externs.js +1 -1
  17. package/dist/shaka-player.ui.js +1396 -1391
  18. package/dist/shaka-player.ui.map +4 -4
  19. package/externs/shaka/player.js +7 -1
  20. package/lib/abr/simple_abr_manager.js +11 -8
  21. package/lib/hls/hls_parser.js +7 -6
  22. package/lib/media/drm_engine.js +9 -197
  23. package/lib/media/presentation_timeline.js +13 -5
  24. package/lib/player.js +20 -12
  25. package/lib/polyfill/media_capabilities.js +4 -4
  26. package/lib/polyfill/patchedmediakeys_apple.js +3 -3
  27. package/lib/polyfill/patchedmediakeys_webkit.js +3 -3
  28. package/lib/text/simple_text_displayer.js +3 -0
  29. package/lib/text/ssa_text_parser.js +22 -7
  30. package/lib/text/text_utils.js +17 -1
  31. package/lib/text/ttml_text_parser.js +5 -3
  32. package/lib/text/ui_text_displayer.js +6 -1
  33. package/lib/util/config_utils.js +5 -0
  34. package/lib/util/drm_utils.js +191 -0
  35. package/lib/util/periods.js +4 -4
  36. package/lib/util/player_configuration.js +1 -0
  37. package/lib/util/ts_parser.js +4 -4
  38. package/package.json +1 -1
  39. package/ui/locales/fr.json +1 -1
  40. package/ui/statistics_button.js +10 -3
@@ -1707,7 +1707,8 @@ shaka.extern.AdsConfiguration;
1707
1707
  * ignoreDevicePixelRatio: boolean,
1708
1708
  * clearBufferSwitch: boolean,
1709
1709
  * safeMarginSwitch: number,
1710
- * cacheLoadThreshold: number
1710
+ * cacheLoadThreshold: number,
1711
+ * minTimeToSwitch: number
1711
1712
  * }}
1712
1713
  *
1713
1714
  * @property {boolean} enabled
@@ -1769,6 +1770,11 @@ shaka.extern.AdsConfiguration;
1769
1770
  * Indicates the value in milliseconds from which a request is not
1770
1771
  * considered cached.
1771
1772
  * Defaults to <code>20</code>.
1773
+ * @property {number} minTimeToSwitch
1774
+ * Indicates the minimum time to change quality once the real bandwidth is
1775
+ * available, in seconds. This time is only used on the first load.
1776
+ * Defaults to <code>0</code> seconds except in Apple browsers whose default
1777
+ * value is <code>0.5</code> seconds.
1772
1778
  * @exportDoc
1773
1779
  */
1774
1780
  shaka.extern.AbrConfiguration;
@@ -433,14 +433,17 @@ shaka.abr.SimpleAbrManager = class {
433
433
  return;
434
434
  }
435
435
  this.startupComplete_ = true;
436
- } else {
437
- // Check if we've left the switch interval.
438
- const now = Date.now();
439
- const delta = now - this.lastTimeChosenMs_;
440
- if (delta < this.config_.switchInterval * 1000) {
441
- shaka.log.v2('Still within switch interval...');
442
- return;
443
- }
436
+
437
+ this.lastTimeChosenMs_ -=
438
+ (this.config_.switchInterval - this.config_.minTimeToSwitch) * 1000;
439
+ }
440
+
441
+ // Check if we've left the switch interval.
442
+ const now = Date.now();
443
+ const delta = now - this.lastTimeChosenMs_;
444
+ if (delta < this.config_.switchInterval * 1000) {
445
+ shaka.log.v2('Still within switch interval...');
446
+ return;
444
447
  }
445
448
 
446
449
  const chosenVariant = this.chooseVariant();
@@ -16,7 +16,6 @@ goog.require('shaka.hls.PlaylistType');
16
16
  goog.require('shaka.hls.Tag');
17
17
  goog.require('shaka.hls.Utils');
18
18
  goog.require('shaka.log');
19
- goog.require('shaka.media.DrmEngine');
20
19
  goog.require('shaka.media.InitSegmentReference');
21
20
  goog.require('shaka.media.ManifestParser');
22
21
  goog.require('shaka.media.PresentationTimeline');
@@ -27,6 +26,7 @@ goog.require('shaka.net.DataUriPlugin');
27
26
  goog.require('shaka.net.NetworkingEngine');
28
27
  goog.require('shaka.util.ArrayUtils');
29
28
  goog.require('shaka.util.BufferUtils');
29
+ goog.require('shaka.util.DrmUtils');
30
30
  goog.require('shaka.util.ContentSteeringManager');
31
31
  goog.require('shaka.util.Error');
32
32
  goog.require('shaka.util.EventManager');
@@ -635,9 +635,10 @@ shaka.hls.HlsParser = class {
635
635
  if (goog.DEBUG) {
636
636
  const firstSequenceStartTime =
637
637
  mediaSequenceToStartTime.values().next().value;
638
- shaka.log.warning(
639
- firstSequenceStartTime == segment0.startTime,
640
- 'Sequence number map is not ordered as expected!');
638
+ if (firstSequenceStartTime != segment0.startTime) {
639
+ shaka.log.warning(
640
+ 'Sequence number map is not ordered as expected!');
641
+ }
641
642
  }
642
643
  for (const [sequence, start] of mediaSequenceToStartTime) {
643
644
  if (start == segment0.startTime) {
@@ -1936,7 +1937,7 @@ shaka.hls.HlsParser = class {
1936
1937
  audioInfos, videoInfos, bandwidth, width, height, frameRate, videoRange,
1937
1938
  videoLayout, drmInfos, keyIds) {
1938
1939
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1939
- const DrmEngine = shaka.media.DrmEngine;
1940
+ const DrmUtils = shaka.util.DrmUtils;
1940
1941
 
1941
1942
  for (const info of videoInfos) {
1942
1943
  this.addVideoAttributes_(
@@ -1979,7 +1980,7 @@ shaka.hls.HlsParser = class {
1979
1980
  const variantUriKey = videoStreamUri + ' - ' + audioStreamUri;
1980
1981
 
1981
1982
  if (audioStream && videoStream) {
1982
- if (!DrmEngine.areDrmCompatible(audioDrmInfos, videoDrmInfos)) {
1983
+ if (!DrmUtils.areDrmCompatible(audioDrmInfos, videoDrmInfos)) {
1983
1984
  shaka.log.warning(
1984
1985
  'Incompatible DRM info in HLS variant. Skipping.');
1985
1986
  continue;
@@ -18,7 +18,6 @@ goog.require('shaka.util.EventManager');
18
18
  goog.require('shaka.util.FakeEvent');
19
19
  goog.require('shaka.util.IDestroyable');
20
20
  goog.require('shaka.util.Iterables');
21
- goog.require('shaka.util.Lazy');
22
21
  goog.require('shaka.util.ManifestParserUtils');
23
22
  goog.require('shaka.util.MapUtils');
24
23
  goog.require('shaka.util.MimeUtils');
@@ -736,12 +735,13 @@ shaka.media.DrmEngine = class {
736
735
  // Note that some init data are extremely large and can't portably be used
737
736
  // as keys in a dictionary.
738
737
 
739
- const metadatas = this.activeSessions_.values();
740
- for (const metadata of metadatas) {
741
- if (shaka.util.BufferUtils.equal(initData, metadata.initData) &&
742
- this.config_.ignoreDuplicateInitData) {
743
- shaka.log.debug('Ignoring duplicate init data.');
744
- return;
738
+ if (this.config_.ignoreDuplicateInitData) {
739
+ const metadatas = this.activeSessions_.values();
740
+ for (const metadata of metadatas) {
741
+ if (shaka.util.BufferUtils.equal(initData, metadata.initData)) {
742
+ shaka.log.debug('Ignoring duplicate init data.');
743
+ return;
744
+ }
745
745
  }
746
746
  }
747
747
 
@@ -762,13 +762,6 @@ shaka.media.DrmEngine = class {
762
762
  return this.initialized_;
763
763
  }
764
764
 
765
- /**
766
- * @param {?shaka.extern.DrmInfo} drmInfo
767
- * @return {string} */
768
- static keySystem(drmInfo) {
769
- return drmInfo ? drmInfo.keySystem : '';
770
- }
771
-
772
765
  /**
773
766
  * Check if DrmEngine (as initialized) will likely be able to support the
774
767
  * given content type.
@@ -1763,30 +1756,13 @@ shaka.media.DrmEngine = class {
1763
1756
  this.playerInterface_.onKeyStatus(shaka.util.MapUtils.asObject(publicMap));
1764
1757
  }
1765
1758
 
1766
- /**
1767
- * Returns true if the browser has recent EME APIs.
1768
- *
1769
- * @return {boolean}
1770
- */
1771
- static isBrowserSupported() {
1772
- const basic =
1773
- !!window.MediaKeys &&
1774
- !!window.navigator &&
1775
- !!window.navigator.requestMediaKeySystemAccess &&
1776
- !!window.MediaKeySystemAccess &&
1777
- // eslint-disable-next-line no-restricted-syntax
1778
- !!window.MediaKeySystemAccess.prototype.getConfiguration;
1779
-
1780
- return basic;
1781
- }
1782
-
1783
1759
  /**
1784
1760
  * Returns a Promise to a map of EME support for well-known key systems.
1785
1761
  *
1786
1762
  * @return {!Promise.<!Object.<string, ?shaka.extern.DrmSupportType>>}
1787
1763
  */
1788
1764
  static async probeSupport() {
1789
- goog.asserts.assert(shaka.media.DrmEngine.isBrowserSupported(),
1765
+ goog.asserts.assert(shaka.util.DrmUtils.isBrowserSupported(),
1790
1766
  'Must have basic EME support');
1791
1767
 
1792
1768
  const testKeySystems = [
@@ -2115,91 +2091,13 @@ shaka.media.DrmEngine = class {
2115
2091
  }
2116
2092
  }
2117
2093
 
2118
- const keySystem = shaka.media.DrmEngine.keySystem(this.currentDrmInfo_);
2094
+ const keySystem = shaka.util.DrmUtils.keySystem(this.currentDrmInfo_);
2119
2095
  const drmInfos = this.getVariantDrmInfos_(variant);
2120
2096
 
2121
2097
  return drmInfos.length == 0 ||
2122
2098
  drmInfos.some((drmInfo) => drmInfo.keySystem == keySystem);
2123
2099
  }
2124
2100
 
2125
- /**
2126
- * Checks if two DrmInfos can be decrypted using the same key system.
2127
- * Clear content is considered compatible with every key system.
2128
- *
2129
- * @param {!Array.<!shaka.extern.DrmInfo>} drms1
2130
- * @param {!Array.<!shaka.extern.DrmInfo>} drms2
2131
- * @return {boolean}
2132
- */
2133
- static areDrmCompatible(drms1, drms2) {
2134
- if (!drms1.length || !drms2.length) {
2135
- return true;
2136
- }
2137
-
2138
- if (drms1 === drms2) {
2139
- return true;
2140
- }
2141
-
2142
- return shaka.media.DrmEngine.getCommonDrmInfos(
2143
- drms1, drms2).length > 0;
2144
- }
2145
-
2146
- /**
2147
- * Returns an array of drm infos that are present in both input arrays.
2148
- * If one of the arrays is empty, returns the other one since clear
2149
- * content is considered compatible with every drm info.
2150
- *
2151
- * @param {!Array.<!shaka.extern.DrmInfo>} drms1
2152
- * @param {!Array.<!shaka.extern.DrmInfo>} drms2
2153
- * @return {!Array.<!shaka.extern.DrmInfo>}
2154
- */
2155
- static getCommonDrmInfos(drms1, drms2) {
2156
- if (!drms1.length) {
2157
- return drms2;
2158
- }
2159
- if (!drms2.length) {
2160
- return drms1;
2161
- }
2162
-
2163
- const commonDrms = [];
2164
-
2165
- for (const drm1 of drms1) {
2166
- for (const drm2 of drms2) {
2167
- if (drm1.keySystem == drm2.keySystem) {
2168
- const initDataMap = new Map();
2169
- const bothInitDatas = (drm1.initData || [])
2170
- .concat(drm2.initData || []);
2171
- for (const d of bothInitDatas) {
2172
- initDataMap.set(d.keyId, d);
2173
- }
2174
- const initData = Array.from(initDataMap.values());
2175
-
2176
- const keyIds = drm1.keyIds && drm2.keyIds ?
2177
- new Set([...drm1.keyIds, ...drm2.keyIds]) :
2178
- drm1.keyIds || drm2.keyIds;
2179
- const mergedDrm = {
2180
- keySystem: drm1.keySystem,
2181
- licenseServerUri: drm1.licenseServerUri || drm2.licenseServerUri,
2182
- distinctiveIdentifierRequired: drm1.distinctiveIdentifierRequired ||
2183
- drm2.distinctiveIdentifierRequired,
2184
- persistentStateRequired: drm1.persistentStateRequired ||
2185
- drm2.persistentStateRequired,
2186
- videoRobustness: drm1.videoRobustness || drm2.videoRobustness,
2187
- audioRobustness: drm1.audioRobustness || drm2.audioRobustness,
2188
- serverCertificate: drm1.serverCertificate || drm2.serverCertificate,
2189
- serverCertificateUri: drm1.serverCertificateUri ||
2190
- drm2.serverCertificateUri,
2191
- initData,
2192
- keyIds,
2193
- };
2194
- commonDrms.push(mergedDrm);
2195
- break;
2196
- }
2197
- }
2198
- }
2199
-
2200
- return commonDrms;
2201
- }
2202
-
2203
2101
  /**
2204
2102
  * Concat the audio and video drmInfos in a variant.
2205
2103
  * @param {shaka.extern.Variant} variant
@@ -2717,73 +2615,6 @@ shaka.media.DrmEngine = class {
2717
2615
  this.newInitData('cenc', combinedData);
2718
2616
  return this.allSessionsLoaded_;
2719
2617
  }
2720
-
2721
- /**
2722
- * A method for generating a key for the MediaKeySystemAccessRequests cache.
2723
- *
2724
- * @param {string} videoCodec
2725
- * @param {string} audioCodec
2726
- * @param {string} keySystem
2727
- * @return {string}
2728
- * @private
2729
- */
2730
- static generateKeySystemCacheKey_(videoCodec, audioCodec, keySystem) {
2731
- return `${videoCodec}#${audioCodec}#${keySystem}`;
2732
- }
2733
-
2734
- /**
2735
- * Check does MediaKeySystemAccess cache contains something for following
2736
- * attributes.
2737
- *
2738
- * @param {string} videoCodec
2739
- * @param {string} audioCodec
2740
- * @param {string} keySystem
2741
- * @return {boolean}
2742
- */
2743
- static hasMediaKeySystemAccess(videoCodec, audioCodec, keySystem) {
2744
- const DrmEngine = shaka.media.DrmEngine;
2745
- const key = DrmEngine.generateKeySystemCacheKey_(
2746
- videoCodec, audioCodec, keySystem);
2747
- return DrmEngine.memoizedMediaKeySystemAccessRequests_.has(key);
2748
- }
2749
-
2750
- /**
2751
- * Get MediaKeySystemAccess object for following attributes.
2752
- *
2753
- * @param {string} videoCodec
2754
- * @param {string} audioCodec
2755
- * @param {string} keySystem
2756
- * @return {?MediaKeySystemAccess}
2757
- */
2758
- static getMediaKeySystemAccess(videoCodec, audioCodec, keySystem) {
2759
- const DrmEngine = shaka.media.DrmEngine;
2760
- const key = DrmEngine.generateKeySystemCacheKey_(
2761
- videoCodec, audioCodec, keySystem);
2762
- return DrmEngine.memoizedMediaKeySystemAccessRequests_.get(key) || null;
2763
- }
2764
-
2765
- /**
2766
- * Store MediaKeySystemAccess object associated with specified attributes.
2767
- *
2768
- * @param {string} videoCodec
2769
- * @param {string} audioCodec
2770
- * @param {string} keySystem
2771
- * @param {!MediaKeySystemAccess} mksa
2772
- */
2773
- static setMediaKeySystemAccess(videoCodec, audioCodec, keySystem, mksa) {
2774
- const DrmEngine = shaka.media.DrmEngine;
2775
- const key = DrmEngine.generateKeySystemCacheKey_(
2776
- videoCodec, audioCodec, keySystem);
2777
- return DrmEngine.memoizedMediaKeySystemAccessRequests_.set(key, mksa);
2778
- }
2779
-
2780
- /**
2781
- * Clears underlying cache.
2782
- */
2783
- static clearMediaKeySystemAccessMap() {
2784
- const DrmEngine = shaka.media.DrmEngine;
2785
- DrmEngine.memoizedMediaKeySystemAccessRequests_.clear();
2786
- }
2787
2618
  };
2788
2619
 
2789
2620
 
@@ -2868,22 +2699,3 @@ shaka.media.DrmEngine.SESSION_LOAD_TIMEOUT_ = 5;
2868
2699
  * @type {number}
2869
2700
  */
2870
2701
  shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME = 0.5;
2871
-
2872
-
2873
- /**
2874
- * Contains the suggested "default" key ID used by EME polyfills that do not
2875
- * have a per-key key status. See w3c/encrypted-media#32.
2876
- * @type {!shaka.util.Lazy.<!ArrayBuffer>}
2877
- */
2878
- shaka.media.DrmEngine.DUMMY_KEY_ID = new shaka.util.Lazy(
2879
- () => shaka.util.BufferUtils.toArrayBuffer(new Uint8Array([0])));
2880
-
2881
-
2882
- /**
2883
- * A cache that stores the MediaKeySystemAccess result of calling
2884
- * `navigator.requestMediaKeySystemAccess` by a key combination of
2885
- * video/audio codec and key system string.
2886
- *
2887
- * @private {!Map<string, !MediaKeySystemAccess>}
2888
- */
2889
- shaka.media.DrmEngine.memoizedMediaKeySystemAccessRequests_ = new Map();
@@ -247,12 +247,22 @@ shaka.media.PresentationTimeline = class {
247
247
  return;
248
248
  }
249
249
 
250
- const firstStartTime = timeline[0].start + startOffset;
251
- const lastEndTime = timeline[timeline.length - 1].end + startOffset;
250
+ // Date.now() is in milliseconds, from which we compute "now" in seconds.
251
+ const now = (Date.now() + this.clockOffset_) / 1000.0;
252
+ // Exclude time ranges that are in the "future".
253
+ const timelineForStart = timeline.filter((timeRange) =>
254
+ timeRange.start + startOffset < now);
255
+ if (timelineForStart.length == 0) {
256
+ return;
257
+ }
258
+
259
+ const firstStartTime = timelineForStart[0].start + startOffset;
260
+ const lastEndTime = timelineForStart[timelineForStart.length - 1].end +
261
+ startOffset;
252
262
 
253
263
  this.notifyMinSegmentStartTime(firstStartTime);
254
264
 
255
- this.maxSegmentDuration_ = timeline.reduce(
265
+ this.maxSegmentDuration_ = timelineForStart.reduce(
256
266
  (max, r) => { return Math.max(max, r.end - r.start); },
257
267
  this.maxSegmentDuration_);
258
268
 
@@ -263,8 +273,6 @@ shaka.media.PresentationTimeline = class {
263
273
  !this.startTimeLocked_) {
264
274
  // Since we have explicit segment end times, calculate a presentation
265
275
  // start based on them. This start time accounts for drift.
266
- // Date.now() is in milliseconds, from which we compute "now" in seconds.
267
- const now = (Date.now() + this.clockOffset_) / 1000.0;
268
276
  this.presentationStartTime_ =
269
277
  now - this.maxSegmentEndTime_ - this.maxSegmentDuration_;
270
278
  }
package/lib/player.js CHANGED
@@ -45,6 +45,7 @@ goog.require('shaka.util.CmcdManager');
45
45
  goog.require('shaka.util.CmsdManager');
46
46
  goog.require('shaka.util.ConfigUtils');
47
47
  goog.require('shaka.util.Dom');
48
+ goog.require('shaka.util.DrmUtils');
48
49
  goog.require('shaka.util.Error');
49
50
  goog.require('shaka.util.EventManager');
50
51
  goog.require('shaka.util.FakeEvent');
@@ -108,8 +109,8 @@ goog.requireType('shaka.media.PresentationTimeline');
108
109
  * 'downloadfailed'
109
110
  * @property {!shaka.extern.Request} request
110
111
  * @property {?shaka.util.Error} error
111
- * @param {number} httpResponseCode
112
- * @param {boolean} aborted
112
+ * @property {number} httpResponseCode
113
+ * @property {boolean} aborted
113
114
  * @exportDoc
114
115
  */
115
116
 
@@ -1072,7 +1073,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1072
1073
  // DRM support is not strictly necessary, but the APIs at least need to be
1073
1074
  // there. Our no-op DRM polyfill should handle that.
1074
1075
  // TODO(#1017): Consider making even DrmEngine optional.
1075
- const drmSupport = shaka.media.DrmEngine.isBrowserSupported();
1076
+ const drmSupport = shaka.util.DrmUtils.isBrowserSupported();
1076
1077
  if (!drmSupport) {
1077
1078
  return false;
1078
1079
  }
@@ -1469,7 +1470,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1469
1470
  // each playback.
1470
1471
  if (this.config_.streaming.clearDecodingCache) {
1471
1472
  shaka.util.StreamUtils.clearDecodingConfigCache();
1472
- shaka.media.DrmEngine.clearMediaKeySystemAccessMap();
1473
+ shaka.util.DrmUtils.clearMediaKeySystemAccessMap();
1473
1474
  }
1474
1475
 
1475
1476
  this.manifest_ = null;
@@ -4330,7 +4331,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4330
4331
  * @export
4331
4332
  */
4332
4333
  keySystem() {
4333
- return shaka.media.DrmEngine.keySystem(this.drmInfo());
4334
+ return shaka.util.DrmUtils.keySystem(this.drmInfo());
4334
4335
  }
4335
4336
 
4336
4337
  /**
@@ -4995,9 +4996,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4995
4996
  Math.abs((a.video.width || 0) - (b.video.width || 0));
4996
4997
  }
4997
4998
  };
4998
- // Find the variant whose size is closest to the active variant. This
4999
- // ensures we stay at about the same resolution when just changing the
5000
- // language/role.
4999
+ // Find the variant whose size is closest to the active variant. This
5000
+ // ensures we stay at about the same resolution when just changing the
5001
+ // language/role.
5001
5002
  const active = this.streamingEngine_.getCurrentVariant();
5002
5003
  const set =
5003
5004
  this.currentAdaptationSetCriteria_.create(this.manifest_.variants);
@@ -5011,6 +5012,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5011
5012
  bestVariant = curVariant;
5012
5013
  }
5013
5014
  }
5015
+ if (bestVariant == active) {
5016
+ shaka.log.debug('Audio already selected.');
5017
+ return;
5018
+ }
5014
5019
  if (bestVariant) {
5015
5020
  const track = shaka.util.StreamUtils.variantToTrack(bestVariant);
5016
5021
  this.selectVariantTrack(track, /* clearBuffer= */ true, safeMargin);
@@ -5475,7 +5480,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5475
5480
  const element = /** @type {!HTMLVideoElement} */ (this.video_);
5476
5481
 
5477
5482
  const completionRatio = element.currentTime / element.duration;
5478
- if (!isNaN(completionRatio)) {
5483
+ if (!isNaN(completionRatio) && !this.isLive()) {
5479
5484
  this.stats_.setCompletionPercent(Math.round(100 * completionRatio));
5480
5485
  }
5481
5486
 
@@ -6926,7 +6931,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6926
6931
  // Check if we should show text (based on difference between audio and text
6927
6932
  // languages).
6928
6933
  if (initialTextStream) {
6929
- if (initialVariant.audio && this.shouldInitiallyShowText_(
6934
+ if (this.shouldInitiallyShowText_(
6930
6935
  initialVariant.audio, initialTextStream)) {
6931
6936
  this.isTextVisible_ = true;
6932
6937
  }
@@ -6946,7 +6951,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6946
6951
  /**
6947
6952
  * Check if we should show text on screen automatically.
6948
6953
  *
6949
- * @param {shaka.extern.Stream} audioStream
6954
+ * @param {?shaka.extern.Stream} audioStream
6950
6955
  * @param {shaka.extern.Stream} textStream
6951
6956
  * @return {boolean}
6952
6957
  * @private
@@ -6977,6 +6982,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6977
6982
  }
6978
6983
 
6979
6984
  if (this.config_.autoShowText == AutoShowText.IF_SUBTITLES_MAY_BE_NEEDED) {
6985
+ if (!audioStream) {
6986
+ return false;
6987
+ }
6980
6988
  /* The text should automatically be shown if the text is
6981
6989
  * language-compatible with the user's text language preference, but not
6982
6990
  * compatible with the audio. These are cases where we deduce that
@@ -7801,7 +7809,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7801
7809
  * @export
7802
7810
  */
7803
7811
  // eslint-disable-next-line no-useless-concat
7804
- shaka.Player.version = 'v4.10.0' + '-uncompiled'; // x-release-please-version
7812
+ shaka.Player.version = 'v4.10.2' + '-uncompiled'; // x-release-please-version
7805
7813
 
7806
7814
  // Initialize the deprecation system using the version string we just set
7807
7815
  // on the player.
@@ -8,8 +8,8 @@ goog.provide('shaka.polyfill.MediaCapabilities');
8
8
 
9
9
  goog.require('shaka.log');
10
10
  goog.require('shaka.media.Capabilities');
11
- goog.require('shaka.media.DrmEngine');
12
11
  goog.require('shaka.polyfill');
12
+ goog.require('shaka.util.DrmUtils');
13
13
  goog.require('shaka.util.Platform');
14
14
 
15
15
 
@@ -284,14 +284,14 @@ shaka.polyfill.MediaCapabilities = class {
284
284
  /** @type {MediaKeySystemAccess} */
285
285
  let keySystemAccess = null;
286
286
  try {
287
- if (shaka.media.DrmEngine.hasMediaKeySystemAccess(
287
+ if (shaka.util.DrmUtils.hasMediaKeySystemAccess(
288
288
  videoCodec, audioCodec, keySystem)) {
289
- keySystemAccess = shaka.media.DrmEngine.getMediaKeySystemAccess(
289
+ keySystemAccess = shaka.util.DrmUtils.getMediaKeySystemAccess(
290
290
  videoCodec, audioCodec, keySystem);
291
291
  } else {
292
292
  keySystemAccess = await navigator.requestMediaKeySystemAccess(
293
293
  mcapKeySystemConfig.keySystem, [mediaKeySystemConfig]);
294
- shaka.media.DrmEngine.setMediaKeySystemAccess(
294
+ shaka.util.DrmUtils.setMediaKeySystemAccess(
295
295
  videoCodec, audioCodec, keySystem, keySystemAccess);
296
296
  }
297
297
  } catch (e) {
@@ -8,9 +8,9 @@ goog.provide('shaka.polyfill.PatchedMediaKeysApple');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
- goog.require('shaka.media.DrmEngine');
12
11
  goog.require('shaka.polyfill');
13
12
  goog.require('shaka.util.BufferUtils');
13
+ goog.require('shaka.util.DrmUtils');
14
14
  goog.require('shaka.util.EventManager');
15
15
  goog.require('shaka.util.FakeEvent');
16
16
  goog.require('shaka.util.FakeEventTarget');
@@ -746,7 +746,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
746
746
  /** @override */
747
747
  forEach(fn) {
748
748
  if (this.status_) {
749
- fn(this.status_, shaka.media.DrmEngine.DUMMY_KEY_ID.value());
749
+ fn(this.status_, shaka.util.DrmUtils.DUMMY_KEY_ID.value());
750
750
  }
751
751
  }
752
752
 
@@ -760,7 +760,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
760
760
 
761
761
  /** @override */
762
762
  has(keyId) {
763
- const fakeKeyId = shaka.media.DrmEngine.DUMMY_KEY_ID.value();
763
+ const fakeKeyId = shaka.util.DrmUtils.DUMMY_KEY_ID.value();
764
764
  if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) {
765
765
  return true;
766
766
  }
@@ -8,9 +8,9 @@ goog.provide('shaka.polyfill.PatchedMediaKeysWebkit');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
- goog.require('shaka.media.DrmEngine');
12
11
  goog.require('shaka.polyfill');
13
12
  goog.require('shaka.util.BufferUtils');
13
+ goog.require('shaka.util.DrmUtils');
14
14
  goog.require('shaka.util.EventManager');
15
15
  goog.require('shaka.util.FakeEvent');
16
16
  goog.require('shaka.util.FakeEventTarget');
@@ -924,7 +924,7 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeyStatusMap = class {
924
924
  /** @override */
925
925
  forEach(fn) {
926
926
  if (this.status_) {
927
- fn(this.status_, shaka.media.DrmEngine.DUMMY_KEY_ID.value());
927
+ fn(this.status_, shaka.util.DrmUtils.DUMMY_KEY_ID.value());
928
928
  }
929
929
  }
930
930
 
@@ -938,7 +938,7 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeyStatusMap = class {
938
938
 
939
939
  /** @override */
940
940
  has(keyId) {
941
- const fakeKeyId = shaka.media.DrmEngine.DUMMY_KEY_ID.value();
941
+ const fakeKeyId = shaka.util.DrmUtils.DUMMY_KEY_ID.value();
942
942
  if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) {
943
943
  return true;
944
944
  }
@@ -37,6 +37,9 @@ shaka.text.SimpleTextDisplayer = class {
37
37
  // If the video element has TextTracks, disable them. If we see one that
38
38
  // was created by a previous instance of Shaka Player, reuse it.
39
39
  for (const track of Array.from(video.textTracks)) {
40
+ if (track.kind === 'metadata' || track.kind === 'chapters') {
41
+ continue;
42
+ }
40
43
  // NOTE: There is no API available to remove a TextTrack from a video
41
44
  // element.
42
45
  track.mode = 'disabled';
@@ -60,24 +60,39 @@ shaka.text.SsaTextParser = class {
60
60
  events: '',
61
61
  };
62
62
 
63
+ let tag = null;
64
+ let lines = null;
63
65
  const parts = str.split(/\r?\n\s*\r?\n/);
64
66
  for (const part of parts) {
67
+ lines = part;
65
68
  // SSA content
66
69
  const match = SsaTextParser.ssaContent_.exec(part);
67
70
  if (match) {
68
- const tag = match[1];
69
- const lines = match[2];
70
- if (tag == 'V4 Styles' || tag == 'V4+ Styles') {
71
+ tag = match[1];
72
+ lines = match[2];
73
+ }
74
+ if (tag == 'V4 Styles' || tag == 'V4+ Styles') {
75
+ section.styles = lines;
76
+ if (section.events) {
77
+ section.styles += '\n' + lines;
78
+ } else {
71
79
  section.styles = lines;
72
- continue;
73
80
  }
74
- if (tag == 'Events') {
81
+ continue;
82
+ }
83
+ if (tag == 'Events') {
84
+ if (section.events) {
85
+ section.events += '\n' + lines;
86
+ } else {
75
87
  section.events = lines;
76
- continue;
77
88
  }
89
+ continue;
90
+ }
91
+ if (tag == 'Script Info') {
92
+ continue;
78
93
  }
79
94
  shaka.log.warning('SsaTextParser parser encountered an unknown part.',
80
- part);
95
+ lines);
81
96
  }
82
97
 
83
98
  // Process styles
@@ -148,7 +148,7 @@ shaka.text.Utils = class {
148
148
  */
149
149
  static getCuesToFlatten(cues, parentCue) {
150
150
  const result = [];
151
- for (const cue of cues) {
151
+ for (const cue of shaka.text.Utils.removeDuplicates(cues)) {
152
152
  if (cue.isContainer) {
153
153
  // Recurse to find the actual text payload cues.
154
154
  result.push(...shaka.text.Utils.getCuesToFlatten(cue.nestedCues, cue));
@@ -162,4 +162,20 @@ shaka.text.Utils = class {
162
162
  }
163
163
  return result;
164
164
  }
165
+
166
+ /**
167
+ * @param {!Array.<!shaka.text.Cue>} cues
168
+ * @return {!Array.<!shaka.text.Cue>}
169
+ */
170
+ static removeDuplicates(cues) {
171
+ const uniqueCues = [];
172
+ for (const cue of cues) {
173
+ const isValid = !uniqueCues.some(
174
+ (existingCue) => shaka.text.Cue.equal(cue, existingCue));
175
+ if (isValid) {
176
+ uniqueCues.push(cue);
177
+ }
178
+ }
179
+ return uniqueCues;
180
+ }
165
181
  };