shaka-player 4.12.4 → 4.12.6

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 (55) hide show
  1. package/dist/deps.js +2 -2
  2. package/dist/locales.js +35 -35
  3. package/dist/shaka-player.compiled.d.ts +11 -3
  4. package/dist/shaka-player.compiled.debug.d.ts +11 -3
  5. package/dist/shaka-player.compiled.debug.externs.js +8 -5
  6. package/dist/shaka-player.compiled.debug.js +340 -331
  7. package/dist/shaka-player.compiled.debug.map +3 -3
  8. package/dist/shaka-player.compiled.externs.js +8 -5
  9. package/dist/shaka-player.compiled.js +1383 -1378
  10. package/dist/shaka-player.compiled.map +4 -4
  11. package/dist/shaka-player.ui.d.ts +11 -3
  12. package/dist/shaka-player.ui.debug.d.ts +11 -3
  13. package/dist/shaka-player.ui.debug.externs.js +8 -5
  14. package/dist/shaka-player.ui.debug.js +357 -348
  15. package/dist/shaka-player.ui.debug.map +3 -3
  16. package/dist/shaka-player.ui.externs.js +8 -5
  17. package/dist/shaka-player.ui.js +1605 -1599
  18. package/dist/shaka-player.ui.map +3 -3
  19. package/externs/shaka/codecs.js +21 -0
  20. package/externs/shaka/offline.js +4 -0
  21. package/lib/cast/cast_utils.js +1 -0
  22. package/lib/dash/dash_parser.js +3 -0
  23. package/lib/hls/hls_parser.js +26 -5
  24. package/lib/media/drm_engine.js +70 -29
  25. package/lib/media/manifest_filterer.js +2 -1
  26. package/lib/media/media_source_engine.js +138 -129
  27. package/lib/media/segment_utils.js +50 -44
  28. package/lib/media/streaming_engine.js +9 -1
  29. package/lib/offline/download_info.js +5 -0
  30. package/lib/offline/download_manager.js +54 -0
  31. package/lib/offline/manifest_converter.js +1 -0
  32. package/lib/offline/storage.js +12 -3
  33. package/lib/player.js +52 -29
  34. package/lib/polyfill/media_capabilities.js +1 -1
  35. package/lib/text/simple_text_displayer.js +9 -1
  36. package/lib/text/ui_text_displayer.js +2 -14
  37. package/lib/transmuxer/aac_transmuxer.js +11 -3
  38. package/lib/transmuxer/ac3_transmuxer.js +11 -3
  39. package/lib/transmuxer/ec3_transmuxer.js +11 -3
  40. package/lib/transmuxer/h264.js +143 -70
  41. package/lib/transmuxer/mp3_transmuxer.js +11 -3
  42. package/lib/transmuxer/ts_transmuxer.js +68 -28
  43. package/lib/util/drm_utils.js +20 -0
  44. package/lib/util/mime_utils.js +3 -0
  45. package/lib/util/object_utils.js +83 -0
  46. package/lib/util/platform.js +48 -40
  47. package/lib/util/stream_utils.js +37 -113
  48. package/lib/util/ts_parser.js +111 -85
  49. package/package.json +1 -1
  50. package/ui/ad_counter.js +5 -0
  51. package/ui/controls.js +4 -2
  52. package/ui/fast_forward_button.js +7 -0
  53. package/ui/play_button.js +1 -1
  54. package/ui/playback_rate_selection.js +1 -1
  55. package/ui/rewind_button.js +7 -0
@@ -196,71 +196,28 @@ shaka.transmuxer.H264 = class {
196
196
  }
197
197
 
198
198
  /**
199
- * @param {!Array.<shaka.extern.VideoNalu>} nalus
200
- * @return {?{data: !Uint8Array, isKeyframe: boolean}}
199
+ * @param {!Array.<shaka.extern.MPEG_PES>} videoData
200
+ * @return {!Array.<shaka.extern.VideoSample>}
201
201
  */
