shaka-player 4.9.35 → 4.9.36

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 (74) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/README.md +9 -3
  4. package/dist/controls.css +1 -1
  5. package/dist/controls.css.map +1 -1
  6. package/dist/deps.js +5 -5
  7. package/dist/locales.js +36 -36
  8. package/dist/shaka-player.compiled.d.ts +1 -0
  9. package/dist/shaka-player.compiled.debug.d.ts +1 -0
  10. package/dist/shaka-player.compiled.debug.externs.js +4 -3
  11. package/dist/shaka-player.compiled.debug.js +436 -421
  12. package/dist/shaka-player.compiled.debug.map +3 -3
  13. package/dist/shaka-player.compiled.externs.js +4 -3
  14. package/dist/shaka-player.compiled.js +1263 -1251
  15. package/dist/shaka-player.compiled.map +3 -3
  16. package/dist/shaka-player.dash.d.ts +4099 -0
  17. package/dist/shaka-player.dash.debug.d.ts +4099 -0
  18. package/dist/shaka-player.dash.debug.externs.js +4188 -0
  19. package/dist/shaka-player.dash.debug.js +2161 -0
  20. package/dist/shaka-player.dash.debug.map +8 -0
  21. package/dist/shaka-player.dash.externs.js +4188 -0
  22. package/dist/shaka-player.dash.js +1007 -0
  23. package/dist/shaka-player.dash.map +8 -0
  24. package/dist/shaka-player.hls.d.ts +4196 -0
  25. package/dist/shaka-player.hls.debug.d.ts +4196 -0
  26. package/dist/shaka-player.hls.debug.externs.js +4403 -0
  27. package/dist/shaka-player.hls.debug.js +2243 -0
  28. package/dist/shaka-player.hls.debug.map +8 -0
  29. package/dist/shaka-player.hls.externs.js +4403 -0
  30. package/dist/shaka-player.hls.js +1076 -0
  31. package/dist/shaka-player.hls.map +8 -0
  32. package/dist/shaka-player.ui.d.ts +1 -0
  33. package/dist/shaka-player.ui.debug.d.ts +1 -0
  34. package/dist/shaka-player.ui.debug.externs.js +4 -3
  35. package/dist/shaka-player.ui.debug.js +460 -445
  36. package/dist/shaka-player.ui.debug.map +3 -3
  37. package/dist/shaka-player.ui.externs.js +4 -3
  38. package/dist/shaka-player.ui.js +1462 -1453
  39. package/dist/shaka-player.ui.map +3 -3
  40. package/externs/shaka/player.js +8 -4
  41. package/lib/cast/cast_proxy.js +48 -10
  42. package/lib/cea/cea708_service.js +9 -16
  43. package/lib/dash/dash_parser.js +23 -16
  44. package/lib/dash/segment_template.js +41 -40
  45. package/lib/hls/hls_parser.js +2 -2
  46. package/lib/media/content_workarounds.js +110 -72
  47. package/lib/media/drm_engine.js +72 -11
  48. package/lib/media/gap_jumping_controller.js +14 -1
  49. package/lib/media/media_source_engine.js +29 -11
  50. package/lib/media/region_timeline.js +4 -3
  51. package/lib/media/streaming_engine.js +37 -11
  52. package/lib/net/http_plugin_utils.js +1 -1
  53. package/lib/net/networking_engine.js +8 -0
  54. package/lib/net/networking_utils.js +1 -0
  55. package/lib/player.js +109 -37
  56. package/lib/polyfill/patchedmediakeys_apple.js +21 -0
  57. package/lib/text/cue.js +6 -2
  58. package/lib/text/mp4_ttml_parser.js +94 -6
  59. package/lib/text/ui_text_displayer.js +35 -4
  60. package/lib/transmuxer/ac3_transmuxer.js +6 -1
  61. package/lib/util/config_utils.js +13 -3
  62. package/lib/util/drm_utils.js +8 -0
  63. package/lib/util/error.js +6 -0
  64. package/lib/util/mp4_generator.js +19 -4
  65. package/lib/util/periods.js +4 -2
  66. package/lib/util/platform.js +60 -12
  67. package/lib/util/player_configuration.js +6 -1
  68. package/lib/util/stream_utils.js +18 -0
  69. package/lib/util/tXml.js +3 -2
  70. package/package.json +3 -3
  71. package/ui/controls.js +2 -6
  72. package/ui/less/thumbnails.less +1 -0
  73. package/ui/statistics_button.js +3 -1
  74. package/ui/ui.js +44 -35
