shaka-player 3.1.3 → 3.1.4

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 (94) hide show
  1. package/AUTHORS +3 -0
  2. package/CHANGELOG.md +56 -0
  3. package/CONTRIBUTORS +3 -0
  4. package/build/generateExterns.js +1 -1
  5. package/demo/common/assets.js +1 -1
  6. package/dist/controls.css +2 -2
  7. package/dist/controls.css.map +1 -1
  8. package/dist/demo.compiled.debug.js +1 -1
  9. package/dist/demo.compiled.js +1 -1
  10. package/dist/deps.js +1 -1
  11. package/dist/locales.js +35 -35
  12. package/dist/receiver.compiled.debug.js +1 -1
  13. package/dist/receiver.compiled.js +1 -1
  14. package/dist/shaka-player.compiled.d.ts +1 -1
  15. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  16. package/dist/shaka-player.compiled.debug.externs.js +1 -1
  17. package/dist/shaka-player.compiled.debug.js +187 -183
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.externs.js +1 -1
  20. package/dist/shaka-player.compiled.js +799 -799
  21. package/dist/shaka-player.compiled.map +2 -2
  22. package/dist/shaka-player.ui.d.ts +1 -1
  23. package/dist/shaka-player.ui.debug.d.ts +1 -1
  24. package/dist/shaka-player.ui.debug.externs.js +3 -3
  25. package/dist/shaka-player.ui.debug.js +264 -260
  26. package/dist/shaka-player.ui.debug.map +4 -4
  27. package/dist/shaka-player.ui.externs.js +3 -3
  28. package/dist/shaka-player.ui.js +896 -895
  29. package/dist/shaka-player.ui.map +4 -4
  30. package/docs/tutorials/fairplay.md +1 -1
  31. package/karma.conf.js +1 -0
  32. package/lib/ads/client_side_ad_manager.js +46 -86
  33. package/lib/ads/server_side_ad_manager.js +7 -10
  34. package/lib/cast/cast_proxy.js +3 -2
  35. package/lib/cast/cast_receiver.js +2 -1
  36. package/lib/cast/cast_sender.js +2 -2
  37. package/lib/dash/dash_parser.js +93 -2
  38. package/lib/dash/segment_list.js +1 -1
  39. package/lib/dash/segment_template.js +1 -1
  40. package/lib/hls/hls_parser.js +30 -17
  41. package/lib/media/drm_engine.js +7 -0
  42. package/lib/media/gap_jumping_controller.js +4 -2
  43. package/lib/media/media_source_engine.js +1 -2
  44. package/lib/media/streaming_engine.js +2 -1
  45. package/lib/net/networking_engine.js +2 -1
  46. package/lib/player.js +22 -22
  47. package/lib/polyfill/media_capabilities.js +7 -1
  48. package/lib/polyfill/mediasource.js +6 -0
  49. package/lib/polyfill/orientation.js +1 -1
  50. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  51. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  52. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  53. package/lib/text/mp4_ttml_parser.js +3 -0
  54. package/lib/text/text_engine.js +14 -2
  55. package/lib/text/ttml_text_parser.js +5 -0
  56. package/lib/text/ui_text_displayer.js +85 -37
  57. package/lib/util/fake_event.js +53 -7
  58. package/lib/util/periods.js +4 -1
  59. package/lib/util/platform.js +13 -1
  60. package/lib/util/stream_utils.js +13 -0
  61. package/package.json +1 -1
  62. package/test/cast/cast_proxy_unit.js +12 -6
  63. package/test/cast/cast_utils_unit.js +1 -1
  64. package/test/dash/dash_parser_manifest_unit.js +187 -1
  65. package/test/hls/hls_parser_unit.js +5 -1
  66. package/test/media/closed_caption_parser_unit.js +6 -1
  67. package/test/media/drm_engine_unit.js +43 -0
  68. package/test/player_integration.js +38 -0
  69. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  70. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  71. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  72. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  73. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  74. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  75. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  76. package/test/test/assets/3675/dash_0.mpd +30 -0
  77. package/test/test/assets/3675/dash_1.mpd +30 -0
  78. package/test/test/assets/3675/dash_2.mpd +30 -0
  79. package/test/test/assets/3675/dash_3.mpd +30 -0
  80. package/test/test/assets/3675/dash_4.mpd +30 -0
  81. package/test/test/assets/3675/dash_5.mpd +26 -0
  82. package/test/test/assets/3675/init-stream0.m4s +0 -0
  83. package/test/test/util/fake_ad_manager.js +1 -1
  84. package/test/text/ttml_text_parser_unit.js +9 -0
  85. package/test/text/ui_text_displayer_unit.js +29 -17
  86. package/test/ui/localization_unit.js +15 -14
  87. package/test/ui/text_displayer_layout_unit.js +13 -1
  88. package/test/util/stream_utils_unit.js +35 -0
  89. package/ui/cast_button.js +2 -3
  90. package/ui/controls.js +4 -6
  91. package/ui/less/containers.less +3 -7
  92. package/ui/locales/ru.json +6 -6
  93. package/ui/localization.js +28 -51
  94. package/ui/pip_button.js +2 -3
