shaka-player 4.9.21 → 4.9.22

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.
package/externs/xbox.js CHANGED
@@ -41,3 +41,23 @@ Windows.Media.Protection.ProtectionCapabilityResult = {
41
41
  maybe: 'Maybe',
42
42
  probably: 'Probably',
43
43
  };
44
+
45
+
46
+ /** @const */
47
+ var chrome = {};
48
+
49
+
50
+ /** @const */
51
+ chrome.webview = {};
52
+
53
+
54
+ /** @const */
55
+ chrome.webview.hostObjects = {};
56
+
57
+
58
+ /** @const */
59
+ chrome.webview.hostObjects.sync = {};
60
+
61
+
62
+ /** @const */
63
+ chrome.webview.hostObjects.sync.Windows = Windows;
@@ -107,7 +107,7 @@ shaka.abr.SimpleAbrManager = class {
107
107
 
108
108
  /** @private {shaka.util.Timer} */
109
109
  this.resizeObserverTimer_ = new shaka.util.Timer(() => {
110
- if (this.config_.restrictToElementSize) {
110
+ if (this.enabled_ && this.config_.restrictToElementSize) {
111
111
  const chosenVariant = this.chooseVariant();
112
112
  if (chosenVariant) {
113
113
  this.switch_(chosenVariant, this.config_.clearBufferSwitch,
@@ -99,8 +99,8 @@ shaka.media.ClosedCaptionParser = class {
99
99
  // This is necessary when a new Period comes in DASH or a discontinuity
100
100
  // in HLS.
101
101
  this.reset();
102
- this.ceaParser_.init(initSegment);
103
102
  }
103
+ this.ceaParser_.init(initSegment);
104
104
  }
105
105
 
106
106
  /**
@@ -2012,7 +2012,7 @@ shaka.media.MediaSourceEngine = class {
2012
2012
  return false;
2013
2013
  }
2014
2014
  const MimeUtils = shaka.util.MimeUtils;
2015
- const currentCodec = MimeUtils.getCodecBase(
2015
+ const currentCodec = MimeUtils.getNormalizedCodec(
2016
2016
  MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
2017
2017
  const currentBasicType = MimeUtils.getBasicType(
2018
2018
  this.sourceBufferTypes_[contentType]);
@@ -2054,7 +2054,7 @@ shaka.media.MediaSourceEngine = class {
2054
2054
  }
2055
2055
  }
2056
2056
 
2057
- const newCodec = MimeUtils.getCodecBase(
2057
+ const newCodec = MimeUtils.getNormalizedCodec(
2058
2058
  MimeUtils.getCodecs(newMimeType));
2059
2059
  const newBasicType = MimeUtils.getBasicType(newMimeType);
2060
2060
 
@@ -2113,7 +2113,7 @@ shaka.media.MediaSourceEngine = class {
2113
2113
  return false;
2114
2114
  }
2115
2115
  const MimeUtils = shaka.util.MimeUtils;
2116
- const currentCodec = MimeUtils.getCodecBase(
2116
+ const currentCodec = MimeUtils.getNormalizedCodec(
2117
2117
  MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
2118
2118
  const currentBasicType = MimeUtils.getBasicType(
2119
2119
  this.sourceBufferTypes_[contentType]);
@@ -2140,7 +2140,7 @@ shaka.media.MediaSourceEngine = class {
2140
2140
  }
2141
2141
  }
2142
2142
 
2143
- const newCodec = MimeUtils.getCodecBase(
2143
+ const newCodec = MimeUtils.getNormalizedCodec(
2144
2144
  MimeUtils.getCodecs(newMimeType));
2145
2145
  const newBasicType = MimeUtils.getBasicType(newMimeType);
2146
2146
 
@@ -15,9 +15,10 @@ goog.require('shaka.log');
15
15
  goog.require('shaka.media.InitSegmentReference');
16
16
  goog.require('shaka.media.ManifestParser');
17
17
  goog.require('shaka.media.MediaSourceEngine');
18
+ goog.require('shaka.media.SegmentIndex');
18
19
  goog.require('shaka.media.SegmentIterator');
19
- goog.require('shaka.media.SegmentReference');
20
20
  goog.require('shaka.media.SegmentPrefetch');
21
+ goog.require('shaka.media.SegmentReference');
21
22
  goog.require('shaka.net.Backoff');
22
23
  goog.require('shaka.net.NetworkingEngine');
23
24
  goog.require('shaka.util.BufferUtils');
@@ -73,6 +74,20 @@ shaka.media.StreamingEngine = class {
73
74
  /** @private {?shaka.extern.StreamingConfiguration} */
74
75
  this.config_ = null;
75
76
 
77
+ /**
78
+ * Retains deferred SegmentIndex objects for streams which were switched
79
+ * away from during an ongoing fetchAndAppend_().
80
+ * They are released and cleared from this map when the next onUpdate_() for
81
+ * a stream's content type occurs.
82
+ * This is because at that point, it is guaranteed no logic is actively
83
+ * using information for these old streams.
84
+ *
85
+ * Lastly, the operations are stored in arrays in the off chance multiple
86
+ * ABR switches happen during an ongoing fetchAndAppend_().
87
+ * @private {!Map.<string, !Array.<?shaka.media.SegmentIndex>>}
88
+ */
89
+ this.deferredCloseSegmentIndex_ = new Map();
90
+
76
91
  /** @private {number} */
77
92
  this.bufferingGoalScale_ = 1;
78
93
 
@@ -161,6 +176,7 @@ shaka.media.StreamingEngine = class {
161
176
  state.segmentPrefetch.clearAll();
162
177
  state.segmentPrefetch = null;
163
178
  }
179
+ this.handleDeferredCloseSegmentIndexes_(state);
164
180
  }
165
181
  for (const prefetch of this.audioPrefetchMap_.values()) {
166
182
  prefetch.clearAll();
@@ -170,6 +186,7 @@ shaka.media.StreamingEngine = class {
170
186
 
171
187
  this.mediaStates_.clear();
172
188
  this.audioPrefetchMap_.clear();
189
+ this.deferredCloseSegmentIndex_.clear();
173
190
 
174
191
  this.playerInterface_ = null;
175
192
  this.manifest_ = null;
@@ -504,6 +521,29 @@ shaka.media.StreamingEngine = class {
504
521
  }
505
522
 
506
523
 
524
+ /**
525
+ * Handles deferred releases of old SegmentIndexes for the mediaState's
526
+ * content type from a previous update.
527
+ * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
528
+ * @private
529
+ */
530
+ handleDeferredCloseSegmentIndexes_(mediaState) {
531
+ for (const [key, value] of this.deferredCloseSegmentIndex_.entries()) {
532
+ const streamId = /** @type {string} */ (key);
533
+ const segmentIndexArray =
534
+ /** @type {!Array.<?shaka.media.SegmentIndex>} */ (value);
535
+ if (streamId.includes(mediaState.type)) {
536
+ for (const segmentIndex of segmentIndexArray) {
537
+ if (segmentIndex) {
538
+ segmentIndex.release();
539
+ }
540
+ }
541
+ this.deferredCloseSegmentIndex_.delete(streamId);
542
+ }
543
+ }
544
+ }
545
+
546
+
507
547
  /**
508
548
  * Switches to the given Stream. |stream| may be from any Variant.
509
549
  *
@@ -572,11 +612,30 @@ shaka.media.StreamingEngine = class {
572
612
  fullMimeType, this.manifest_.sequenceMode, stream.external);
573
613
  }
574
614
 
575
- // Releases the segmentIndex of the old stream.
615
+ // Releases the segmentIndex of the old stream if safe to do so.
576
616
  // Do not close segment indexes we are prefetching.
577
617
  if (!this.audioPrefetchMap_.has(mediaState.stream)) {
578
618
  if (mediaState.stream.closeSegmentIndex) {
579
- mediaState.stream.closeSegmentIndex();
619
+ if (mediaState.performingUpdate) {
620
+ const oldStreamTag =
621
+ shaka.media.StreamingEngine.logPrefix_(mediaState);
622
+ // The ongoing update is still using the old stream's segment
623
+ // reference information.
624
+ // If we close the old stream now, the update will not complete
625
+ // correctly.
626
+ // onUpdate_() will resume the closeSegmentIndex() operation for the
627
+ // old stream when the update has finished.
628
+ if (!this.deferredCloseSegmentIndex_.has(oldStreamTag)) {
629
+ this.deferredCloseSegmentIndex_.set(
630
+ oldStreamTag, [mediaState.stream.segmentIndex]);
631
+ } else {
632
+ // We switched from stream A -> B -> back to A.
633
+ this.deferredCloseSegmentIndex_.get(oldStreamTag).push(
634
+ mediaState.stream.segmentIndex);
635
+ }
636
+ } else {
637
+ mediaState.stream.closeSegmentIndex();
638
+ }
580
639
  }
581
640
  }
582
641
 
@@ -584,8 +643,8 @@ shaka.media.StreamingEngine = class {
584
643
  mediaState.segmentIterator = null;
585
644
  mediaState.adaptation = !!adaptation;
586
645
 
587
- const streamTag = shaka.media.StreamingEngine.logPrefix_(mediaState);
588
- shaka.log.debug('switch: switching to Stream ' + streamTag);
646
+ const newStreamTag = shaka.media.StreamingEngine.logPrefix_(mediaState);
647
+ shaka.log.debug('switch: switching to Stream ' + newStreamTag);
589
648
 
590
649
  if (clearBuffer) {
591
650
  if (mediaState.clearingBuffer) {
@@ -1169,6 +1228,8 @@ shaka.media.StreamingEngine = class {
1169
1228
  return;
1170
1229
  }
1171
1230
 
1231
+ this.handleDeferredCloseSegmentIndexes_(mediaState);
1232
+
1172
1233
  // Make sure the segment index exists. If not, create the segment index.
1173
1234
  if (!mediaState.stream.segmentIndex) {
1174
1235
  const thisStream = mediaState.stream;
@@ -1343,6 +1404,8 @@ shaka.media.StreamingEngine = class {
1343
1404
  return this.config_.updateIntervalSeconds / 2;
1344
1405
  }
1345
1406
 
1407
+ // Lack of segment iterator is the best indicator stream has changed.
1408
+ const streamChanged = !mediaState.segmentIterator;
1346
1409
  const reference = this.getSegmentReferenceNeeded_(
1347
1410
  mediaState, presentationTime, bufferEnd);
1348
1411
  if (!reference) {
@@ -1352,6 +1415,14 @@ shaka.media.StreamingEngine = class {
1352
1415
  // a SegmentReference eventually.
1353
1416
  return this.config_.updateIntervalSeconds;
1354
1417
  }
1418
+ // Get media state adaptation and reset this value. By guarding it during
1419
+ // actual stream change we ensure it won't be cleaned by accident on regular
1420
+ // append.
1421
+ let adaptation = false;
1422
+ if (streamChanged && mediaState.adaptation) {
1423
+ adaptation = true;
1424
+ mediaState.adaptation = false;
1425
+ }
1355
1426
 
1356
1427
  // Do not let any one stream get far ahead of any other.
1357
1428
  let minTimeNeeded = Infinity;
@@ -1390,7 +1461,8 @@ shaka.media.StreamingEngine = class {
1390
1461
  mediaState.segmentPrefetch.prefetchSegmentsByTime(reference.startTime);
1391
1462
  }
1392
1463
 
1393
- const p = this.fetchAndAppend_(mediaState, presentationTime, reference);
1464
+ const p = this.fetchAndAppend_(mediaState, presentationTime, reference,
1465
+ adaptation);
1394
1466
  p.catch(() => {}); // TODO(#1993): Handle asynchronous errors.
1395
1467
  return null;
1396
1468
  }
@@ -1512,9 +1584,10 @@ shaka.media.StreamingEngine = class {
1512
1584
  * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
1513
1585
  * @param {number} presentationTime
1514
1586
  * @param {!shaka.media.SegmentReference} reference
1587
+ * @param {boolean} adaptation
1515
1588
  * @private
1516
1589
  */
1517
- async fetchAndAppend_(mediaState, presentationTime, reference) {
1590
+ async fetchAndAppend_(mediaState, presentationTime, reference, adaptation) {
1518
1591
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1519
1592
  const StreamingEngine = shaka.media.StreamingEngine;
1520
1593
  const logPrefix = StreamingEngine.logPrefix_(mediaState);
@@ -1543,7 +1616,7 @@ shaka.media.StreamingEngine = class {
1543
1616
  shaka.util.Error.Category.NETWORK,
1544
1617
  shaka.util.Error.Code.SEGMENT_MISSING);
1545
1618
  }
1546
- await this.initSourceBuffer_(mediaState, reference);
1619
+ await this.initSourceBuffer_(mediaState, reference, adaptation);
1547
1620
  this.destroyer_.ensureNotDestroyed();
1548
1621
  if (this.fatalError_) {
1549
1622
  return;
@@ -1598,7 +1671,7 @@ shaka.media.StreamingEngine = class {
1598
1671
  remaining = remaining.subarray(offset);
1599
1672
  await this.append_(
1600
1673
  mediaState, presentationTime, stream, reference, dataToAppend,
1601
- /* isChunkedData= */ true);
1674
+ /* isChunkedData= */ true, adaptation);
1602
1675
 
1603
1676
  if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1604
1677
  mediaState.segmentPrefetch.prefetchSegmentsByTime(
@@ -1637,8 +1710,8 @@ shaka.media.StreamingEngine = class {
1637
1710
  return;
1638
1711
  }
1639
1712
 
1640
- await this.append_(
1641
- mediaState, presentationTime, stream, reference, result);
1713
+ await this.append_(mediaState, presentationTime, stream, reference,
1714
+ result, /* chunkedData= */ false, adaptation);
1642
1715
  }
1643
1716
 
1644
1717
  if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
@@ -1669,8 +1742,8 @@ shaka.media.StreamingEngine = class {
1669
1742
  return;
1670
1743
  }
1671
1744
 
1672
- await this.append_(
1673
- mediaState, presentationTime, stream, reference, result);
1745
+ await this.append_(mediaState, presentationTime, stream, reference,
1746
+ result, /* chunkedData= */ false, adaptation);
1674
1747
  }
1675
1748
 
1676
1749
  this.destroyer_.ensureNotDestroyed();
@@ -1911,10 +1984,11 @@ shaka.media.StreamingEngine = class {
1911
1984
  *
1912
1985
  * @param {shaka.media.StreamingEngine.MediaState_} mediaState
1913
1986
  * @param {!shaka.media.SegmentReference} reference
1987
+ * @param {boolean} adaptation
1914
1988
  * @return {!Promise}
1915
1989
  * @private
1916
1990
  */
1917
- async initSourceBuffer_(mediaState, reference) {
1991
+ async initSourceBuffer_(mediaState, reference, adaptation) {
1918
1992
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1919
1993
  const MimeUtils = shaka.util.MimeUtils;
1920
1994
  const StreamingEngine = shaka.media.StreamingEngine;
@@ -2109,7 +2183,8 @@ shaka.media.StreamingEngine = class {
2109
2183
  mediaState.type, initSegment);
2110
2184
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
2111
2185
  mediaState.type, initSegment, /* reference= */ null,
2112
- mediaState.stream, hasClosedCaptions);
2186
+ mediaState.stream, hasClosedCaptions, mediaState.seeked,
2187
+ adaptation);
2113
2188
  } catch (error) {
2114
2189
  mediaState.lastInitSegmentReference = null;
2115
2190
  throw error;
@@ -2147,11 +2222,12 @@ shaka.media.StreamingEngine = class {
2147
2222
  * @param {!shaka.media.SegmentReference} reference
2148
2223
  * @param {BufferSource} segment
2149
2224
  * @param {boolean=} isChunkedData
2225
+ * @param {boolean=} adaptation
2150
2226
  * @return {!Promise}
2151
2227
  * @private
2152
2228
  */
2153
2229
  async append_(mediaState, presentationTime, stream, reference, segment,
2154
- isChunkedData = false) {
2230
+ isChunkedData = false, adaptation = false) {
2155
2231
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
2156
2232
 
2157
2233
  const hasClosedCaptions = stream.closedCaptions &&
@@ -2195,8 +2271,6 @@ shaka.media.StreamingEngine = class {
2195
2271
  // appendBuffer() call.
2196
2272
  const seeked = mediaState.seeked;
2197
2273
  mediaState.seeked = false;
2198
- const adaptation = mediaState.adaptation;
2199
- mediaState.adaptation = false;
2200
2274
 
2201
2275
  await this.playerInterface_.beforeAppendSegment(mediaState.type, segment);
2202
2276
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
@@ -275,7 +275,8 @@ shaka.media.VideoWrapper.PlayheadMover = class {
275
275
  }
276
276
 
277
277
  // Yay! We were successful.
278
- if (this.mediaElement_.currentTime != this.originTime_) {
278
+ if (this.mediaElement_.currentTime != this.originTime_ ||
279
+ this.mediaElement_.currentTime === this.targetTime_) {
279
280
  this.timer_.stop();
280
281
  return;
281
282
  }
package/lib/player.js CHANGED
@@ -7484,7 +7484,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7484
7484
  * @export
7485
7485
  */
7486
7486
  // eslint-disable-next-line no-useless-concat
7487
- shaka.Player.version = 'v4.9.21' + '-uncompiled'; // x-release-please-version
7487
+ shaka.Player.version = 'v4.9.22' + '-uncompiled'; // x-release-please-version
7488
7488
 
7489
7489
  // Initialize the deprecation system using the version string we just set
7490
7490
  // on the player.
@@ -471,8 +471,8 @@ shaka.text.UITextDisplayer = class {
471
471
  const linesUnit = shaka.text.CueRegion.units.LINES;
472
472
  const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
473
473
  const pixelUnit = shaka.text.CueRegion.units.PX;
474
- const heightUnit = region.heightUnits == percentageUnit ? '%' : 'px';
475
- const widthUnit = region.widthUnits == percentageUnit ? '%' : 'px';
474
+ let heightUnit = region.heightUnits == percentageUnit ? '%' : 'px';
475
+ let widthUnit = region.widthUnits == percentageUnit ? '%' : 'px';
476
476
  const viewportAnchorUnit =
477
477
  region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
478
478
 
@@ -481,16 +481,19 @@ shaka.text.UITextDisplayer = class {
481
481
 
482
482
  regionElement.style.position = 'absolute';
483
483
 
484
+ let regionHeight = region.height;
485
+ let regionWidth = region.width;
486
+
484
487
  if (region.heightUnits === linesUnit) {
485
- regionElement.style.height = region.height * lineHeightMultiple + '%';
486
- } else {
487
- regionElement.style.height = region.height + heightUnit;
488
+ regionHeight = region.height * lineHeightMultiple;
489
+ heightUnit = '%';
488
490
  }
489
491
  if (region.widthUnits === linesUnit) {
490
- regionElement.style.width = region.width * lineWidthMultiple + '%';
491
- } else {
492
- regionElement.style.width = region.width + widthUnit;
492
+ regionWidth = region.width * lineWidthMultiple;
493
+ widthUnit = '%';
493
494
  }
495
+ regionElement.style.height = regionHeight + heightUnit;
496
+ regionElement.style.width = regionWidth + widthUnit;
494
497
 
495
498
  if (region.viewportAnchorUnits === linesUnit) {
496
499
  // from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708
@@ -498,15 +501,15 @@ shaka.text.UITextDisplayer = class {
498
501
  const windowWidth = this.aspectRatio_ === 4/3 ? 160 : 210;
499
502
  let left = region.viewportAnchorX / windowWidth * 100;
500
503
  // adjust top and left values based on the region anchor and window size
501
- top -= region.regionAnchorY * region.height * lineHeightMultiple / 100;
502
- left -= region.regionAnchorX * region.width * lineWidthMultiple / 100;
504
+ top -= region.regionAnchorY * regionHeight / 100;
505
+ left -= region.regionAnchorX * regionWidth / 100;
503
506
  regionElement.style.top = top + '%';
504
507
  regionElement.style.left = left + '%';
505
508
  } else {
506
509
  regionElement.style.top = region.viewportAnchorY -
507
- region.regionAnchorY * region.height / 100 + viewportAnchorUnit;
510
+ region.regionAnchorY * regionHeight / 100 + viewportAnchorUnit;
508
511
  regionElement.style.left = region.viewportAnchorX -
509
- region.regionAnchorX * region.width / 100 + viewportAnchorUnit;
512
+ region.regionAnchorX * regionWidth / 100 + viewportAnchorUnit;
510
513
  }
511
514
  if (region.heightUnits !== pixelUnit &&
512
515
  region.widthUnits !== pixelUnit &&
@@ -160,9 +160,17 @@ shaka.text.VttTextParser = class {
160
160
  shaka.util.Error.Code.INVALID_TEXT_HEADER);
161
161
  }
162
162
 
163
- const mpegTime = Number(mpegTimeMatch[1]);
163
+ let mpegTime = Number(mpegTimeMatch[1]);
164
164
  const mpegTimescale = shaka.text.VttTextParser.MPEG_TIMESCALE_;
165
165
 
166
+ const rolloverSeconds =
167
+ shaka.text.VttTextParser.TS_ROLLOVER_ / mpegTimescale;
168
+ let segmentStart = time.segmentStart - time.periodStart;
169
+ while (segmentStart >= rolloverSeconds) {
170
+ segmentStart -= rolloverSeconds;
171
+ mpegTime += shaka.text.VttTextParser.TS_ROLLOVER_;
172
+ }
173
+
166
174
  return time.periodStart + mpegTime / mpegTimescale - cueTime;
167
175
  }
168
176
 
@@ -1007,6 +1015,13 @@ shaka.text.VttTextParser = class {
1007
1015
  */
1008
1016
  shaka.text.VttTextParser.MPEG_TIMESCALE_ = 90000;
1009
1017
 
1018
+ /**
1019
+ * At this value, timestamps roll over in TS content.
1020
+ * @const {number}
1021
+ * @private
1022
+ */
1023
+ shaka.text.VttTextParser.TS_ROLLOVER_ = 0x200000000;
1024
+
1010
1025
  /**
1011
1026
  * @const
1012
1027
  * @private {!RegExp}
@@ -749,10 +749,21 @@ shaka.util.Platform = class {
749
749
  maxResolution.width = 1920;
750
750
  maxResolution.height = 1080;
751
751
  try {
752
+ let winRT = undefined;
753
+
754
+ // Try to access to WinRT for WebView, if it's not defined,
755
+ // try to access to WinRT for WebView2, if it's not defined either,
756
+ // let it throw.
757
+ if (typeof Windows !== 'undefined') {
758
+ winRT = Windows;
759
+ } else {
760
+ winRT = chrome.webview.hostObjects.sync.Windows;
761
+ }
762
+
752
763
  const protectionCapabilities =
753
- new Windows.Media.Protection.ProtectionCapabilities();
764
+ new winRT.Media.Protection.ProtectionCapabilities();
754
765
  const protectionResult =
755
- Windows.Media.Protection.ProtectionCapabilityResult;
766
+ winRT.Media.Protection.ProtectionCapabilityResult;
756
767
  // isTypeSupported may return "maybe", which means the operation is not
757
768
  // completed. This means we need to retry
758
769
  // https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
@@ -374,6 +374,9 @@ shaka.util.StreamUtils = class {
374
374
  goog.asserts.assert(navigator.mediaCapabilities,
375
375
  'MediaCapabilities should be valid.');
376
376
 
377
+ if (shaka.util.Platform.isXboxOne()) {
378
+ shaka.util.StreamUtils.overrideDolbyVisionCodecs(manifest.variants);
379
+ }
377
380
  await shaka.util.StreamUtils.getDecodingInfosForVariants(
378
381
  manifest.variants, usePersistentLicenses, /* srcEquals= */ false,
379
382
  preferredKeySystems);
@@ -400,6 +403,40 @@ shaka.util.StreamUtils = class {
400
403
  }
401
404
 
402
405
 
406
+ /**
407
+ * Maps Dolby Vision codecs to H.264 and H.265 equivalents as a workaround
408
+ * to make Dolby Vision playback work on some platforms.
409
+ *
410
+ * Mapping is done according to the relevant Dolby documentation found here:
411
+ * https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH?language=en_US
412
+ * @param {!Array<!shaka.extern.Variant>} variants
413
+ */
414
+ static overrideDolbyVisionCodecs(variants) {
415
+ /** @type {!Object<string, string>} */
416
+ const codecMap = {
417
+ 'dvav': 'avc3',
418
+ 'dva1': 'avc1',
419
+ 'dvhe': 'hev1',
420
+ 'dvh1': 'hvc1',
421
+ };
422
+ /** @type {!Set<!shaka.extern.Stream>} */
423
+ const streams = new Set();
424
+ for (const variant of variants) {
425
+ if (variant.video) {
426
+ streams.add(variant.video);
427
+ }
428
+ }
429
+ for (const video of streams) {
430
+ for (const dvCodec of Object.keys(codecMap)) {
431
+ if (video.codecs.includes(dvCodec)) {
432
+ video.codecs = video.codecs.replace(dvCodec, codecMap[dvCodec]);
433
+ break;
434
+ }
435
+ }
436
+ }
437
+ }
438
+
439
+
403
440
  /**
404
441
  * @param {!shaka.extern.Variant} variant
405
442
  * @param {?string} keySystem
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.9.21",
4
+ "version": "4.9.22",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -36,7 +36,7 @@
36
36
  "google-closure-library": "^20221102.0.0",
37
37
  "htmlhint": "^1.1.3",
38
38
  "jasmine-ajax": "^4.0.0",
39
- "jimp": "^0.16.1",
39
+ "jimp": "^0.22.12",
40
40
  "js-yaml": "^4.1.0",
41
41
  "jsdoc": "github:joeyparrish/jsdoc#2ca85bb6",
42
42
  "karma": "github:joeyparrish/karma#shaka-fixes",
@@ -50,7 +50,7 @@
50
50
  "karma-webdriver-launcher": "^1.0.8",
51
51
  "lcevc_dec.js": "^1.1.3",
52
52
  "less": "^4.2.0",
53
- "less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
53
+ "less-plugin-clean-css": "^1.6.0",
54
54
  "material-design-lite": "^1.3.0",
55
55
  "open-sans-fonts": "^1.6.2",
56
56
  "postcss-less": "^6.0.0",