@@ -1204,7 +1204,8 @@ shaka.extern.MssManifestConfiguration;
1204
1204
  * A default <code>presentationDelay</code> value.
1205
1205
  * For DASH, it's a default <code>presentationDelay</code> value if
1206
1206
  * <code>suggestedPresentationDelay</code> is missing in the MPEG DASH
1207
- * manifest. The default value is <code>1.5 * minBufferTime</code> if not
1207
+ * manifest. The default value is the lower of <code>1.5 *
1208
+ * minBufferTime</code> and <code>segmentAvailabilityDuration</code> if not
1208
1209
  * configured or set as 0.
1209
1210
  * For HLS, the default value is 3 segments duration if not configured or
1210
1211
  * set as 0.
@@ -1483,9 +1484,12 @@ shaka.extern.ManifestConfiguration;
1483
1484
  * <code>vodDynamicPlaybackRateLowBufferRate</code>.
1484
1485
  * Defaults to <code>0.5</code>.
1485
1486
  * @property {boolean} infiniteLiveStreamDuration
1486
- * If <code>true</code>, the media source live duration
1487
- * set as a<code>Infinity</code>
1488
- * Defaults to <code> false </code>.
1487
+ * If <code>true</code>, the media source duration of livestreams will be set
1488
+ * to <code>Infinity</code>. Otherwise, it will be set to a high but
1489
+ * non-infinite number (2^32).
1490
+ * <br>
1491
+ * Defaults to <code>false</code> except on Safari 17 or above whose default
1492
+ * value is <code>true</code>.
1489
1493
  * @property {number} preloadNextUrlWindow
1490
1494
  * The window of time at the end of the presentation to begin preloading the
1491
1495
  * next URL, such as one specified by a urn:mpeg:dash:chaining:2016 element
@@ -81,17 +81,21 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
81
81
  this.compiledToExternNames_ = new Map();
82
82
 
83
83
  /** @private {shaka.cast.CastSender} */
84
- this.sender_ = new shaka.cast.CastSender(
85
- receiverAppId,
86
- () => this.onCastStatusChanged_(),
87
- () => this.onFirstCastStateUpdate_(),
88
- (targetName, event) => this.onRemoteEvent_(targetName, event),
89
- () => this.onResumeLocal_(),
90
- () => this.getInitState_(),
91
- androidReceiverCompatible);
92
-
84
+ this.sender_ = null;
93
85
 
94
- this.init_();
86
+ if (window.chrome) {
87
+ this.sender_ = new shaka.cast.CastSender(
88
+ receiverAppId,
89
+ () => this.onCastStatusChanged_(),
90
+ () => this.onFirstCastStateUpdate_(),
91
+ (targetName, event) => this.onRemoteEvent_(targetName, event),
92
+ () => this.onResumeLocal_(),
93
+ () => this.getInitState_(),
94
+ androidReceiverCompatible);
95
+ this.init_();
96
+ } else {
97
+ this.initWithoutSender_();
98
+ }
95
99
  }
96
100
 
97
101
  /**
@@ -163,6 +167,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
163
167
  * @export
164
168
  */
165
169
  canCast() {
170
+ if (!this.sender_) {
171
+ return false;
172
+ }
166
173
  return this.sender_.apiReady() && this.sender_.hasReceivers();
167
174
  }
168
175
 
@@ -171,6 +178,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
171
178
  * @export
172
179
  */
