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
@@ -25,6 +25,25 @@ goog.require('shaka.util.StringUtils');
25
25
  * @export
26
26
  */
27
27
  shaka.polyfill.PatchedMediaKeysApple = class {
28
+ /**
29
+ * Installs the polyfill if needed.
30
+ */
31
+ static defaultInstall() {
32
+ if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
33
+ // No HTML5 video or no prefixed EME.
34
+ return;
35
+ }
36
+ if (navigator.requestMediaKeySystemAccess &&
37
+ // eslint-disable-next-line no-restricted-syntax
38
+ MediaKeySystemAccess.prototype.getConfiguration) {
39
+ // Unprefixed EME available
40
+ return;
41
+ }
42
+ // If there is no unprefixed EME and prefixed EME exists, apply installation
43
+ // by default. Eg: older versions of Safari.
44
+ shaka.polyfill.PatchedMediaKeysApple.install();
45
+ }
46
+
28
47
  /**
29
48
  * Installs the polyfill if needed.
30
49
  * @param {boolean=} enableUninstall enables uninstalling the polyfill
@@ -798,3 +817,5 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
798
817
  * @private {string}
799
818
  */
800
819
  shaka.polyfill.PatchedMediaKeysApple.apiName_ = 'apple';
820
+
821
+ shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysApple.defaultInstall);
package/lib/text/cue.js CHANGED
@@ -367,8 +367,12 @@ shaka.text.Cue = class {
367
367
  // performance optimization. We can avoid the more expensive recursive
368
368
  // checks if the top-level properties don't match.
369
369
  // See: https://github.com/shaka-project/shaka-player/issues/3018
370
- if (cue1.startTime != cue2.startTime || cue1.endTime != cue2.endTime ||
371
- cue1.payload != cue2.payload) {
370
+ if (cue1.payload != cue2.payload) {
371
+ return false;
372
+ }
373
+ const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.001;
374
+ if (!isDiffNegligible(cue1.startTime, cue2.startTime) ||
375
+ !isDiffNegligible(cue1.endTime, cue2.endTime)) {
372
376
  return false;
373
377
  }
374
378
  for (const k in cue1) {
@@ -6,10 +6,14 @@
6
6
 
7
7
  goog.provide('shaka.text.Mp4TtmlParser');
8
8
 
9
+ goog.require('goog.asserts');
9
10
  goog.require('shaka.text.TextEngine');
10
11
  goog.require('shaka.text.TtmlTextParser');
12
+ goog.require('shaka.util.BufferUtils');
11
13
  goog.require('shaka.util.Error');
14
+ goog.require('shaka.util.Mp4BoxParsers');
12
15
  goog.require('shaka.util.Mp4Parser');
16
+ goog.require('shaka.util.Uint8ArrayUtils');
13
17
 
14
18
 
15
19
  /**
@@ -78,25 +82,109 @@ shaka.text.Mp4TtmlParser = class {
78
82
  parseMedia(data, time, uri) {
79
83
  const Mp4Parser = shaka.util.Mp4Parser;
80
84
 
81
- let sawMDAT = false;
82
85
  let payload = [];
86
+ let defaultSampleSize = null;
87
+
88
+ /** @type {!Array<Uint8Array>} */
89
+ const mdats = [];
90
+
91
+ /* @type {!Map<number,!Array<number>>} */
92
+ const subSampleSizesPerSample = new Map();
93
+
94
+ /** @type {!Array<number>} */
95
+ const sampleSizes = [];
83
96
 
84
97
  const parser = new Mp4Parser()
98
+ .box('moof', Mp4Parser.children)
99
+ .box('traf', Mp4Parser.children)
100
+ .fullBox('tfhd', (box) => {
101
+ goog.asserts.assert(
102
+ box.flags != null,
103
+ 'A TFHD box should have a valid flags value');
104
+ const parsedTFHDBox = shaka.util.Mp4BoxParsers.parseTFHD(
105
+ box.reader, box.flags);
106
+ defaultSampleSize = parsedTFHDBox.defaultSampleSize;
107
+ })
108
+ .fullBox('trun', (box) => {
109
+ goog.asserts.assert(
110
+ box.version != null,
111
+ 'A TRUN box should have a valid version value');
112
+ goog.asserts.assert(
113
+ box.flags != null,
114
+ 'A TRUN box should have a valid flags value');
115
+
116
+ const parsedTRUNBox = shaka.util.Mp4BoxParsers.parseTRUN(
117
+ box.reader, box.version, box.flags);
118
+
119
+ for (const sample of parsedTRUNBox.sampleData) {
120
+ const sampleSize =
121
+ sample.sampleSize || defaultSampleSize || 0;
122
+ sampleSizes.push(sampleSize);
123
+ }
124
+ })
125
+ .fullBox('subs', (box) => {
126
+ const reader = box.reader;
127
+ const entryCount = reader.readUint32();
128
+ let currentSampleNum = -1;
129
+ for (let i = 0; i < entryCount; i++) {
130
+ const sampleDelta = reader.readUint32();
131
+ currentSampleNum += sampleDelta;
132
+ const subsampleCount = reader.readUint16();
133
+ const subsampleSizes = [];
134
+ for (let j = 0; j < subsampleCount; j++) {
135
+ if (box.version == 1) {
136
+ subsampleSizes.push(reader.readUint32());
137
+ } else {
138
+ subsampleSizes.push(reader.readUint16());
139
+ }
140
+ reader.readUint8(); // priority
141
+ reader.readUint8(); // discardable
142
+ reader.readUint32(); // codec_specific_parameters
143
+ }
144
+ subSampleSizesPerSample.set(currentSampleNum, subsampleSizes);
145
+ }
146
+ })
85
147
  .box('mdat', Mp4Parser.allData((data) => {
86
- sawMDAT = true;
87
- // Join this to any previous payload, in case the mp4 has multiple
88
- // mdats.
89
- payload = payload.concat(this.parser_.parseMedia(data, time, uri));
148
+ // We collect all of the mdats first, before parsing any of them.
149
+ // This is necessary in case the mp4 has multiple mdats.
150
+ mdats.push(data);
90
151
  }));
91
152
  parser.parse(data, /* partialOkay= */ false);
92
153
 
93
- if (!sawMDAT) {
154
+ if (mdats.length == 0) {
94
155
  throw new shaka.util.Error(
95
156
  shaka.util.Error.Severity.CRITICAL,
96
157
  shaka.util.Error.Category.TEXT,
97
158
  shaka.util.Error.Code.INVALID_MP4_TTML);
98
159
  }
99
160
 
161
+ const fullData =
162
+ shaka.util.Uint8ArrayUtils.concat(...mdats);
163
+
164
+ let sampleOffset = 0;
165
+ for (let sampleNum = 0; sampleNum < sampleSizes.length; sampleNum++) {
166
+ const sampleData =
167
+ shaka.util.BufferUtils.toUint8(fullData, sampleOffset,
168
+ sampleSizes[sampleNum]);
169
+ sampleOffset += sampleSizes[sampleNum];
170
+
171
+ const subSampleSizes = subSampleSizesPerSample.get(sampleNum);
172
+
173
+ if (subSampleSizes && subSampleSizes.length) {
174
+ const contentData =
175
+ shaka.util.BufferUtils.toUint8(sampleData, 0, subSampleSizes[0]);
176
+ let subOffset = subSampleSizes[0];
177
+ for (let i = 1; i < subSampleSizes.length; i++) {
178
+ subOffset += subSampleSizes[i];
179
+ }
180
+ payload = payload.concat(
181
+ this.parser_.parseMedia(contentData, time, uri));
182
+ } else {
183
+ payload = payload.concat(
184
+ this.parser_.parseMedia(sampleData, time, uri));
185
+ }
186
+ }
187
+
100
188
  return payload;
101
189
  }
102
190
  };
@@ -55,6 +55,9 @@ shaka.text.UITextDisplayer = class {
55
55
  /** @private {?number} */
56
56
  this.aspectRatio_ = null;
57
57
 
58
+ /** @private {?shaka.extern.TextDisplayerConfiguration} */
59
+ this.config_ = null;
60
+
58
61
  /** @type {HTMLElement} */
59
62
  this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
60
63
  this.textContainer_.classList.add('shaka-text-container');
@@ -77,7 +80,8 @@ shaka.text.UITextDisplayer = class {
77
80
  if (!this.video_.paused) {
78
81
  this.updateCaptions_();
79
82
  }
80
- }).tickEvery(/* seconds= */ 0.25);
83
+ });
84
+ this.configureCaptionsTimer_();
81
85
 