@@ -31,7 +31,7 @@ player.configure('drm.initDataTransform', (initData, initDataType) => {
31
31
 
32
32
  // 'initData' is a buffer containing an 'skd://' URL as a UTF-8 string.
33
33
  const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData);
34
- const contentId = getMyContentId(sdkUri);
34
+ const contentId = getMyContentId(skdUri);
35
35
  const cert = player.drmInfo().serverCertificate;
36
36
  return shaka.util.FairPlayUtils.initDataTransform(initData, contentId, cert);
37
37
  });
package/karma.conf.js CHANGED
@@ -140,6 +140,7 @@ module.exports = (config) => {
140
140
  included: false,
141
141
  },
142
142
  {pattern: 'test/test/assets/*', included: false},
143
+ {pattern: 'test/test/assets/3675/*', included: false},
143
144
  {pattern: 'dist/shaka-player.ui.js', included: false},
144
145
  {pattern: 'dist/locales.js', included: false},
145
146
  {pattern: 'demo/locales/en.json', included: false},
@@ -138,7 +138,7 @@ shaka.ads.ClientSideAdManager = class {
138
138
  // Remove ad breaks from the timeline
139
139
  this.onEvent_(
140
140
  new shaka.util.FakeEvent(shaka.ads.AdManager.CUEPOINTS_CHANGED,
141
- {'cuepoints': []}));
141
+ (new Map()).set('cuepoints', [])));
142
142
  }
143
143
 
144
144
 
@@ -151,17 +151,14 @@ shaka.ads.ClientSideAdManager = class {
151
151
 
152
152
  const now = Date.now() / 1000;
153
153
  const loadTime = now - this.requestAdsStartTime_;
154
- this.onEvent_(
155
- new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED,
156
- {'loadTime': loadTime}));
154
+ this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED,
155
+ (new Map()).set('loadTime', loadTime)));
157
156
 
158
157
  this.imaAdsManager_ = e.getAdsManager(this.video_);
159
158
 
160
159
  this.onEvent_(new shaka.util.FakeEvent(
161
160
  shaka.ads.AdManager.IMA_AD_MANAGER_LOADED,
162
- {
163
- 'imaAdManager': this.imaAdsManager_,
164
- }));
161
+ (new Map()).set('imaAdManager', this.imaAdsManager_)));
165
162
 
166
163
  const cuePointStarts = this.imaAdsManager_.getCuePoints();
167
164
  if (cuePointStarts.length) {
@@ -172,9 +169,9 @@ shaka.ads.ClientSideAdManager = class {
172
169
  cuePoints.push(shakaCuePoint);
173
170
  }
174
171
 
175
- this.onEvent_(
176
- new shaka.util.FakeEvent(shaka.ads.AdManager.CUEPOINTS_CHANGED,
177
- {'cuepoints': cuePoints}));
172
+ this.onEvent_(new shaka.util.FakeEvent(
173
+ shaka.ads.AdManager.CUEPOINTS_CHANGED,
174
+ (new Map()).set('cuepoints', cuePoints)));
178
175
  }
179
176
 
180
177
  this.addImaEventListeners_();