173
180
  isCasting() {
181
+ if (!this.sender_) {
182
+ return false;
183
+ }
174
184
  return this.sender_.isCasting();
175
185
  }
176
186
 
@@ -179,6 +189,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
179
189
  * @export
180
190
  */
181
191
  receiverName() {
192
+ if (!this.sender_) {
193
+ return '';
194
+ }
182
195
  return this.sender_.receiverName();
183
196
  }
184
197
 
@@ -188,6 +201,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
188
201
  * @export
189
202
  */
190
203
  async cast() {
204
+ if (!this.sender_) {
205
+ return;
206
+ }
191
207
  // TODO: transfer manually-selected tracks?
192
208
  // TODO: transfer side-loaded text tracks?
193
209
 
@@ -208,6 +224,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
208
224
  * @export
209
225
  */
210
226
  setAppData(appData) {
227
+ if (!this.sender_) {
228
+ return;
229
+ }
211
230
  this.sender_.setAppData(appData);
212
231
  }
213
232
 
@@ -216,6 +235,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
216
235
  * @export
217
236
  */
218
237
  suggestDisconnect() {
238
+ if (!this.sender_) {
239
+ return;
240
+ }
219
241
  this.sender_.showDisconnectDialog();
220
242
  }
221
243
 
@@ -224,6 +246,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
224
246
  * @export
225
247
  */
226
248
  forceDisconnect() {
249
+ if (!this.sender_) {
250
+ return;
251
+ }
227
252
  this.sender_.forceDisconnect();
228
253
  }
229
254
 
@@ -243,6 +268,10 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
243
268
  this.receiverAppId_ = newAppId;
244
269
  this.androidReceiverCompatible_ = newCastAndroidReceiver;
245
270
 
271
+ if (!this.sender_) {
272
+ return;
273
+ }
274
+
246
275
  // Destroy the old sender
247
276
  this.sender_.forceDisconnect();
248
277
  await this.sender_.destroy();
@@ -262,6 +291,15 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
262
291
  this.sender_.init();
263
292
  }
264
293
 