82
86
  /**
83
87
  * Maps cues to cue elements. Specifically points out the wrapper element of
@@ -101,6 +105,10 @@ shaka.text.UITextDisplayer = class {
101
105
  this.updateCaptions_(/* forceUpdate= */ true);
102
106
  });
103
107
 
108
+ this.eventManager_.listen(this.video_, 'ratechange', () => {
109
+ this.configureCaptionsTimer_();
110
+ });
111
+
104
112
  // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
105
113
  // Whenever the natural width or natural height of the video changes
106
114
  // (including, for example, because the selected video track was changed),
@@ -137,9 +145,8 @@ shaka.text.UITextDisplayer = class {
137
145
  * @export
138
146
  */
139
147
  configure(config) {
140
- if (this.captionsTimer_) {
141
- this.captionsTimer_.tickEvery(config.captionsUpdatePeriod);
142
- }
148
+ this.config_ = config;
149
+ this.configureCaptionsTimer_();
143
150
  }
144
151
 
145
152
 
@@ -163,6 +170,9 @@ shaka.text.UITextDisplayer = class {
163
170
  this.cues_.push(cue);
164
171
  }
165
172
  }
173
+ if (this.cues_.length) {
174
+ this.configureCaptionsTimer_();
175
+ }
166
176
 
167
177
  this.updateCaptions_();