@@ -224,6 +221,15 @@ shaka.ads.ClientSideAdManager = class {
224
221
  * @private
225
222
  */
226
223
  addImaEventListeners_() {
224
+ /**
225
+ * @param {!Event} e
226
+ * @param {string} type
227
+ */
228
+ const convertEventAndSend = (e, type) => {
229
+ const data = (new Map()).set('originalEvent', e);
230
+ this.onEvent_(new shaka.util.FakeEvent(type, data));
231
+ };
232
+
227
233
  this.eventManager_.listen(this.imaAdsManager_,
228
234
  google.ima.AdErrorEvent.Type.AD_ERROR, (error) => {
229
235
  this.onAdError_(/** @type {!google.ima.AdErrorEvent} */ (error));
@@ -241,30 +247,22 @@ shaka.ads.ClientSideAdManager = class {
241
247
 
242
248
  this.eventManager_.listen(this.imaAdsManager_,
243
249
  google.ima.AdEvent.Type.FIRST_QUARTILE, (e) => {
244
- this.onEvent_(
245
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_FIRST_QUARTILE,
246
- {'originalEvent': e}));
250
+ convertEventAndSend(e, shaka.ads.AdManager.AD_FIRST_QUARTILE);
247
251
  });
248
252
 
249
253
  this.eventManager_.listen(this.imaAdsManager_,
250
254
  google.ima.AdEvent.Type.MIDPOINT, (e) => {
251
- this.onEvent_(
252
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_MIDPOINT,
253
- {'originalEvent': e}));
255
+ convertEventAndSend(e, shaka.ads.AdManager.AD_MIDPOINT);
254
256
  });
255
257
 
256
258
  this.eventManager_.listen(this.imaAdsManager_,
257
259
  google.ima.AdEvent.Type.THIRD_QUARTILE, (e) => {
258
- this.onEvent_(
259
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_THIRD_QUARTILE,
260
- {'originalEvent': e}));
260
+ convertEventAndSend(e, shaka.ads.AdManager.AD_THIRD_QUARTILE);
261
261
  });
262
262
 
263
263
  this.eventManager_.listen(this.imaAdsManager_,
264
264
  google.ima.AdEvent.Type.COMPLETE, (e) => {
265
- this.onEvent_(
266
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_COMPLETE,
267
- {'originalEvent': e}));
265
+ convertEventAndSend(e, shaka.ads.AdManager.AD_COMPLETE);
268
266
  });
269
267
 
270
268
  this.eventManager_.listen(this.imaAdsManager_,
@@ -279,140 +277,102 @@ shaka.ads.ClientSideAdManager = class {
279
277
 
280
278
  this.eventManager_.listen(this.imaAdsManager_,
281
279
  google.ima.AdEvent.Type.SKIPPED, (e) => {
282
- this.onEvent_(
283
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_SKIPPED,
284
- {'originalEvent': e}));
280
+ convertEventAndSend(e, shaka.ads.AdManager.AD_SKIPPED);
285
281
  });
286
282
 
287
283
  this.eventManager_.listen(this.imaAdsManager_,
288
284
  google.ima.AdEvent.Type.VOLUME_CHANGED, (e) => {
289
- this.onEvent_(
290
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_VOLUME_CHANGED,
291
- {'originalEvent': e}));
285
+ convertEventAndSend(e, shaka.ads.AdManager.AD_VOLUME_CHANGED);
292
286
  });
293
287
 
294
288
  this.eventManager_.listen(this.imaAdsManager_,
295
289
  google.ima.AdEvent.Type.VOLUME_MUTED, (e) => {
296
- this.onEvent_(
297
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_MUTED,
298
- {'originalEvent': e}));
290
+ convertEventAndSend(e, shaka.ads.AdManager.AD_MUTED);
299
291
  });
300
292
 
301
293
  this.eventManager_.listen(this.imaAdsManager_,
302
294
  google.ima.AdEvent.Type.PAUSED, (e) => {
303
295
  goog.asserts.assert(this.ad_ != null, 'Ad should not be null!');
304
296
  this.ad_.setPaused(true);
305
- this.onEvent_(
306
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_PAUSED,
307
- {'originalEvent': e}));
297
+ convertEventAndSend(e, shaka.ads.AdManager.AD_PAUSED);
308
298
  });
309
299
 
310
300
  this.eventManager_.listen(this.imaAdsManager_,
311
301
  google.ima.AdEvent.Type.RESUMED, (e) => {
312
302
  goog.asserts.assert(this.ad_ != null, 'Ad should not be null!');
313
303
  this.ad_.setPaused(false);
314
- this.onEvent_(
315
- new shaka.util.FakeEvent(shaka.ads.AdManager.AD_RESUMED,
316
- {'originalEvent': e}));
304
+ convertEventAndSend(e, shaka.ads.AdManager.AD_RESUMED);
317
305
  });
318
306
 
319
307
  this.eventManager_.listen(this.imaAdsManager_,
320
308
  google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, (e) => {
321
309
  goog.asserts.assert(this.ad_ != null, 'Ad should not be null!');
322
- this.onEvent_(new shaka.util.FakeEvent(
323
- shaka.ads.AdManager.AD_SKIP_STATE_CHANGED,
324
- {'originalEvent': e}));
310
+ convertEventAndSend(e, shaka.ads.AdManager.AD_SKIP_STATE_CHANGED);
325
311
  });
326
312
 
327
313
  this.eventManager_.listen(this.imaAdsManager_,
328
314
  google.ima.AdEvent.Type.CLICK, (e) => {
329
- this.onEvent_(new shaka.util.FakeEvent(
330
- shaka.ads.AdManager.AD_CLICKED,
331
- {'originalEvent': e}));
315
+ convertEventAndSend(e, shaka.ads.AdManager.AD_CLICKED);
332
316
  });
333
317
 