294
+ /**
295
+ * Initialize the Proxies without Cast sender.
296
+ * @private
297
+ */
298
+ initWithoutSender_() {
299
+ this.videoProxy_ = /** @type {Object} */(this.localVideo_);
300
+ this.playerProxy_ = /** @type {Object} */(this.localPlayer_);
301
+ }
302
+
265
303
  /**
266
304
  * Initialize the Proxies and the Cast sender.
267
305
  * @private
@@ -169,24 +169,17 @@ shaka.cea.Cea708Service = class {
169
169
  const firstByte = dtvccPacket.readByte().value;
170
170
  const secondByte = dtvccPacket.readByte().value;
171
171
 
172
- const isTextBlock = (b) => {
173
- return (b >= 0x20 && b <= 0x7f) || (b >= 0xa0 && b <= 0xff);
172
+ const toHexString = (byteArray) => {
173
+ return byteArray.map((byte) => {
174
+ return ('0' + (byte & 0xFF).toString(16)).slice(-2);
175
+ }).join('');
174
176
  };
175
177
 
176
- if (isTextBlock(firstByte) && isTextBlock(secondByte)) {
177
- const toHexString = (byteArray) => {
178
- return byteArray.map((byte) => {
179
- return ('0' + (byte & 0xFF).toString(16)).slice(-2);
180
- }).join('');
181
- };
182
- const unicode = toHexString([firstByte, secondByte]);
183
- // Takes a unicode hex string and creates a single character.
184
- const char = String.fromCharCode(parseInt(unicode, 16));
185
- this.currentWindow_.setCharacter(char);
186
- return null;
187
- } else {
188
- dtvccPacket.rewind(2);
189
- }
178
+ const unicode = toHexString([firstByte, secondByte]);
179
+ // Takes a unicode hex string and creates a single character.
180
+ const char = String.fromCharCode(parseInt(unicode, 16));
181
+ this.currentWindow_.setCharacter(char);
182
+ return null;
190
183
  }
191
184
 
192
185
  const window = this.currentWindow_;
@@ -658,18 +658,28 @@ shaka.dash.DashParser = class {
658
658
  // feasible choices for the value. Nothing older than
659
659
  // timeShiftBufferDepth is still available, and anything less than
660
660
  // minBufferTime will cause buffering issues.
661
- //
662
- // We have decided that our default will be the configured value, or
663
- // 1.5 * minBufferTime if not configured. This is fairly conservative.
664
- // Content providers should provide a suggestedPresentationDelay whenever
665
- // possible to optimize the live streaming experience.
666
- const defaultPresentationDelay =
667
- this.config_.defaultPresentationDelay || minBufferTime * 1.5;
668
- const presentationDelay = suggestedPresentationDelay != null ?
669
- suggestedPresentationDelay : defaultPresentationDelay;
661
+ let delay = 0;
662
+ if (suggestedPresentationDelay != null) {
663
+ // 1. If a suggestedPresentationDelay is provided by the manifest, that
664
+ // will be used preferentially.
665
+ // This is given a minimum bound of segmentAvailabilityDuration.
666
+ // Content providers should provide a suggestedPresentationDelay
667
+ // whenever possible to optimize the live streaming experience.
668
+ delay = Math.min(
669
+ suggestedPresentationDelay,
670
+ segmentAvailabilityDuration || Infinity);
671
+ } else if (this.config_.defaultPresentationDelay > 0) {
672
+ // 2. If the developer provides a value for
673
+ // "manifest.defaultPresentationDelay", that is used as a fallback.
674
+ delay = this.config_.defaultPresentationDelay;
675
+ } else {
676
+ // 3. Otherwise, we default to the lower of segmentAvailabilityDuration
677
+ // and 1.5 * minBufferTime. This is fairly conservative.
678
+ delay = Math.min(
679
+ minBufferTime * 1.5, segmentAvailabilityDuration || Infinity);
680
+ }
670
681
  presentationTimeline = new shaka.media.PresentationTimeline(
671
- presentationStartTime, presentationDelay,
672
- this.config_.dash.autoCorrectDrift);
682
+ presentationStartTime, delay, this.config_.dash.autoCorrectDrift);
673
683
  }
674
684
 
675
685
  presentationTimeline.setStatic(mpdType == 'static');
@@ -2123,12 +2133,8 @@ shaka.dash.DashParser = class {
2123
2133
  return element.attributes['schemeIdUri'] == expectedUri &&
2124
2134
  element.attributes['value'] == expectedValue;
2125
2135
  });
2126
- const codecs = context.representation.codecs;
2127
- // Detect the presence of Dolby Atmos audio content based on the codec and
2128
- // bandwidth. Bandwidth above 384 kbps is a good indicator of Atmos content.
2129
- const isAtmos = codecs.includes('ec-3') && context.bandwidth >= 384000;
2130
2136
  let spatialAudio = false;
2131
- if (hasJoc || isAtmos) {
2137
+ if (hasJoc) {
2132
2138
  spatialAudio = true;
2133
2139
  }
2134
2140
 
@@ -2162,6 +2168,7 @@ shaka.dash.DashParser = class {
2162
2168
 
2163
2169
  let hdr;
2164
2170
  const profiles = context.profiles;
2171
+ const codecs = context.representation.codecs;
2165
2172
 
2166
2173
  const hevcHDR = 'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
2167
2174
  if (profiles.includes(hevcHDR) && (codecs.includes('hvc1.2.4.L153.B0') ||
@@ -121,24 +121,6 @@ shaka.dash.SegmentTemplate = class {
121
121
 
122
122
  shaka.log.debug(`New manifest ${periodStart} - ${periodEnd}`);
123
123
 
124
- /* When to fit segments. All refactors should honor/update this table:
125
- *
126
- * | dynamic | infinite | last | should | notes |
127
- * | | period | period | fit | |
128
- * | ------- | -------- | ------ | ------ | ------------------------- |
129
- * | F | F | X | T | typical VOD |
130
- * | F | T | X | X | impossible: infinite VOD |
131
- * | T | F | F | T | typical live, old period |
132
- * | T | F | T | F | typical IPR |
133
- * | T | T | F | X | impossible: old, infinite |
134
- * | T | T | T | F | typical live, new period |
135
- */
136
-
137
- // We never fit the final period of dynamic content, which could be
138
- // infinite live (with no limit to fit to) or IPR (which would expand the
139
- // most recent segment to the end of the presentation).
140
- const shouldFit = !(context.dynamic && context.periodInfo.isLastPeriod);
141
-
142
124
  if (!segmentIndex) {
143
125
  shaka.log.debug(`Creating TSI with end ${periodEnd}`);
144
126
  segmentIndex = new TimelineSegmentIndex(
@@ -149,14 +131,13 @@ shaka.dash.SegmentTemplate = class {
149
131
  periodStart,
150
132
  periodEnd,
151
133
  initSegmentReference,
152
- shouldFit,
153
134
  aesKey,
154
135
  context.representation.segmentSequenceCadence,
155
136
  );
156
137
  } else {
157
138
  const tsi = /** @type {!TimelineSegmentIndex} */(segmentIndex);
158
139
  tsi.appendTemplateInfo(
159
- info, periodStart, periodEnd, shouldFit, initSegmentReference);
140
+ info, periodStart, periodEnd, initSegmentReference);
160
141
 
161
142
  const availabilityStart =
162
143
  context.presentationTimeline.getSegmentAvailabilityStart();
@@ -166,7 +147,9 @@ shaka.dash.SegmentTemplate = class {
166
147
  if (info.timeline &&
167
148
  context.adaptationSet.contentType !== 'image' &&
168
149
  context.adaptationSet.contentType !== 'text') {
169
- const timeline = info.timeline;
150
+ const tsi = /** @type {!TimelineSegmentIndex} */(segmentIndex);
151
+ // getTimeline is the info.timeline but fitted to the period.
152
+ const timeline = tsi.getTimeline();
170
153
  context.presentationTimeline.notifyTimeRange(
171
154
  timeline,
172
155
  periodStart);
@@ -184,7 +167,7 @@ shaka.dash.SegmentTemplate = class {
184
167
  if (segmentIndex instanceof shaka.dash.TimelineSegmentIndex &&
185
168
  segmentIndex.isEmpty()) {
186
169
  segmentIndex.appendTemplateInfo(info, periodStart,
187
- periodEnd, shouldFit, initSegmentReference);
170
+ periodEnd, initSegmentReference);
188
171
  }
189
172
  return Promise.resolve(segmentIndex);
190
173
  },
@@ -668,38 +651,42 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
668
651
  * @param {number} periodStart
669
652
  * @param {number} periodEnd
670
653
  * @param {shaka.media.InitSegmentReference} initSegmentReference
671
- * @param {boolean} shouldFit
672
654
  * @param {shaka.extern.aesKey|undefined} aesKey
673
655
  * @param {number} segmentSequenceCadence
674
656
  */
675
657
  constructor(templateInfo, representationId, bandwidth, getBaseUris,
676
- periodStart, periodEnd, initSegmentReference, shouldFit,
658
+ periodStart, periodEnd, initSegmentReference,
677
659
  aesKey, segmentSequenceCadence) {
678
660
  super([]);
679
661
 
680
662
  /** @private {?shaka.dash.SegmentTemplate.SegmentTemplateInfo} */
681
663
  this.templateInfo_ = templateInfo;
664
+
682
665
  /** @private {?string} */
683
666
  this.representationId_ = representationId;
667
+
684
668
  /** @private {number} */
685
669
  this.bandwidth_ = bandwidth;
686
- /** @private {function():Array.<string>} */
670
+
671
+ /** @private {function(): Array<string>} */
687
672
  this.getBaseUris_ = getBaseUris;
673
+
688
674
  /** @private {number} */
689
675
  this.periodStart_ = periodStart;
676
+
690
677
  /** @private {number} */
691
678
  this.periodEnd_ = periodEnd;
679
+
692
680
  /** @private {shaka.media.InitSegmentReference} */
693
681
  this.initSegmentReference_ = initSegmentReference;
682
+
694
683
  /** @private {shaka.extern.aesKey|undefined} */
695
684
  this.aesKey_ = aesKey;
685
+
696
686
  /** @private {number} */
697
687
  this.segmentSequenceCadence_ = segmentSequenceCadence;
698
688
 
699
-
700
- if (shouldFit) {
701
- this.fitTimeline();
702
- }
689
+ this.fitTimeline();
703
690
  }
704
691
 
705
692
  /**
@@ -767,11 +754,9 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
767
754
  * @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info
768
755
  * @param {number} periodStart
769
756
  * @param {number} periodEnd
770
- * @param {boolean} shouldFit
771
757
  * @param {shaka.media.InitSegmentReference} initSegmentReference
772
758
  */
773
- appendTemplateInfo(info, periodStart, periodEnd, shouldFit,
774
- initSegmentReference) {
759
+ appendTemplateInfo(info, periodStart, periodEnd, initSegmentReference) {
775
760
  this.updateInitSegmentReference(initSegmentReference);
776
761
  if (!this.templateInfo_) {
777
762
  this.templateInfo_ = info;
@@ -805,9 +790,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
805
790
  }
806
791
  }
807
792
 
808
- if (shouldFit) {
809
- this.fitTimeline();
810
- }
793
+ this.fitTimeline();
811
794
  }
812
795
 
813
796
  /**
@@ -852,14 +835,19 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
852
835
  return;
853
836
  }
854
837
  const timeline = this.templateInfo_.timeline;
855
- while (timeline.length) {
856
- const lastTimePeriod = timeline[timeline.length - 1];
857
- if (lastTimePeriod.start >= this.periodEnd_) {
858
- timeline.pop();
838
+ goog.asserts.assert(timeline, 'Timeline should be non-null!');
839
+ const newTimeline = [];
840
+ for (const range of timeline) {
841
+ if (range.start >= this.periodEnd_) {
842
+ // Starts after end of period.
843
+ } else if (range.end <= 0) {
844
+ // Ends before start of period.
859
845
  } else {
860
- break;
846
+ // Usable.
847
+ newTimeline.push(range);
861
848
  }
862
849
  }
850
+ this.templateInfo_.timeline = newTimeline;
863
851
 
864
852
  this.evict(this.periodStart_);
865
853
 
@@ -869,6 +857,19 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
869
857
  // find/get() methods whenever possible.
870
858
  }
871
859
 
860
+ /**
861
+ * Get the current timeline
862
+ * @return {!Array<shaka.media.PresentationTimeline.TimeRange>}
863
+ */
864
+ getTimeline() {
865
+ if (!this.templateInfo_) {
866
+ return [];
867
+ }
868
+ const timeline = this.templateInfo_.timeline;
869
+ goog.asserts.assert(timeline, 'Timeline should be non-null!');
870
+ return timeline;
871
+ }
872
+
872
873
  /**
873
874
  * @override
874
875
  */
@@ -4312,7 +4312,7 @@ shaka.hls.HlsParser = class {
4312
4312
  shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
4313
4313
  }
4314
4314
 
4315
- if (shaka.util.Platform.isMediaKeysPolyfilled()) {
4315
+ if (shaka.util.Platform.isMediaKeysPolyfilled('apple')) {
4316
4316
  throw new shaka.util.Error(
4317
4317
  shaka.util.Error.Severity.CRITICAL,
4318
4318
  shaka.util.Error.Category.MANIFEST,
@@ -4452,7 +4452,7 @@ shaka.hls.HlsParser = class {
4452
4452
  shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
4453
4453
  }
4454
4454
 
4455
- if (shaka.util.Platform.isMediaKeysPolyfilled()) {
4455
+ if (shaka.util.Platform.isMediaKeysPolyfilled('apple')) {
4456
4456
  throw new shaka.util.Error(
4457
4457
  shaka.util.Error.Severity.CRITICAL,
4458
4458
  shaka.util.Error.Category.MANIFEST,
@@ -58,6 +58,20 @@ shaka.media.ContentWorkarounds = class {
58
58
  /** @type {!Array.<{box: shaka.extern.ParsedBox, newType: number}>} */
59
59
  const boxesToModify = [];
60
60
 
61
+ const pushEncv = (box) => {
62
+ boxesToModify.push({
63
+ box,
64
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
65
+ });
66
+ };
67
+
68
+ const pushEnca = (box) => {
69
+ boxesToModify.push({
70
+ box,
71
+ newType: ContentWorkarounds.BOX_TYPE_ENCA_,
72
+ });
73
+ };
74
+
61
75
  new shaka.util.Mp4Parser()
62
76
  .box('moov', onSimpleAncestorBox)
63
77
  .box('trak', onSimpleAncestorBox)
@@ -71,78 +85,23 @@ shaka.media.ContentWorkarounds = class {
71
85
  })
72
86
  .fullBox('encv', onEncryptionMetadataBox)
73
87
  .fullBox('enca', onEncryptionMetadataBox)
74
- .fullBox('dvav', (box) => {
75
- boxesToModify.push({
76
- box,
77
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
78
- });
79
- })
80
- .fullBox('dva1', (box) => {
81
- boxesToModify.push({
82
- box,
83
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
84
- });
85
- })
86
- .fullBox('dvh1', (box) => {
87
- boxesToModify.push({
88
- box,
89
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
90
- });
91
- })
92
- .fullBox('dvhe', (box) => {
93
- boxesToModify.push({
94
- box,
95
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
96
- });
97
- })
98
- .fullBox('hev1', (box) => {
99
- boxesToModify.push({
100
- box,
101
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
102
- });
103
- })
104
- .fullBox('hvc1', (box) => {
105
- boxesToModify.push({
106
- box,
107
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
108
- });
109
- })
110
- .fullBox('avc1', (box) => {
111
- boxesToModify.push({
112
- box,
113
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
114
- });
115
- })
116
- .fullBox('avc3', (box) => {
117
- boxesToModify.push({
118
- box,
119
- newType: ContentWorkarounds.BOX_TYPE_ENCV_,
120
- });
121
- })
122
- .fullBox('ac-3', (box) => {
123
- boxesToModify.push({
124
- box,
125
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
126
- });
127
- })
128
- .fullBox('ec-3', (box) => {
129
- boxesToModify.push({
130
- box,
131
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
132
- });
133
- })
134
- .fullBox('ac-4', (box) => {
135
- boxesToModify.push({
136
- box,
137
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
138
- });
139
- })
140
- .fullBox('mp4a', (box) => {
141
- boxesToModify.push({
142
- box,
143
- newType: ContentWorkarounds.BOX_TYPE_ENCA_,
144
- });
145
- }).parse(initSegment);
88
+ .fullBox('dvav', pushEncv)
89
+ .fullBox('dva1', pushEncv)
90
+ .fullBox('dvh1', pushEncv)
91
+ .fullBox('dvhe', pushEncv)
92
+ .fullBox('dvc1', pushEncv)
93
+ .fullBox('dvi1', pushEncv)
94
+ .fullBox('hev1', pushEncv)
95
+ .fullBox('hvc1', pushEncv)
96
+ .fullBox('avc1', pushEncv)
97
+ .fullBox('avc3', pushEncv)
98
+ .fullBox('ac-3', pushEnca)
99
+ .fullBox('ec-3', pushEnca)
100
+ .fullBox('ac-4', pushEnca)
101
+ .fullBox('Opus', pushEnca)
102
+ .fullBox('fLaC', pushEnca)
103
+ .fullBox('mp4a', pushEnca)
104
+ .parse(initSegment);
146
105
 
147
106
  if (isEncrypted) {
148
107
  shaka.log.debug('Init segment already indicates encryption.');
@@ -335,6 +294,53 @@ shaka.media.ContentWorkarounds = class {
335
294
  boxView.setUint32(ContentWorkarounds.BOX_SIZE_OFFSET_, newBoxSize);
336
295
  }
337
296
  }
297
+
298
+ /**
299
+ * Transform the init segment into a new init segment buffer that indicates
300
+ * EC-3 as audio codec instead of AC-3. Even though any EC-3 decoder should
301
+ * be able to decode AC-3 streams, there are platforms that do not accept
302
+ * AC-3 as codec.
303
+ *
304
+ * Should only be called for MP4 init segments, and only on platforms that
305
+ * need this workaround. Returns a new buffer containing the modified init
306
+ * segment.
307
+ *
308
+ * @param {!BufferSource} initSegmentBuffer
309
+ * @return {!Uint8Array}
310
+ */
311
+ static fakeEC3(initSegmentBuffer) {
312
+ const ContentWorkarounds = shaka.media.ContentWorkarounds;
313
+ const initSegment = shaka.util.BufferUtils.toUint8(initSegmentBuffer);
314
+ const ancestorBoxes = [];
315
+
316
+ const onSimpleAncestorBox = (box) => {
317
+ ancestorBoxes.push({start: box.start, size: box.size});
318
+ shaka.util.Mp4Parser.children(box);
319
+ };
320
+
321
+ new shaka.util.Mp4Parser()
322
+ .box('moov', onSimpleAncestorBox)
323
+ .box('trak', onSimpleAncestorBox)
324
+ .box('mdia', onSimpleAncestorBox)
325
+ .box('minf', onSimpleAncestorBox)
326
+ .box('stbl', onSimpleAncestorBox)
327
+ .box('stsd', (box) => {
328
+ ancestorBoxes.push({start: box.start, size: box.size});
329
+ const stsdBoxView = shaka.util.BufferUtils.toDataView(
330
+ initSegment, box.start);
331
+ // "size - 3" is because we immediately read a uint32.
332
+ for (let i = 0; i < box.size -3; i++) {
333
+ const codecTag = stsdBoxView.getUint32(i);
334
+ if (codecTag == ContentWorkarounds.BOX_TYPE_AC_3_) {
335
+ stsdBoxView.setUint32(i, ContentWorkarounds.BOX_TYPE_EC_3_);
336
+ } else if (codecTag == ContentWorkarounds.BOX_TYPE_DAC3_) {
337
+ stsdBoxView.setUint32(i, ContentWorkarounds.BOX_TYPE_DEC3_);
338
+ }
339
+ }
340
+ }).parse(initSegment);
341
+
342
+ return initSegment;
343
+ }
338
344
  };
339
345
 
340
346
  /**
@@ -470,3 +476,35 @@ shaka.media.ContentWorkarounds.BOX_TYPE_ENCV_ = 0x656e6376;
470
476
  * @private
471
477
  */
472
478
  shaka.media.ContentWorkarounds.BOX_TYPE_ENCA_ = 0x656e6361;
479
+
480
+ /**
481
+ * Box type for "ac-3".
482
+ *
483
+ * @const {number}
484
+ * @private
485
+ */
486
+ shaka.media.ContentWorkarounds.BOX_TYPE_AC_3_ = 0x61632d33;
487
+
488
+ /**
489
+ * Box type for "dac3".
490
+ *
491
+ * @const {number}
492
+ * @private
493
+ */
494
+ shaka.media.ContentWorkarounds.BOX_TYPE_DAC3_ = 0x64616333;
495
+
496
+ /**
497
+ * Box type for "ec-3".
498
+ *
499
+ * @const {number}
500
+ * @private
501
+ */
502
+ shaka.media.ContentWorkarounds.BOX_TYPE_EC_3_ = 0x65632d33;
503
+
504
+ /**
505
+ * Box type for "dec3".
506
+ *
507
+ * @const {number}
508
+ * @private
509
+ */
510
+ shaka.media.ContentWorkarounds.BOX_TYPE_DEC3_ = 0x64656333;