168
178
  }
@@ -186,6 +196,7 @@ shaka.text.UITextDisplayer = class {
186
196
  this.cues_ = [];
187
197
  if (this.captionsTimer_) {
188
198
  this.captionsTimer_.stop();
199
+ this.captionsTimer_ = null;
189
200
  }
190
201
 
191
202
  this.currentCuesMap_.clear();
@@ -224,6 +235,9 @@ shaka.text.UITextDisplayer = class {
224
235
  // displaying if removed (say, due to the user changing languages).
225
236
  const forceUpdate = oldNumCues > this.cues_.length;
226
237
  this.updateCaptions_(forceUpdate);
238
+ if (!this.cues_.length) {
239
+ this.configureCaptionsTimer_();
240
+ }
227
241
 
228
242
  return true;
229
243
  }
@@ -246,6 +260,23 @@ shaka.text.UITextDisplayer = class {
246
260
  this.updateCaptions_(/* forceUpdate= */ true);
247
261
  }
248
262
 
263
+ /**
264
+ * @private
265
+ */
266
+ configureCaptionsTimer_() {
267
+ if (this.captionsTimer_) {
268
+ if (this.cues_.length) {
269
+ const captionsUpdatePeriod = this.config_ ?
270
+ this.config_.captionsUpdatePeriod : 0.25;
271
+ const updateTime = captionsUpdatePeriod /
272
+ Math.max(1, Math.abs(this.video_.playbackRate));
273
+ this.captionsTimer_.tickEvery(updateTime);
274
+ } else {
275
+ this.captionsTimer_.stop();
276
+ }
277
+ }
278
+ }
279
+
249
280
  /**
250
281
  * @private
251
282
  */
@@ -14,6 +14,7 @@ goog.require('shaka.util.Error');
14
14
  goog.require('shaka.util.Id3Utils');
15
15
  goog.require('shaka.util.ManifestParserUtils');
16
16
  goog.require('shaka.util.Mp4Generator');
17
+ goog.require('shaka.util.Platform');
17
18
  goog.require('shaka.util.Uint8ArrayUtils');
18
19
 
19
20
 
@@ -86,7 +87,11 @@ shaka.transmuxer.Ac3Transmuxer = class {
86
87
  */
87
88
  convertCodecs(contentType, mimeType) {
88
89
  if (this.isAc3Container_(mimeType)) {
89
- return 'audio/mp4; codecs="ac-3"';
90
+ if (shaka.util.Platform.requiresEC3InitSegments()) {
91
+ return 'audio/mp4; codecs="ec-3"';
92
+ } else {
93
+ return 'audio/mp4; codecs="ac-3"';
94
+ }
90
95
  }
91
96
  return mimeType;
92
97
  }
@@ -8,6 +8,7 @@ goog.provide('shaka.util.ConfigUtils');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
+ goog.require('shaka.util.ArrayUtils');
11
12
  goog.require('shaka.util.ObjectUtils');
12
13
 
13
14
 
@@ -189,13 +190,20 @@ shaka.util.ConfigUtils = class {
189
190
  // eslint-disable-next-line no-prototype-builtins
190
191
  if (!base.hasOwnProperty(key)) {
191
192
  acc[key] = value;
193
+ } else if (value instanceof HTMLElement &&
194
+ base[key] instanceof HTMLElement) {
195
+ if (!value.isEqualNode(base[key])) {
196
+ acc[key] = value;
197
+ }
192
198
  } else if (isObject(value) && isObject(base[key])) {
193
199
  const diff = changes(value, base[key]);
194
200
  if (Object.keys(diff).length > 0 || !isObject(diff)) {
195
201
  acc[key] = diff;
196
202
  }
197
- } else if (isArrayEmpty(value) && isArrayEmpty(base[key])) {
198
- // Do nothing if both are empty arrays
203
+ } else if (Array.isArray(value) && Array.isArray(base[key])) {
204
+ if (!shaka.util.ArrayUtils.hasSameElements(value, base[key])) {
205
+ acc[key] = value;
206
+ }
199
207
  } else if (Number.isNaN(value) && Number.isNaN(base[key])) {
200
208
  // Do nothing if both are NaN
201
209
  } else if (value !== base[key]) {
@@ -209,7 +217,9 @@ shaka.util.ConfigUtils = class {
209
217
 
210
218
  const removeEmpty = (obj) => {
211
219
  for (const key of Object.keys(obj)) {
212
- if (isObject(obj[key]) && Object.keys(obj[key]).length === 0) {
220
+ if (obj[key] instanceof HTMLElement) {
221
+ // Do nothing if it's a HTMLElement
222
+ } else if (isObject(obj[key]) && Object.keys(obj[key]).length === 0) {
213
223
  delete obj[key];
214
224
  } else if (isArrayEmpty(obj[key])) {
215
225
  delete obj[key];
@@ -157,6 +157,14 @@ shaka.util.DrmUtils = class {
157
157
  return false;
158
158
  }
159
159
 
160
+ /**
161
+ * @param {?string} keySystem
162
+ * @return {boolean}
163
+ */
164
+ static isWisePlayKeySystem(keySystem) {
165
+ return keySystem === 'com.huawei.wiseplay';
166
+ }
167
+
160
168
  /**
161
169
  * A method for generating a key for the MediaKeySystemAccessRequests cache.
162
170
  *
package/lib/util/error.js CHANGED
@@ -960,6 +960,12 @@ shaka.util.Error.Code = {
960
960
  */
961
961
  'SRC_EQUALS_PRELOAD_NOT_SUPPORTED': 7005,
962
962
 
963
+ /**
964
+ * The operation failed because the preload has been destroyed. This can
965
+ * happen by reusing the same preload in multiple load calls.
966
+ */
967
+ 'PRELOAD_DESTROYED': 7006,
968
+
963
969
  /**
964
970
  * The Cast API is unavailable. This may be because of one of the following:
965
971
  * 1. The browser may not have Cast support
@@ -8,6 +8,7 @@ goog.provide('shaka.util.Mp4Generator');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.util.ManifestParserUtils');
11
+ goog.require('shaka.util.Platform');
11
12
  goog.require('shaka.util.Uint8ArrayUtils');
12
13
 
13
14
 
@@ -298,6 +299,20 @@ shaka.util.Mp4Generator = class {
298
299
  stsd_(streamInfo) {
299
300
  const Mp4Generator = shaka.util.Mp4Generator;
300
301
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
302
+ let audioCodec = 'aac';
303
+ if (streamInfo.codecs.includes('mp3')) {
304
+ audioCodec = 'mp3';
305
+ } else if (streamInfo.codecs.includes('ac-3')) {
306
+ if (shaka.util.Platform.requiresEC3InitSegments()) {
307
+ audioCodec = 'ec-3';
308
+ } else {
309
+ audioCodec = 'ac-3';
310
+ }
311
+ } else if (streamInfo.codecs.includes('ec-3')) {
312
+ audioCodec = 'ec-3';
313
+ } else if (streamInfo.codecs.includes('opus')) {
314
+ audioCodec = 'opus';
315
+ }
301
316
  let bytes = new Uint8Array([]);
302
317
  switch (streamInfo.type) {
303
318
  case ContentType.VIDEO:
@@ -308,13 +323,13 @@ shaka.util.Mp4Generator = class {
308
323
  }
309
324
  break;
310
325
  case ContentType.AUDIO:
311
- if (streamInfo.codecs.includes('mp3')) {
326
+ if (audioCodec == 'mp3') {
312
327
  bytes = this.mp3_(streamInfo);
313
- } else if (streamInfo.codecs.includes('ac-3')) {
328
+ } else if (audioCodec == 'ac-3') {
314
329
  bytes = this.ac3_(streamInfo);
315
- } else if (streamInfo.codecs.includes('ec-3')) {
330
+ } else if (audioCodec == 'ec-3') {
316
331
  bytes = this.ec3_(streamInfo);
317
- } else if (streamInfo.codecs.includes('opus')) {
332
+ } else if (audioCodec == 'opus') {
318
333
  bytes = this.opus_(streamInfo);
319
334
  } else {
320
335
  bytes = this.mp4a_(streamInfo);
@@ -836,7 +836,7 @@ shaka.util.PeriodCombiner = class {
836
836
  clone.roles = clone.roles.slice();
837
837
  clone.segmentIndex = null;
838
838
  clone.emsgSchemeIdUris = [];
839
- clone.keyIds = new Set();
839
+ clone.keyIds = new Set(stream.keyIds);
840
840
  clone.closedCaptions = stream.closedCaptions ?
841
841
  new Map(stream.closedCaptions) : null;
842
842
  clone.trickModeVideo = null;
@@ -860,7 +860,7 @@ shaka.util.PeriodCombiner = class {
860
860
  clone.roles = clone.roles.slice();
861
861
  // These are wiped out now and rebuilt later from the various per-period
862
862
  // streams that match this output.
863
- clone.keyIds = new Set();
863
+ clone.keyIds = new Set(streamDb.keyIds);
864
864
  clone.segments = [];
865
865
  clone.variantIds = [];
866
866
  clone.closedCaptions = streamDb.closedCaptions ?
@@ -1772,6 +1772,7 @@ shaka.util.PeriodCombiner = class {
1772
1772
  v.roles,
1773
1773
  v.closedCaptions ? Array.from(v.closedCaptions.entries()) : null,
1774
1774
  v.bandwidth,
1775
+ Array.from(v.keyIds),
1775
1776
  ]);
1776
1777
  }
1777
1778
 
@@ -1794,6 +1795,7 @@ shaka.util.PeriodCombiner = class {
1794
1795
  a.roles,
1795
1796
  a.audioSamplingRate,
1796
1797
  a.primary,
1798
+ Array.from(a.keyIds),
1797
1799
  ]);
1798
1800
  }
1799
1801
 
@@ -307,13 +307,15 @@ shaka.util.Platform = class {
307
307
  !shaka.util.Platform.isTizen() &&
308
308
  !shaka.util.Platform.isEOS() &&
309
309
  !shaka.util.Platform.isAPL() &&
310
+ !shaka.util.Platform.isAPP() &&
310
311
  !shaka.util.Platform.isVirginMedia() &&
311
312
  !shaka.util.Platform.isOrange() &&
312
313
  !shaka.util.Platform.isPS4() &&
313
314
  !shaka.util.Platform.isAmazonFireTV() &&
314
315
  !shaka.util.Platform.isComcastX1() &&
315
316
  !shaka.util.Platform.isZenterio() &&
316
- !shaka.util.Platform.isSkyQ();
317
+ !shaka.util.Platform.isSkyQ() &&
318
+ !shaka.util.Platform.isMultiChoice();
317
319
  }
318
320
 
319
321
  /**
@@ -391,6 +393,16 @@ shaka.util.Platform = class {
391
393
  return shaka.util.Platform.userAgentContains_('DT_STB_BCM');
392
394
  }
393
395
 
396
+ /**
397
+ * Check if the current platform is a MultiChoice STB.
398
+ * @return {boolean}
399
+ */
400
+ static isMultiChoice() {
401
+ // cspell: ignore MDMP
402
+ return shaka.util.Platform.userAgentContains_('MDMP1001S') ||
403
+ shaka.util.Platform.userAgentContains_('PS5525IMC');
404
+ }
405
+
394
406
  /**
395
407
  * Returns a major version number for Safari, or Safari-based iOS browsers.
396
408
  *
@@ -456,6 +468,14 @@ shaka.util.Platform = class {
456
468
  return shaka.util.Platform.userAgentContains_('PC=APL');
457
469
  }
458
470
 
471
+ /**
472
+ * Check if the current platform is an APPSTB set-top box.
473
+ * @return {boolean}
474
+ */
475
+ static isAPP() {
476
+ return shaka.util.Platform.userAgentContains_('PC=APPSTB');
477
+ }
478
+
459
479
  /**
460
480
  * Guesses if the platform is a mobile one (iOS or Android).
461
481
  *
@@ -523,6 +543,9 @@ shaka.util.Platform = class {
523
543
  * @return {boolean}
524
544
  */
525
545
  static isAndroid() {
546
+ if (navigator.userAgentData && navigator.userAgentData.platform) {
547
+ return navigator.userAgentData.platform.toLowerCase() == 'android';
548
+ }
526
549
  return shaka.util.Platform.userAgentContains_('Android');
527
550
  }
528
551
 
@@ -532,6 +555,9 @@ shaka.util.Platform = class {
532
555
  * @return {boolean}
533
556
  */
534
557
  static isFuchsia() {
558
+ if (navigator.userAgentData && navigator.userAgentData.platform) {
559
+ return navigator.userAgentData.platform.toLowerCase() == 'fuchsia';
560
+ }
535
561
  return shaka.util.Platform.userAgentContains_('Fuchsia');
536
562
  }
537
563
 
@@ -548,7 +574,8 @@ shaka.util.Platform = class {
548
574
  Platform.isEOS() || Platform.isAPL() ||
549
575
  Platform.isVirginMedia() || Platform.isOrange() ||
550
576
  Platform.isComcastX1() || Platform.isChromecast() ||
551
- Platform.isHisense() || Platform.isZenterio()) {
577
+ Platform.isHisense() || Platform.isZenterio() ||
578
+ Platform.isAPP() || Platform.isMultiChoice()) {
552
579
  return true;
553
580
  }
554
581
  return false;
@@ -621,6 +648,19 @@ shaka.util.Platform = class {
621
648
  (Platform.isEdge() && Platform.isWindows() && isPlayReady);
622
649
  }
623
650
 
651
+ /**
652
+ * Returns true if the platform requires AC-3 signalling in init
653
+ * segments to be replaced with EC-3 signalling.
654
+ * For such platforms, MediaSourceEngine will attempt to work
655
+ * around it by inserting fake EC-3 signalling into
656
+ * initialization segments.
657
+ *
658
+ * @return {boolean}
659
+ */
660
+ static requiresEC3InitSegments() {
661
+ return shaka.util.Platform.isTizen3();
662
+ }
663
+
624
664
  /**
625
665
  * Returns true if the platform supports SourceBuffer "sequence mode".
626
666
  *
@@ -629,7 +669,7 @@ shaka.util.Platform = class {
629
669
  static supportsSequenceMode() {
630
670
  const Platform = shaka.util.Platform;
631
671
  if (Platform.isTizen3() || Platform.isTizen2() ||
632
- Platform.isWebOS3() || Platform.isPS4()) {
672
+ Platform.isWebOS3() || Platform.isPS4() || Platform.isPS5()) {
633
673
  return false;
634
674
  }
635
675
  return true;
@@ -685,16 +725,13 @@ shaka.util.Platform = class {
685
725
  }
686
726
 
687
727
  /**
688
- * Returns true if MediaKeys is polyfilled
728
+ * Returns true if MediaKeys is polyfilled by the specified polyfill.
689
729
  *
690
- * @param {string=} polyfillType
730
+ * @param {string} polyfillType
691
731
  * @return {boolean}
692
732
  */
693
733
  static isMediaKeysPolyfilled(polyfillType) {
694
- if (polyfillType) {
695
- return polyfillType === window.shakaMediaKeysPolyfill;
696
- }
697
- return !!window.shakaMediaKeysPolyfill;
734
+ return polyfillType === window.shakaMediaKeysPolyfill;
698
735
  }
699
736
 
700
737
 
@@ -780,10 +817,21 @@ shaka.util.Platform = class {
780
817
  maxResolution.height = 1080;
781
818
  }
782
819
  } else if (Platform.isHisense()) {
783
- // eslint-disable-next-line new-cap
784
- if (window.Hisense_Get4KSupportState &&
820
+ let supports4k = null;
821
+ if (window.Hisense_Get4KSupportState) {
822
+ try {
785
823
  // eslint-disable-next-line new-cap
786
- window.Hisense_Get4KSupportState()) {
824
+ supports4k = window.Hisense_Get4KSupportState();
825
+ } catch (e) {
826
+ shaka.log.debug('Hisense: Failed to get 4K support state', e);
827
+ }
828
+ }
829
+ if (supports4k == null) {
830
+ // If API is not there or not working for whatever reason, fallback to
831
+ // user agent check, as it contains UHD or FHD info.
832
+ supports4k = Platform.userAgentContains_('UHD');
833
+ }
834
+ if (supports4k) {
787
835
  maxResolution.width = 3840;
788
836
  maxResolution.height = 2160;
789
837
  } else {
@@ -73,7 +73,7 @@ shaka.util.PlayerConfiguration = class {
73
73
  persistentSessionOnlinePlayback: false,
74
74
  persistentSessionsMetadata: [],
75
75
  initDataTransform: (initData, initDataType, drmInfo) => {
76
- if (shaka.util.Platform.isMediaKeysPolyfilled() &&
76
+ if (shaka.util.Platform.isMediaKeysPolyfilled('apple') &&
77
77
  initDataType == 'skd') {
78
78
  const cert = drmInfo.serverCertificate;
79
79
  const contentId =
@@ -261,6 +261,11 @@ shaka.util.PlayerConfiguration = class {
261
261
  shaka.util.Platform.isPS5(),
262
262
  };
263
263
 
264
+ const safariVersion = shaka.util.Platform.safariVersion();
265
+ if (safariVersion && safariVersion >= 17) {
266
+ streaming.infiniteLiveStreamDuration = true;
267
+ }
268
+
264
269
  // WebOS, Tizen, Chromecast and Hisense have long hardware pipelines
265
270
  // that respond slowly to seeking.
266
271
  // Therefore we should not seek when we detect a stall
@@ -996,6 +996,11 @@ shaka.util.StreamUtils = class {
996
996
  }
997
997
  }
998
998
 
999
+ if (codecs.toLowerCase() == 'ac-3' &&
1000
+ shaka.util.Platform.requiresEC3InitSegments()) {
1001
+ return 'ec-3';
1002
+ }
1003
+
999
1004
  return codecs;
1000
1005
  }
1001
1006
 
@@ -1191,6 +1196,9 @@ shaka.util.StreamUtils = class {
1191
1196
  * @return {shaka.extern.Track}
1192
1197
  */
1193
1198
  static variantToTrack(variant) {
1199
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
1200
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
1201
+
1194
1202
  /** @type {?shaka.extern.Stream} */
1195
1203
  const audio = variant.audio;
1196
1204
  /** @type {?shaka.extern.Stream} */
@@ -1309,6 +1317,16 @@ shaka.util.StreamUtils = class {
1309
1317
  track.audioSamplingRate = video.audioSamplingRate;
1310
1318
  track.spatialAudio = video.spatialAudio;
1311
1319
  track.originalLanguage = video.originalLanguage;
1320
+ track.audioMimeType = videoMimeType;
1321
+ const allCodecs = videoCodec.split(',');
1322
+ try {
1323
+ track.videoCodec = ManifestParserUtils.guessCodecs(
1324
+ ContentType.VIDEO, allCodecs);
1325
+ track.audioCodec = ManifestParserUtils.guessCodecs(
1326
+ ContentType.AUDIO, allCodecs);
1327
+ } catch (e) {
1328
+ // Ignore this error.
1329
+ }
1312
1330
  }
1313
1331
  }
1314
1332
 
package/lib/util/tXml.js CHANGED
@@ -555,8 +555,9 @@ shaka.util.TXml = class {
555
555
  const schemaNS = shaka.util.TXml.getKnownNameSpace(ns);
556
556
  const found = [];
557
557
  if (elem.children) {
558
+ const tagName = schemaNS ? `${schemaNS}:${name}` : name;
558
559
  for (const child of elem.children) {
559
- if (child && child.tagName === `${schemaNS}:${name}`) {
560
+ if (child && child.tagName === tagName) {
560
561
  found.push(child);
561
562
  }
562
563
  }
@@ -625,7 +626,7 @@ shaka.util.TXml = class {
625
626
 
626
627
  const re = '^P(?:([0-9]*)Y)?(?:([0-9]*)M)?(?:([0-9]*)D)?' +
627
628
  '(?:T(?:([0-9]*)H)?(?:([0-9]*)M)?(?:([0-9.]*)S)?)?$';
628
- const matches = new RegExp(re).exec(durationString);
629
+ const matches = new RegExp(re, 'i').exec(durationString);
629
630
 
630
631
  if (!matches) {
631
632
  shaka.log.warning('Invalid duration string:', durationString);