334
318
  this.eventManager_.listen(this.imaAdsManager_,
335
319
  google.ima.AdEvent.Type.AD_PROGRESS, (e) => {
336
- this.onEvent_(new shaka.util.FakeEvent(
337
- shaka.ads.AdManager.AD_PROGRESS,
338
- {'originalEvent': e}));
320
+ convertEventAndSend(e, shaka.ads.AdManager.AD_PROGRESS);
339
321
  });
340
322
 
341
323
  this.eventManager_.listen(this.imaAdsManager_,
342
324
  google.ima.AdEvent.Type.AD_BUFFERING, (e) => {
343
- this.onEvent_(new shaka.util.FakeEvent(
344
- shaka.ads.AdManager.AD_BUFFERING,
345
- {'originalEvent': e}));
325
+ convertEventAndSend(e, shaka.ads.AdManager.AD_BUFFERING);
346
326
  });
347
327
 
348
328
  this.eventManager_.listen(this.imaAdsManager_,
349
329
  google.ima.AdEvent.Type.IMPRESSION, (e) => {
350
- this.onEvent_(new shaka.util.FakeEvent(
351
- shaka.ads.AdManager.AD_IMPRESSION,
352
- {'originalEvent': e}));
330
+ convertEventAndSend(e, shaka.ads.AdManager.AD_IMPRESSION);
353
331
  });
354
332
 
355
333
  this.eventManager_.listen(this.imaAdsManager_,
356
334
  google.ima.AdEvent.Type.DURATION_CHANGE, (e) => {
357
- this.onEvent_(new shaka.util.FakeEvent(
358
- shaka.ads.AdManager.AD_DURATION_CHANGED,
359
- {'originalEvent': e}));
335
+ convertEventAndSend(e, shaka.ads.AdManager.AD_DURATION_CHANGED);
360
336
  });
361
337
 
362
338
  this.eventManager_.listen(this.imaAdsManager_,
363
339
  google.ima.AdEvent.Type.USER_CLOSE, (e) => {
364
- this.onEvent_(new shaka.util.FakeEvent(
365
- shaka.ads.AdManager.AD_CLOSED,
366
- {'originalEvent': e}));
340
+ convertEventAndSend(e, shaka.ads.AdManager.AD_CLOSED);
367
341
  });
368
342
 
369
343
  this.eventManager_.listen(this.imaAdsManager_,
370
344
  google.ima.AdEvent.Type.LOADED, (e) => {
371
- this.onEvent_(new shaka.util.FakeEvent(
372
- shaka.ads.AdManager.AD_LOADED,
373
- {'originalEvent': e}));
345
+ convertEventAndSend(e, shaka.ads.AdManager.AD_LOADED);
374
346
  });
375
347
 
376
348
  this.eventManager_.listen(this.imaAdsManager_,
377
349
  google.ima.AdEvent.Type.ALL_ADS_COMPLETED, (e) => {
378
- this.onEvent_(new shaka.util.FakeEvent(
379
- shaka.ads.AdManager.ALL_ADS_COMPLETED,
380
- {'originalEvent': e}));
350
+ convertEventAndSend(e, shaka.ads.AdManager.ALL_ADS_COMPLETED);
381
351
  });
382
352
 
383
353
  this.eventManager_.listen(this.imaAdsManager_,
384
354
  google.ima.AdEvent.Type.LINEAR_CHANGED, (e) => {
385
- this.onEvent_(new shaka.util.FakeEvent(
386
- shaka.ads.AdManager.AD_LINEAR_CHANGED,
387
- {'originalEvent': e}));
355
+ convertEventAndSend(e, shaka.ads.AdManager.AD_LINEAR_CHANGED);
388
356
  });
389
357
 
390
358
  this.eventManager_.listen(this.imaAdsManager_,
391
359
  google.ima.AdEvent.Type.AD_METADATA, (e) => {
392
- this.onEvent_(new shaka.util.FakeEvent(
393
- shaka.ads.AdManager.AD_METADATA,
394
- {'originalEvent': e}));
360
+ convertEventAndSend(e, shaka.ads.AdManager.AD_METADATA);
395
361
  });
396
362
 
397
363
  this.eventManager_.listen(this.imaAdsManager_,
398
364
  google.ima.AdEvent.Type.LOG, (e) => {
399
- this.onEvent_(new shaka.util.FakeEvent(
400
- shaka.ads.AdManager.AD_RECOVERABLE_ERROR,
401
- {'originalEvent': e}));
365
+ convertEventAndSend(e, shaka.ads.AdManager.AD_RECOVERABLE_ERROR);
402
366
  });
403
367
 