202
- static parseFrame(nalus) {
202
+ static getVideoSamples(videoData) {
203
203
  const H264 = shaka.transmuxer.H264;
204
- let isKeyframe = false;
205
- const nalusData = [];
206
- const spsNalu = nalus.find((nalu) => {
207
- return nalu.type == H264.NALU_TYPE_SPS_;
208
- });
209
- let avcSample = false;
210
- for (const nalu of nalus) {
211
- let push = false;
212
- switch (nalu.type) {
213
- case H264.NALU_TYPE_NDR_: {
214
- avcSample = true;
215
- push = true;
216
- const data = nalu.data;
217
- // Only check slice type to detect KF in case SPS found in same packet
218
- // (any keyframe is preceded by SPS ...)
219
- if (spsNalu && data.length > 4) {
220
- // retrieve slice type by parsing beginning of NAL unit (follow
221
- // H264 spec,slice_header definition) to detect keyframe embedded
222
- // in NDR
223
- const sliceType = new shaka.util.ExpGolomb(data).readSliceType();
224
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
225
- // SI slice : A slice that is coded using intra prediction only and
226
- // using quantisation of the prediction samples.
227
- // An SI slice can be coded such that its decoded samples can be
228
- // constructed identically to an SP slice.
229
- // I slice: A slice that is not an SI slice that is decoded using
230
- // intra prediction only.
231
- if (sliceType === 2 || sliceType === 4 ||
232
- sliceType === 7 || sliceType === 9) {
233
- isKeyframe = true;
234
- }
235
- }
236
- break;
237
- }
238
- case H264.NALU_TYPE_IDR_:
239
- avcSample = true;
240
- push = true;
241
- isKeyframe = true;
242
- break;
243
- case H264.NALU_TYPE_SEI_:
244
- push = true;
245
- break;
246
- case H264.NALU_TYPE_SPS_:
247
- push = true;
248
- break;
249
- case H264.NALU_TYPE_PPS_:
250
- push = true;
251
- break;
252
- case H264.NALU_TYPE_AUD_:
253
- push = true;
254
- avcSample = true;
255
- break;
256
- case H264.NALU_TYPE_FILLER_DATA_:
257
- push = true;
258
- break;
259
- default:
260
- push = false;
261
- break;
204
+
205
+ /** @type {!Array.<shaka.extern.VideoSample>} */
206
+ const videoSamples = [];
207
+ /** @type {?shaka.extern.VideoSample} */
208
+ let lastVideoSample = null;
209
+ /** @type {boolean} */
210
+ let audFound = false;
211
+
212
+ const addLastVideoSample = () => {
213
+ if (!lastVideoSample) {
214
+ return;
262
215
  }
263
- if (avcSample && push) {
216
+ if (!lastVideoSample.nalus.length || !lastVideoSample.frame) {
217
+ return;
218
+ }
219
+ const nalusData = [];
220
+ for (const nalu of lastVideoSample.nalus) {
264
221
  const size = nalu.fullData.byteLength;
265
222
  const naluLength = new Uint8Array(4);
266
223
  naluLength[0] = (size >> 24) & 0xff;
@@ -270,15 +227,131 @@ shaka.transmuxer.H264 = class {
270
227
  nalusData.push(naluLength);
271
228
  nalusData.push(nalu.fullData);
272
229
  }
273
- }
274
- if (!nalusData.length) {
275
- return null;
276
- }
277
- const data = shaka.util.Uint8ArrayUtils.concat(...nalusData);
278
- return {
279
- data,
280
- isKeyframe,
230
+ lastVideoSample.data = shaka.util.Uint8ArrayUtils.concat(...nalusData);
231
+ videoSamples.push(lastVideoSample);
232
+ };
233
+
234
+ const createLastVideoSample = (pes) => {
235
+ lastVideoSample = {
236
+ data: new Uint8Array([]),
237
+ frame: false,
238
+ isKeyframe: false,
239
+ pts: pes.pts,
240
+ dts: pes.dts,
241
+ nalus: [],
242
+ };
281
243
  };
244
+
245
+ for (let i = 0; i < videoData.length; i++) {
246
+ const pes = videoData[i];
247
+ const nalus = pes.nalus;
248
+ let spsFound = false;
249
+
250
+ // If new NAL units found and last sample still there, let's push ...
251
+ // This helps parsing streams with missing AUD
252
+ // (only do this if AUD never found)
253
+ if (lastVideoSample && nalus.length && !audFound) {
254
+ addLastVideoSample();
255
+ createLastVideoSample(pes);
256
+ }
257
+
258
+ for (const nalu of pes.nalus) {
259
+ let push = false;
260
+ switch (nalu.type) {
261
+ case H264.NALU_TYPE_NDR_: {
262
+ let isKeyframe = false;
263
+ push = true;
264
+ const data = nalu.data;
265
+ // Only check slice type to detect KF in case SPS found in same
266
+ // packet (any keyframe is preceded by SPS ...)
267
+ if (spsFound && data.length > 4) {
268
+ // retrieve slice type by parsing beginning of NAL unit (follow
269
+ // H264 spec,slice_header definition) to detect keyframe embedded
270
+ // in NDR
271
+ const sliceType = new shaka.util.ExpGolomb(data).readSliceType();
272
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
273
+ // SI slice : A slice that is coded using intra prediction only
274
+ // and using quantisation of the prediction samples.
275
+ // An SI slice can be coded such that its decoded samples can be
276
+ // constructed identically to an SP slice.
277
+ // I slice: A slice that is not an SI slice that is decoded using
278
+ // intra prediction only.
279
+ if (sliceType === 2 || sliceType === 4 ||
280
+ sliceType === 7 || sliceType === 9) {
281
+ isKeyframe = true;
282
+ }
283
+ }
284
+ if (isKeyframe) {
285
+ // If we have non-keyframe data already, that cannot belong to
286
+ // the same frame as a keyframe, so force a push
287
+ if (lastVideoSample &&
288
+ lastVideoSample.frame && !lastVideoSample.isKeyframe) {
289
+ addLastVideoSample();
290
+ lastVideoSample = null;
291
+ }
292
+ }
293
+ if (!lastVideoSample) {
294
+ createLastVideoSample(pes);
295
+ }
296
+ lastVideoSample.frame = true;
297
+ lastVideoSample.isKeyframe = isKeyframe;
298
+ break;
299
+ }
300
+ case H264.NALU_TYPE_IDR_: {
301
+ push = true;
302
+ // Handle PES not starting with AUD
303
+ // If we have frame data already, that cannot belong to the same
304
+ // frame, so force a push
305
+ if (lastVideoSample &&
306
+ lastVideoSample.frame && !lastVideoSample.isKeyframe) {
307
+ addLastVideoSample();
308
+ lastVideoSample = null;
309
+ }
310
+ if (!lastVideoSample) {
311
+ createLastVideoSample(pes);
312
+ }
313
+ lastVideoSample.frame = true;
314
+ lastVideoSample.isKeyframe = true;
315
+ break;
316
+ }
317
+ case H264.NALU_TYPE_SEI_:
318
+ push = true;
319
+ break;
320
+ case H264.NALU_TYPE_SPS_:
321
+ push = true;
322
+ spsFound = true;
323
+ break;
324
+ case H264.NALU_TYPE_PPS_:
325
+ push = true;
326
+ break;
327
+ case H264.NALU_TYPE_AUD_:
328
+ push = true;
329
+ audFound = true;
330
+ if (lastVideoSample && lastVideoSample.frame) {
331
+ addLastVideoSample();
332
+ lastVideoSample = null;
333
+ }
334
+ if (!lastVideoSample) {
335
+ createLastVideoSample(pes);
336
+ }
337
+ break;
338
+ case H264.NALU_TYPE_FILLER_DATA_:
339
+ push = true;
340
+ break;
341
+ default:
342
+ push = false;
343
+ break;
344
+ }
345
+ if (lastVideoSample && push) {
346
+ lastVideoSample.nalus.push(nalu);
347
+ }
348
+ }
349
+ }
350
+
351
+ // If last PES packet, push samples
352
+ addLastVideoSample();
353
+
354
+ return videoSamples;
282
355
  }
283
356
  };
284
357
 
@@ -34,6 +34,9 @@ shaka.transmuxer.Mp3Transmuxer = class {
34
34
 
35
35
  /** @private {!Map.<string, !Uint8Array>} */
36
36
  this.initSegments = new Map();
37
+
38
+ /** @private {?Uint8Array} */
39
+ this.lastInitSegment_ = null;
37
40
  }
38
41
 
39
42
 
@@ -196,11 +199,16 @@ shaka.transmuxer.Mp3Transmuxer = class {
196
199
  } else {
197
200
  initSegment = this.initSegments.get(initSegmentKey);
198
201
  }
202
+ const appendInitSegment = this.lastInitSegment_ !== initSegment;
199
203
  const segmentData = mp4Generator.segmentData();
200
-
204
+ this.lastInitSegment_ = initSegment;
201
205
  this.frameIndex_++;
202
- const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
203
- return Promise.resolve(transmuxData);
206
+ if (appendInitSegment) {
207
+ const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
208
+ return Promise.resolve(transmuxData);
209
+ } else {
210
+ return Promise.resolve(segmentData);
211
+ }
204
212
  }
205
213
  };
206
214
 
@@ -52,6 +52,9 @@ shaka.transmuxer.TsTransmuxer = class {
52
52
 
53
53
  /** @private {?shaka.transmuxer.AacTransmuxer} */
54
54
  this.aacTransmuxer_ = null;
55
+
56
+ /** @private {?Uint8Array} */
57
+ this.lastInitSegment_ = null;
55
58
  }
56
59
 
57
60
 
@@ -293,11 +296,16 @@ shaka.transmuxer.TsTransmuxer = class {
293
296
  } else {
294
297
  initSegment = this.initSegments.get(initSegmentKey);
295
298
  }
299
+ const appendInitSegment = this.lastInitSegment_ !== initSegment;
296
300
  const segmentData = mp4Generator.segmentData();
297
-
301
+ this.lastInitSegment_ = initSegment;
298
302
  this.frameIndex_++;
299
- const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
300
- return Promise.resolve(transmuxData);
303
+ if (appendInitSegment) {
304
+ const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
305
+ return Promise.resolve(transmuxData);
306
+ } else {
307
+ return Promise.resolve(segmentData);
308
+ }
301
309
  }
302
310
 
303
311
 
@@ -312,6 +320,7 @@ shaka.transmuxer.TsTransmuxer = class {
312
320
  getAacStreamInfo_(tsParser, stream, duration, reference) {
313
321
  const ADTS = shaka.transmuxer.ADTS;
314
322
  const timescale = shaka.util.TsParser.Timescale;
323
+ const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
315
324
 
316
325
  /** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
317
326
  const samples = [];
@@ -320,12 +329,41 @@ shaka.transmuxer.TsTransmuxer = class {
320
329
 
321
330
  let firstPts = null;
322
331
 
332
+ /** @type {?number} */
333
+ let nextStartOffset = null;
334
+ /** @type {?Uint8Array} */
335
+ let overflowBytes = null;
336
+
323
337
  for (const audioData of tsParser.getAudioData()) {
324
- const data = audioData.data;
338
+ let data = audioData.data;
325
339
  if (!data) {
326
340
  continue;
327
341
  }
328
342
  let offset = 0;
343
+ if (nextStartOffset == -1 && overflowBytes) {
344
+ data = Uint8ArrayUtils.concat(overflowBytes, audioData.data);
345
+ nextStartOffset = null;
346
+ } else if (nextStartOffset != null && overflowBytes) {
347
+ offset = Math.max(0, nextStartOffset);
348
+ const missingFrameData =
349
+ Uint8ArrayUtils.concat(overflowBytes, data.subarray(0, offset));
350
+ samples.push({
351
+ data: missingFrameData,
352
+ size: missingFrameData.byteLength,
353
+ duration: ADTS.AAC_SAMPLES_PER_FRAME,
354
+ cts: 0,
355
+ flags: {
356
+ isLeading: 0,
357
+ isDependedOn: 0,
358
+ hasRedundancy: 0,
359
+ degradPrio: 0,
360
+ dependsOn: 2,
361
+ isNonSync: 0,
362
+ },
363
+ });
364
+ overflowBytes = null;
365
+ nextStartOffset = null;
366
+ }
329
367
  info = ADTS.parseInfo(data, offset);
330
368
  if (!info) {
331
369
  throw new shaka.util.Error(
@@ -342,12 +380,16 @@ shaka.transmuxer.TsTransmuxer = class {
342
380
  while (offset < data.length) {
343
381
  const header = ADTS.parseHeader(data, offset);
344
382
  if (!header) {
345
- // We will increment one byte each time until we find the header.
346
- offset++;
347
- continue;
383
+ overflowBytes = data.subarray(offset, data.length);
384
+ nextStartOffset = -1;
385
+ break;
348
386
  }
349
387
  const length = header.headerLength + header.frameLength;
350
- if (offset + length <= data.length) {
388
+ nextStartOffset = Math.max(0, offset + length - data.length);
389
+ if (nextStartOffset != 0) {
390
+ overflowBytes = data.subarray(
391
+ offset + header.headerLength, offset + length);
392
+ } else if (offset + length <= data.length) {
351
393
  const frameData = data.subarray(
352
394
  offset + header.headerLength, offset + length);
353
395
 
@@ -865,50 +907,48 @@ shaka.transmuxer.TsTransmuxer = class {
865
907
  /** @type {?number} */
866
908
  let baseMediaDecodeTime = null;
867
909
 
868
- const nalus = [];
869
910
  const videoData = tsParser.getVideoData();
870
- if (!videoData.length) {
911
+ const videoSamples = H264.getVideoSamples(videoData);
912
+ if (!videoSamples.length) {
871
913
  throw new shaka.util.Error(
872
914
  shaka.util.Error.Severity.CRITICAL,
873
915
  shaka.util.Error.Category.MEDIA,
874
916
  shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA,
875
917
  reference ? reference.getUris()[0] : null);
876
918
  }
877
- for (let i = 0; i < videoData.length; i++) {
878
- const pes = videoData[i];
879
- const dataNalus = pes.nalus;
880
- nalus.push(...dataNalus);
881
- const frame = H264.parseFrame(dataNalus);
882
- if (!frame) {
883
- continue;
884
- }
919
+ for (let i = 0; i < videoSamples.length; i++) {
920
+ const videoSample = videoSamples[i];
885
921
  if (baseMediaDecodeTime == null) {
886
- baseMediaDecodeTime = pes.dts;
922
+ baseMediaDecodeTime = videoSample.dts;
887
923
  }
888
924
  let duration;
889
- if (i + 1 < videoData.length) {
890
- duration = (videoData[i + 1].dts || 0) - (pes.dts || 0);
891
- } else if (videoData.length > 1) {
892
- duration = (pes.dts || 0) - (videoData[i - 1].dts || 0);
925
+ if (i + 1 < videoSamples.length) {
926
+ duration = (videoSamples[i + 1].dts || 0) - (videoSample.dts || 0);
927
+ } else if (videoSamples.length > 1) {
928
+ duration = (videoSample.dts || 0) - (videoSamples[i - 1].dts || 0);
893
929
  } else {
894
930
  duration = (reference.endTime - reference.startTime) * timescale;
895
931
  }
896
932
  samples.push({
897
- data: frame.data,
898
- size: frame.data.byteLength,
933
+ data: videoSample.data,
934
+ size: videoSample.data.byteLength,
899
935
  duration: duration,
900
- cts: Math.round((pes.pts || 0) - (pes.dts || 0)),
936
+ cts: Math.round((videoSample.pts || 0) - (videoSample.dts || 0)),
901
937
  flags: {
902
938
  isLeading: 0,
903
939
  isDependedOn: 0,
904
940
  hasRedundancy: 0,
905
941
  degradPrio: 0,
906
- dependsOn: frame.isKeyframe ? 2 : 1,
907
- isNonSync: frame.isKeyframe ? 0 : 1,
942
+ dependsOn: videoSample.isKeyframe ? 2 : 1,
943
+ isNonSync: videoSample.isKeyframe ? 0 : 1,
908
944
  },
909
945
  });
910
946
  }
911
947
 
948
+ const nalus = [];
949
+ for (const pes of videoData) {
950
+ nalus.push(...pes.nalus);
951
+ }
912
952
  const info = H264.parseInfo(nalus);
913
953
 
914
954
  if (!info || baseMediaDecodeTime == null) {
@@ -113,6 +113,26 @@ shaka.util.DrmUtils = class {
113
113
  return drmInfo ? drmInfo.keySystem : '';
114
114
  }
115
115
 
116
+ /**
117
+ * @param {?string} keySystem
118
+ * @return {boolean}
119
+ */
120
+ static isClearKeySystem(keySystem) {
121
+ return keySystem === 'org.w3.clearkey';
122
+ }
123
+
124
+ /**
125
+ * @param {?string} keySystem
126
+ * @return {boolean}
127
+ */
128
+ static isWidevineKeySystem(keySystem) {
129
+ if (keySystem) {
130
+ return !!keySystem.match(/^com\.widevine\.alpha/);
131
+ }
132
+
133
+ return false;
134
+ }
135
+
116
136
  /**
117
137
  * @param {?string} keySystem
118
138
  * @return {boolean}
@@ -179,6 +179,9 @@ shaka.util.MimeUtils = class {
179
179
  return 'vvc'; // H266
180
180
  case base === 'dvh1':
181
181
  case base === 'dvhe':
182
+ if (profile && profile.startsWith('05')) {
183
+ return 'dovi-p5'; // Dolby Vision profile 5
184
+ }
182
185
  return 'dovi-hevc'; // Dolby Vision based in HEVC
183
186
  case base === 'dvav':
184
187
  case base === 'dva1':
@@ -89,4 +89,87 @@ shaka.util.ObjectUtils = class {
89
89
  }
90
90
  return clone;
91
91
  }
92
+
93
+
94
+ /**
95
+ * Constructs a string out of a value, similar to the JSON.stringify method.
96
+ * Unlike that method, this guarantees that the order of the keys in an
97
+ * object is alphabetical, so it can be used as a way to reliably compare two
98
+ * objects.
99
+ *
100
+ * @param {?} value
101
+ * @return {string}
102
+ */
103
+ static alphabeticalKeyOrderStringify(value) {
104
+ if (Array.isArray(value)) {
105
+ return shaka.util.ObjectUtils.arrayStringify_(value);
106
+ } else if (typeof value == 'function') {
107
+ // For safety, skip functions. For function x,
108
+ // x.prototype.constructor.prototype === x.prototype, so all functions
109
+ // contain circular references if treated like Objects.
110
+ return '';
111
+ } else if (value instanceof Object) {
112
+ return shaka.util.ObjectUtils.objectStringify_(value);
113
+ } else {
114
+ return JSON.stringify(value);
115
+ }
116
+ }
117
+
118
+
119
+ /**
120
+ * Helper for alphabeticalKeyOrderStringify for objects.
121
+ *
122
+ * @param {!Object} obj
123
+ * @return {string}
124
+ * @private
125
+ */
126
+ static objectStringify_(obj) {
127
+ // NOTE: This excludes prototype chain keys. For now, this is intended for
128
+ // anonymous objects only, so we don't care. If that changes, go back to a
129
+ // for-in loop.
130
+ const keys = Object.keys(obj);
131
+ // Alphabetically sort the keys, so they will be in a reliable order.
132
+ keys.sort();
133
+
134
+ const terms = [];
135
+ for (const key of keys) {
136
+ const escapedKey = JSON.stringify(key);
137
+ const value = obj[key];
138
+ if (value !== undefined) {
139
+ const escapedValue =
140
+ shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
141
+ if (escapedValue) {
142
+ terms.push(escapedKey + ':' + escapedValue);
143
+ }
144
+ }
145
+ }
146
+ return '{' + terms.join(',') + '}';
147
+ }
148
+
149
+
150
+ /**
151
+ * Helper for alphabeticalKeyOrderStringify for arrays.
152
+ *
153
+ * This could itself be JSON.stringify, except we want objects within the
154
+ * array to go through our own stringifiers.
155
+ *
156
+ * @param {!Array} arr
157
+ * @return {string}
158
+ * @private
159
+ */
160
+ static arrayStringify_(arr) {
161
+ const terms = [];
162
+ for (let index = 0; index < arr.length; index++) {
163
+ const escapedKey = index.toString();
164
+ const value = arr[index];
165
+ if (value !== undefined) {
166
+ const escapedValue =
167
+ shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
168
+ if (escapedValue) {
169
+ terms.push(escapedKey + ':' + escapedValue);
170
+ }
171
+ }
172
+ }
173
+ return '[' + terms.join(',') + ']';
174
+ }
92
175
  };
@@ -780,46 +780,6 @@ shaka.util.Platform = class {
780
780
  shaka.log.alwaysWarn('Tizen: Error detecting screen size, default ' +
781
781
  'screen size 1920x1080.');
782
782
  }
783
- } else if (Platform.isXboxOne()) {
784
- maxResolution.width = 1920;
785
- maxResolution.height = 1080;
786
- try {
787
- let winRT = undefined;
788
-
789
- // Try to access to WinRT for WebView, if it's not defined,
790
- // try to access to WinRT for WebView2, if it's not defined either,
791
- // let it throw.
792
- if (typeof Windows !== 'undefined') {
793
- winRT = Windows;
794
- } else {
795
- winRT = chrome.webview.hostObjects.sync.Windows;
796
- }
797
-
798
- const protectionCapabilities =
799
- new winRT.Media.Protection.ProtectionCapabilities();
800
- const protectionResult =
801
- winRT.Media.Protection.ProtectionCapabilityResult;
802
- // isTypeSupported may return "maybe", which means the operation is not
803
- // completed. This means we need to retry
804
- // https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
805
- let result = null;
806
- const type =
807
- 'video/mp4;codecs="hvc1,mp4a";features="decode-res-x=3840,' +
808
- 'decode-res-y=2160,decode-bitrate=20000,decode-fps=30,' +
809
- 'decode-bpc=10,display-res-x=3840,display-res-y=2160,' +
810
- 'display-bpc=8"';
811
- const keySystem = 'com.microsoft.playready.recommendation';
812
- do {
813
- result = protectionCapabilities.isTypeSupported(type, keySystem);
814
- } while (result === protectionResult.maybe);
815
- if (result === protectionResult.probably) {
816
- maxResolution.width = 3840;
817
- maxResolution.height = 2160;
818
- }
819
- } catch (e) {
820
- shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
821
- 'screen size 1920x1080.');
822
- }
823
783
  } else if (Platform.isWebOS()) {
824
784
  try {
825
785
  const deviceInfo =
@@ -866,6 +826,54 @@ shaka.util.Platform = class {
866
826
  maxResolution.width = 1920;
867
827
  maxResolution.height = 1080;
868
828
  }
829
+ } else {
830
+ // For Xbox and UWP apps.
831
+ let winRT = undefined;
832
+ try {
833
+ // Try to access to WinRT for WebView, if it's not defined,
834
+ // try to access to WinRT for WebView2, if it's not defined either,
835
+ // let it throw.
836
+ if (typeof Windows !== 'undefined') {
837
+ winRT = Windows;
838
+ } else {
839
+ winRT = chrome.webview.hostObjects.sync.Windows;
840
+ }
841
+ } catch (e) {}
842
+ if (winRT) {
843
+ maxResolution.width = 1920;
844
+ maxResolution.height = 1080;
845
+ try {
846
+ const protectionCapabilities =
847
+ new winRT.Media.Protection.ProtectionCapabilities();
848
+ const protectionResult =
849
+ winRT.Media.Protection.ProtectionCapabilityResult;
850
+ // isTypeSupported may return "maybe", which means the operation
851
+ // is not completed. This means we need to retry
852
+ // https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
853
+ let result = null;
854
+ const type =
855
+ 'video/mp4;codecs="hvc1,mp4a";features="decode-res-x=3840,' +
856
+ 'decode-res-y=2160,decode-bitrate=20000,decode-fps=30,' +
857
+ 'decode-bpc=10,display-res-x=3840,display-res-y=2160,' +
858
+ 'display-bpc=8"';
859
+ const keySystem = 'com.microsoft.playready.recommendation';
860
+ do {
861
+ result = protectionCapabilities.isTypeSupported(type, keySystem);
862
+ } while (result === protectionResult.maybe);
863
+ if (result === protectionResult.probably) {
864
+ maxResolution.width = 3840;
865
+ maxResolution.height = 2160;
866
+ }
867
+ } catch (e) {
868
+ shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
869
+ 'screen size 1920x1080.');
870
+ }
871
+ } else if (Platform.isXboxOne()) {
872
+ maxResolution.width = 1920;
873
+ maxResolution.height = 1080;
874
+ shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
875
+ 'screen size 1920x1080.');
876
+ }
869
877
  }
870
878
  return maxResolution;
871
879
  }