404
368
  this.eventManager_.listen(this.imaAdsManager_,
405
369
  google.ima.AdEvent.Type.AD_BREAK_READY, (e) => {
406
- this.onEvent_(new shaka.util.FakeEvent(
407
- shaka.ads.AdManager.AD_BREAK_READY,
408
- {'originalEvent': e}));
370
+ convertEventAndSend(e, shaka.ads.AdManager.AD_BREAK_READY);
409
371
  });
410
372
 
411
373
  this.eventManager_.listen(this.imaAdsManager_,
412
374
  google.ima.AdEvent.Type.INTERACTION, (e) => {
413
- this.onEvent_(new shaka.util.FakeEvent(
414
- shaka.ads.AdManager.AD_INTERACTION,
415
- {'originalEvent': e}));
375
+ convertEventAndSend(e, shaka.ads.AdManager.AD_INTERACTION);
416
376
  });
417
377
  }
418
378
 
@@ -426,12 +386,12 @@ shaka.ads.ClientSideAdManager = class {
426
386
 
427
387
  const imaAd = e.getAd();
428
388
  this.ad_ = new shaka.ads.ClientSideAd(imaAd, this.imaAdsManager_);
429
- this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STARTED,
430
- {
431
- 'ad': this.ad_,
432
- 'sdkAdObject': imaAd,
433
- 'originalEvent': e,
434
- }));
389
+ const data = new Map()
390
+ .set('ad', this.ad_)
391
+ .set('sdkAdObject', imaAd)
392
+ .set('originalEvent', e);
393
+ this.onEvent_(new shaka.util.FakeEvent(
394
+ shaka.ads.AdManager.AD_STARTED, data));
435
395
  this.adContainer_.setAttribute('ad-active', 'true');
436
396
  this.video_.pause();
437
397
  }
@@ -442,7 +402,7 @@ shaka.ads.ClientSideAdManager = class {
442
402
  */
443
403
  onAdComplete_(e) {
444
404
  this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED,
445
- {'originalEvent': e}));
405
+ (new Map()).set('originalEvent', e)));
446
406
  this.adContainer_.removeAttribute('ad-active');
447
407
  this.video_.play();
448
408
  }
@@ -77,9 +77,7 @@ shaka.ads.ServerSideAdManager = class {
77
77
 
78
78
  this.onEvent_(new shaka.util.FakeEvent(
79
79
  shaka.ads.AdManager.IMA_STREAM_MANAGER_LOADED,
80
- {
81
- 'imaStreamManager': this.streamManager_,
82
- }));
80
+ (new Map()).set('imaStreamManager', this.streamManager_)));
83
81
 
84
82
  // Events
85
83
  this.eventManager_.listen(this.streamManager_,
@@ -298,7 +296,7 @@ shaka.ads.ServerSideAdManager = class {
298
296
  }
299
297
 
300
298
  this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STARTED,
301
- {'ad': this.ad_}));
299
+ (new Map()).set('ad', this.ad_)));
302
300
  this.adContainer_.setAttribute('ad-active', 'true');
303
301
  }
304
302
 
@@ -324,9 +322,8 @@ shaka.ads.ServerSideAdManager = class {
324
322
  onLoaded_(e) {
325
323
  const now = Date.now() / 1000;
326
324
  const loadTime = now - this.streamRequestStartTime_;
327
- this.onEvent_(
328
- new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED,
329
- {'loadTime': loadTime}));
325
+ this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED,
326
+ (new Map()).set('loadTime', loadTime)));
330
327
 
331
328
  const streamData = e.getStreamData();
332
329
  const url = streamData.url;
@@ -386,8 +383,8 @@ shaka.ads.ServerSideAdManager = class {
386
383
  cuePoints.push(shakaCuePoint);
387
384
  }
388
385
 
389
- this.onEvent_(
390
- new shaka.util.FakeEvent(shaka.ads.AdManager.CUEPOINTS_CHANGED,
391
- {'cuepoints': cuePoints}));
386
+ this.onEvent_(new shaka.util.FakeEvent(
387
+ shaka.ads.AdManager.CUEPOINTS_CHANGED,
388
+ (new Map()).set('cuepoints', cuePoints)));
392
389
  }
393
390
  };
@@ -525,7 +525,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
525
525
  goog.asserts.assert(error instanceof shaka.util.Error,
526
526
  'Wrong error type!');
527
527
  const eventType = shaka.Player.EventName.Error;
528
- const event = new shaka.util.FakeEvent(eventType, {'detail': error});
528
+ const data = (new Map()).set('detail', error);
529
+ const event = new shaka.util.FakeEvent(eventType, data);
529
530
  this.localPlayer_.dispatchEvent(event);
530
531
  });
531
532
  }
@@ -598,7 +599,7 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
598
599
 
599
600
  // Convert this real Event into a FakeEvent for dispatch from our
600
601
  // FakeEventListener.
601
- const fakeEvent = new shaka.util.FakeEvent(event.type, event);
602
+ const fakeEvent = shaka.util.FakeEvent.fromRealEvent(event);
602
603
  this.videoEventTarget_.dispatchEvent(fakeEvent);
603
604
  }
604
605
 
@@ -407,7 +407,8 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
407
407
  goog.asserts.assert(error instanceof shaka.util.Error,
408
408
  'Wrong error type!');
409
409
  const eventType = shaka.Player.EventName.Error;
410
- const event = new shaka.util.FakeEvent(eventType, {'detail': error});
410
+ const data = (new Map()).set('detail', error);
411
+ const event = new shaka.util.FakeEvent(eventType, data);
411
412
  // Only dispatch the event if the player still exists.
412
413
  if (this.player_) {
413
414
  this.player_.dispatchEvent(event);
@@ -647,7 +647,7 @@ shaka.cast.CastSender = class {
647
647
  case 'event': {
648
648
  const targetName = message['targetName'];
649
649
  const event = message['event'];
650
- const fakeEvent = new shaka.util.FakeEvent(event['type'], event);
650
+ const fakeEvent = shaka.util.FakeEvent.fromRealEvent(event);
651
651
  this.onRemoteEvent_(targetName, fakeEvent);
652
652
  break;
653
653
  }
@@ -725,7 +725,7 @@ shaka.cast.CastSender = class {
725
725
 
726
726
  // Dispatch it through the Player proxy
727
727
  const fakeEvent = new shaka.util.FakeEvent(
728
- 'error', {'detail': shakaError});
728
+ 'error', (new Map()).set('detail', shakaError));
729
729
  this.onRemoteEvent_('player', fakeEvent);
730
730
 
731
731
  // Force this session to disconnect and transfer playback to the local
@@ -95,6 +95,18 @@ shaka.dash.DashParser = class {
95
95
  /** @private {!shaka.util.OperationManager} */
96
96
  this.operationManager_ = new shaka.util.OperationManager();
97
97
 
98
+ /**
99
+ * Largest period start time seen.
100
+ * @private {?number}
101
+ */
102
+ this.largestPeriodStartTime_ = null;
103
+
104
+ /**
105
+ * Period IDs seen in previous manifest.
106
+ * @private {!Array.<number>}
107
+ */
108
+ this.lastManifestUpdatePeriodIds_ = [];
109
+
98
110
  /**
99
111
  * The minimum of the availabilityTimeOffset values among the adaptation
100
112
  * sets.
@@ -533,6 +545,7 @@ shaka.dash.DashParser = class {
533
545
  for (const {i, item: elem, next} of enumerate(periodNodes)) {
534
546
  const start = /** @type {number} */ (
535
547
  XmlUtils.parseAttr(elem, 'start', XmlUtils.parseDuration, prevEnd));
548
+ const periodId = elem.id;
536
549
  const givenDuration =
537
550
  XmlUtils.parseAttr(elem, 'duration', XmlUtils.parseDuration);
538
551
 
@@ -566,6 +579,42 @@ shaka.dash.DashParser = class {
566
579
  periodDuration = givenDuration;
567
580
  }
568
581
 
582
+ /**
583
+ * This is to improve robustness when the player observes manifest with
584
+ * past periods that are inconsistent to previous ones.
585
+ *
586
+ * This may happen when a CDN or proxy server switches its upstream from
587
+ * one encoder to another redundant encoder.
588
+ *
589
+ * Skip periods that match all of the following criteria:
590
+ * - Start time is earlier than latest period start time ever seen
591
+ * - Period ID is never seen in the previous manifest
592
+ * - Not the last period in the manifest
593
+ *
594
+ * Periods that meet the aforementioned criteria are considered invalid
595
+ * and should be safe to discard.
596
+ */
597
+
598
+ if (this.largestPeriodStartTime_ !== null &&
599
+ periodId !== null && start !== null &&
600
+ start < this.largestPeriodStartTime_ &&
601
+ !this.lastManifestUpdatePeriodIds_.includes(periodId) &&
602
+ i + 1 != periodNodes.length) {
603
+ shaka.log.debug(
604
+ `Skipping Period with ID ${periodId} as its start time is smaller` +
605
+ ' than the largest period start time that has been seen, and ID ' +
606
+ 'is unseen before');
607
+ continue;
608
+ }
609
+
610
+
611
+ // Save maximum period start time if it is the last period
612
+ if (start !== null &&
613
+ (this.largestPeriodStartTime_ === null ||
614
+ start > this.largestPeriodStartTime_)) {
615
+ this.largestPeriodStartTime_ = start;
616
+ }
617
+
569
618
  // Parse child nodes.
570
619
  const info = {
571
620
  start: start,
@@ -597,6 +646,9 @@ shaka.dash.DashParser = class {
597
646
  prevEnd = start + periodDuration;
598
647
  } // end of period parsing loop
599
648
 
649
+ // Replace previous seen periods with the current one.
650
+ this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
651
+
600
652
  if (presentationDuration != null) {
601
653
  if (prevEnd != presentationDuration) {
602
654
  shaka.log.warning(
@@ -808,6 +860,25 @@ shaka.dash.DashParser = class {
808
860
  }
809
861
  }
810
862
 
863
+ // Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
864
+ let videoRange;
865
+ const videoRangeScheme = 'urn:mpeg:mpegB:cicp:TransferCharacteristics';
866
+ const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
867
+ switch (cicp) {
868
+ case 1:
869
+ case 6:
870
+ case 13:
871
+ case 14:
872
+ case 15:
873
+ return 'SDR';
874
+ case 16:
875
+ return 'PQ';
876
+ case 18:
877
+ return 'HLG';
878
+ }
879
+ return undefined;
880
+ };
881
+
811
882
  const essentialProperties =
812
883
  XmlUtils.findChildren(elem, 'EssentialProperty');
813
884
  // ID of real AdaptationSet if this is a trick mode set:
@@ -817,11 +888,26 @@ shaka.dash.DashParser = class {
817
888
  const schemeId = prop.getAttribute('schemeIdUri');
818
889
  if (schemeId == 'http://dashif.org/guidelines/trickmode') {
819
890
  trickModeFor = prop.getAttribute('value');
891
+ } else if (schemeId == videoRangeScheme) {
892
+ videoRange = getVideoRangeFromTransferCharacteristicCICP(
893
+ parseInt(prop.getAttribute('value'), 10),
894
+ );
820
895
  } else {
821
896
  unrecognizedEssentialProperty = true;
822
897
  }
823
898
  }
824
899
 
900
+ const supplementalProperties =
901
+ XmlUtils.findChildren(elem, 'SupplementalProperty');
902
+ for (const prop of supplementalProperties) {
903
+ const schemeId = prop.getAttribute('schemeIdUri');
904
+ if (schemeId == videoRangeScheme) {
905
+ videoRange = getVideoRangeFromTransferCharacteristicCICP(
906
+ parseInt(prop.getAttribute('value'), 10),
907
+ );
908
+ }
909
+ }
910
+
825
911
  const accessibilities = XmlUtils.findChildren(elem, 'Accessibility');
826
912
  const LanguageUtils = shaka.util.LanguageUtils;
827
913
  const closedCaptions = new Map();
@@ -947,8 +1033,13 @@ shaka.dash.DashParser = class {
947
1033
  // Parse Representations into Streams.
948
1034
  const representations = XmlUtils.findChildren(elem, 'Representation');
949
1035
  const streams = representations.map((representation) => {
950
- return this.parseRepresentation_(context, contentProtection, kind,
951
- language, label, main, roleValues, closedCaptions, representation);
1036
+ const parsedRepresentation = this.parseRepresentation_(context,
1037
+ contentProtection, kind, language, label, main, roleValues,
1038
+ closedCaptions, representation);
1039
+ if (parsedRepresentation) {
1040
+ parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
1041
+ }
1042
+ return parsedRepresentation;
952
1043
  }).filter((s) => !!s);
953
1044
 
954
1045
  if (streams.length == 0) {
@@ -63,12 +63,12 @@ shaka.dash.SegmentList = class {
63
63
  const start = context.presentationTimeline.getSegmentAvailabilityStart();
64
64
  segmentIndex.mergeAndEvict(references, start);
65
65
  } else {
66
- context.presentationTimeline.notifySegments(references);
67
66
  segmentIndex = new shaka.media.SegmentIndex(references);
68
67
  if (id && context.dynamic) {
69
68
  segmentIndexMap[id] = segmentIndex;
70
69
  }
71
70
  }
71
+ context.presentationTimeline.notifySegments(references);
72
72
 
73
73
  if (!context.dynamic || !context.periodInfo.isLastPeriod) {
74
74
  const periodStart = context.periodInfo.start;
@@ -116,12 +116,12 @@ shaka.dash.SegmentTemplate = class {
116
116
  segmentIndex.mergeAndEvict(references,
117
117
  context.presentationTimeline.getSegmentAvailabilityStart());
118
118
  } else {
119
- context.presentationTimeline.notifySegments(references);
120
119
  segmentIndex = new shaka.media.SegmentIndex(references);
121
120
  if (id && context.dynamic) {
122
121
  segmentIndexMap[id] = segmentIndex;
123
122
  }
124
123
  }
124
+ context.presentationTimeline.notifySegments(references);
125
125
 
126
126
  if (shouldFit) {
127
127
  segmentIndex.fit(periodStart, periodEnd);
@@ -414,18 +414,16 @@ shaka.hls.HlsParser = class {
414
414
  const uri = tag.getAttributeValue('URI');
415
415
  const language = tag.getAttributeValue('LANGUAGE');
416
416
  const value = tag.getAttributeValue('VALUE');
417
- const data = {
418
- 'id': id,
419
- };
417
+ const data = (new Map()).set('id', id);
420
418
  if (uri) {
421
- data.uri =
422
- shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri);
419
+ data.set('uri',
420
+ shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
423
421
  }
424
422
  if (language) {
425
- data.language = language;
423
+ data.set('language', language);
426
424
  }
427
425
  if (value) {
428
- data.value = value;
426
+ data.set('value', value);
429
427
  }
430
428
  const event = new shaka.util.FakeEvent('sessiondata', data);
431
429
  if (this.playerInterface_) {
@@ -529,6 +527,30 @@ shaka.hls.HlsParser = class {
529
527
  }
530
528
  }
531
529
 
530
+ // Now that the content has been fit, notify segments.
531
+ this.segmentsToNotifyByStream_ = [];
532
+ const streamsToNotify = [];
533
+ for (const variant of variants) {
534
+ for (const stream of [variant.video, variant.audio]) {
535
+ if (stream) {
536
+ streamsToNotify.push(stream);
537
+ }
538
+ }
539
+ }
540
+ await Promise.all(streamsToNotify.map(async (stream) => {
541
+ await stream.createSegmentIndex();
542
+ }));
543
+ for (const stream of streamsToNotify) {
544
+ this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
545
+ }
546
+ this.notifySegments_();
547
+
548
+ // This asserts that the live edge is being calculated from segment times.
549
+ // For VOD and event streams, this check should still pass.
550
+ goog.asserts.assert(
551
+ !this.presentationTimeline_.usingPresentationStartTime(),
552
+ 'We should not be using the presentation start time in HLS!');
553
+
532
554
  this.manifest_ = {
533
555
  presentationTimeline: this.presentationTimeline_,
534
556
  variants,
@@ -1151,7 +1173,7 @@ shaka.hls.HlsParser = class {
1151
1173
  // "AUTOSELECT=YES". A value of "AUTOSELECT=NO" would imply that it may
1152
1174
  // only be selected explicitly by the user, and we don't have a way to
1153
1175
  // represent that in our model.
1154
- const defaultAttrValue = tag.getAttribute('DEFAULT');
1176
+ const defaultAttrValue = tag.getAttributeValue('DEFAULT');
1155
1177
  const primary = defaultAttrValue == 'YES';
1156
1178
 
1157
1179
  const channelsCount = type == 'audio' ? this.getChannelsCount_(tag) : null;
@@ -1538,12 +1560,6 @@ shaka.hls.HlsParser = class {
1538
1560
  }
1539
1561
 
1540
1562
  this.notifySegments_();
1541
-
1542
- // This asserts that the live edge is being calculated from segment times.
1543
- // For VOD and event streams, this check should still pass.
1544
- goog.asserts.assert(
1545
- !this.presentationTimeline_.usingPresentationStartTime(),
1546
- 'We should not be using the presentation start time in HLS!');
1547
1563
  }
1548
1564
 
1549
1565
  /**
@@ -1915,9 +1931,6 @@ shaka.hls.HlsParser = class {
1915
1931
  }
1916
1932
  }
1917
1933
 
1918
- this.segmentsToNotifyByStream_.push(references);
1919
- this.notifySegments_();
1920
-
1921
1934
  return references;
1922
1935
  }
1923
1936
 
@@ -1889,9 +1889,16 @@ shaka.media.DrmEngine = class {
1889
1889
  // audio adaptations, so we shouldn't have to worry about checking
1890
1890
  // robustness.
1891
1891
  if (drm1.keySystem == drm2.keySystem) {
1892
+ /** @type {Array<shaka.extern.InitDataOverride>} */
1892
1893
  let initData = [];
1893
1894
  initData = initData.concat(drm1.initData || []);
1894
1895
  initData = initData.concat(drm2.initData || []);
1896
+ initData = initData.filter((d, i) => {
1897
+ return d.keyId === undefined || i === initData.findIndex((d2) => {
1898
+ return d2.keyId === d.keyId;
1899
+ });
1900
+ });
1901
+
1895
1902
  const keyIds = drm1.keyIds && drm2.keyIds ?
1896
1903
  new Set([...drm1.keyIds, ...drm2.keyIds]) :
1897
1904
  drm1.keyIds || drm2.keyIds;