shaka-player 3.1.1 → 3.1.5

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 (151) hide show
  1. package/AUTHORS +7 -2
  2. package/CHANGELOG.md +199 -0
  3. package/CONTRIBUTORS +9 -3
  4. package/build/generateExterns.js +1 -1
  5. package/build/misspellings.txt +1 -0
  6. package/build/shakaBuildHelpers.py +6 -5
  7. package/build/subprocessWindowsPatch.py +103 -0
  8. package/demo/common/assets.js +1 -1
  9. package/dist/controls.css +2 -2
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.compiled.debug.js +1 -1
  12. package/dist/demo.compiled.js +1 -1
  13. package/dist/deps.js +7 -7
  14. package/dist/locales.js +35 -33
  15. package/dist/receiver.compiled.debug.js +1 -1
  16. package/dist/receiver.compiled.js +1 -1
  17. package/dist/shaka-player.compiled.d.ts +171 -3
  18. package/dist/shaka-player.compiled.debug.d.ts +171 -3
  19. package/dist/shaka-player.compiled.debug.externs.js +179 -1
  20. package/dist/shaka-player.compiled.debug.js +365 -344
  21. package/dist/shaka-player.compiled.debug.map +3 -3
  22. package/dist/shaka-player.compiled.externs.js +179 -1
  23. package/dist/shaka-player.compiled.js +806 -798
  24. package/dist/shaka-player.compiled.map +3 -3
  25. package/dist/shaka-player.ui.d.ts +171 -3
  26. package/dist/shaka-player.ui.debug.d.ts +171 -3
  27. package/dist/shaka-player.ui.debug.externs.js +181 -3
  28. package/dist/shaka-player.ui.debug.js +458 -435
  29. package/dist/shaka-player.ui.debug.map +4 -4
  30. package/dist/shaka-player.ui.externs.js +181 -3
  31. package/dist/shaka-player.ui.js +927 -918
  32. package/dist/shaka-player.ui.map +4 -4
  33. package/docs/tutorials/fairplay.md +1 -1
  34. package/docs/tutorials/faq.md +14 -0
  35. package/docs/tutorials/index.json +2 -2
  36. package/docs/tutorials/upgrade-v3.md +38 -0
  37. package/docs/tutorials/upgrade.md +3 -3
  38. package/externs/ima.js +4 -0
  39. package/externs/shaka/ads.js +2 -0
  40. package/externs/shaka/player.js +6 -0
  41. package/externs/shaka/text.js +10 -0
  42. package/karma.conf.js +1 -0
  43. package/lib/ads/ad_manager.js +29 -11
  44. package/lib/ads/client_side_ad_manager.js +76 -86
  45. package/lib/ads/server_side_ad_manager.js +26 -11
  46. package/lib/cast/cast_proxy.js +3 -2
  47. package/lib/cast/cast_receiver.js +2 -1
  48. package/lib/cast/cast_sender.js +2 -2
  49. package/lib/cea/sei_processor.js +24 -17
  50. package/lib/dash/dash_parser.js +100 -5
  51. package/lib/dash/segment_list.js +1 -1
  52. package/lib/dash/segment_template.js +7 -4
  53. package/lib/hls/hls_parser.js +44 -17
  54. package/lib/media/closed_caption_parser.js +3 -1
  55. package/lib/media/content_workarounds.js +12 -0
  56. package/lib/media/drm_engine.js +20 -1
  57. package/lib/media/gap_jumping_controller.js +8 -4
  58. package/lib/media/media_source_engine.js +1 -2
  59. package/lib/media/playhead.js +1 -1
  60. package/lib/media/presentation_timeline.js +3 -1
  61. package/lib/media/segment_reference.js +8 -0
  62. package/lib/media/streaming_engine.js +28 -19
  63. package/lib/net/networking_engine.js +2 -1
  64. package/lib/player.js +108 -54
  65. package/lib/polyfill/encryption_scheme.js +2 -0
  66. package/lib/polyfill/fullscreen.js +2 -0
  67. package/lib/polyfill/mathround.js +2 -0
  68. package/lib/polyfill/media_capabilities.js +76 -22
  69. package/lib/polyfill/mediasource.js +8 -0
  70. package/lib/polyfill/orientation.js +3 -1
  71. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  72. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  73. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  74. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  75. package/lib/polyfill/pip_webkit.js +2 -0
  76. package/lib/polyfill/storage_estimate.js +2 -0
  77. package/lib/polyfill/video_play_promise.js +2 -0
  78. package/lib/polyfill/videoplaybackquality.js +2 -0
  79. package/lib/polyfill/vttcue.js +2 -0
  80. package/lib/text/cue.js +17 -0
  81. package/lib/text/mp4_ttml_parser.js +3 -0
  82. package/lib/text/text_engine.js +14 -2
  83. package/lib/text/ttml_text_parser.js +135 -87
  84. package/lib/text/ui_text_displayer.js +233 -83
  85. package/lib/text/vtt_text_parser.js +4 -2
  86. package/lib/util/fake_event.js +53 -7
  87. package/lib/util/mp4_box_parsers.js +1 -1
  88. package/lib/util/periods.js +111 -35
  89. package/lib/util/platform.js +13 -1
  90. package/lib/util/stream_utils.js +79 -10
  91. package/lib/util/xml_utils.js +19 -0
  92. package/package.json +2 -2
  93. package/test/cast/cast_proxy_unit.js +12 -6
  94. package/test/cast/cast_receiver_integration.js +1 -1
  95. package/test/cast/cast_utils_unit.js +1 -1
  96. package/test/cea/mp4_cea_parser_unit.js +26 -0
  97. package/test/dash/dash_parser_live_unit.js +1 -0
  98. package/test/dash/dash_parser_manifest_unit.js +220 -2
  99. package/test/dash/dash_parser_segment_template_unit.js +3 -3
  100. package/test/hls/hls_parser_unit.js +5 -1
  101. package/test/media/closed_caption_parser_unit.js +20 -0
  102. package/test/media/drm_engine_unit.js +56 -0
  103. package/test/media/playhead_unit.js +27 -1
  104. package/test/media/presentation_timeline_unit.js +19 -0
  105. package/test/media/streaming_engine_integration.js +2 -1
  106. package/test/media/streaming_engine_unit.js +148 -3
  107. package/test/player_integration.js +38 -0
  108. package/test/player_unit.js +101 -0
  109. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  110. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  111. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  112. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  113. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  114. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  115. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  116. package/test/test/assets/3675/dash_0.mpd +30 -0
  117. package/test/test/assets/3675/dash_1.mpd +30 -0
  118. package/test/test/assets/3675/dash_2.mpd +30 -0
  119. package/test/test/assets/3675/dash_3.mpd +30 -0
  120. package/test/test/assets/3675/dash_4.mpd +30 -0
  121. package/test/test/assets/3675/dash_5.mpd +26 -0
  122. package/test/test/assets/3675/init-stream0.m4s +0 -0
  123. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  124. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  125. package/test/test/util/fake_ad_manager.js +4 -1
  126. package/test/test/util/simple_fakes.js +5 -3
  127. package/test/test/util/ttml_utils.js +81 -0
  128. package/test/text/mp4_ttml_parser_unit.js +11 -18
  129. package/test/text/ttml_text_parser_unit.js +542 -220
  130. package/test/text/ui_text_displayer_unit.js +95 -18
  131. package/test/text/vtt_text_parser_unit.js +17 -1
  132. package/test/ui/localization_unit.js +15 -14
  133. package/test/ui/text_displayer_layout_unit.js +52 -1
  134. package/test/util/mp4_box_parsers_unit.js +28 -0
  135. package/test/util/periods_unit.js +190 -2
  136. package/test/util/stream_utils_unit.js +59 -2
  137. package/ui/airplay_button.js +5 -0
  138. package/ui/big_play_button.js +10 -0
  139. package/ui/cast_button.js +2 -3
  140. package/ui/controls.js +8 -6
  141. package/ui/less/buttons.less +0 -4
  142. package/ui/less/containers.less +10 -5
  143. package/ui/locales/en.json +1 -0
  144. package/ui/locales/ru.json +6 -6
  145. package/ui/locales/source.json +4 -0
  146. package/ui/localization.js +28 -51
  147. package/ui/pip_button.js +2 -3
  148. package/ui/play_button.js +10 -10
  149. package/ui/small_play_button.js +12 -0
  150. package/ui/text_selection.js +1 -0
  151. package/docs/tutorials/upgrade-v2-4.md +0 -481
@@ -80,7 +80,10 @@ shaka.util.PeriodCombiner = class {
80
80
 
81
81
  /** @return {!Array.<shaka.extern.Stream>} */
82
82
  getTextStreams() {
83
- return this.textStreams_;
83
+ // Return a copy of the array because makeTextStreamsForClosedCaptions
84
+ // may make changes to the contents of the array. Those changes should not
85
+ // propagate back to the PeriodCombiner.
86
+ return this.textStreams_.slice();
84
87
  }
85
88
 
86
89
  /** @return {!Array.<shaka.extern.Stream>} */
@@ -100,6 +103,7 @@ shaka.util.PeriodCombiner = class {
100
103
  shaka.util.PeriodCombiner.filterOutAudioStreamDuplicates_(periods);
101
104
  shaka.util.PeriodCombiner.filterOutVideoStreamDuplicates_(periods);
102
105
  shaka.util.PeriodCombiner.filterOutTextStreamDuplicates_(periods);
106
+ shaka.util.PeriodCombiner.filterOutImageStreamDuplicates_(periods);
103
107
 
104
108
  // Optimization: for single-period VOD, do nothing. This makes sure
105
109
  // single-period DASH content will be 100% accurately represented in the
@@ -142,12 +146,18 @@ shaka.util.PeriodCombiner = class {
142
146
  const imageStreamsPerPeriod = periods.map(
143
147
  (period) => period.imageStreams);
144
148
 
145
- // It's okay to have a period with no text, but our algorithm fails on any
146
- // period without matching streams. So we add dummy text streams to each
147
- // period. Since we combine text streams by language, we might need a
148
- // dummy even in periods with text streams already.
149
+ // It's okay to have a period with no text or images, but our algorithm
150
+ // fails on any period without matching streams. So we add dummy streams
151
+ // to each period. Since we combine text streams by language and image
152
+ // streams by resolution, we might need a dummy even in periods with these
153
+ // streams already.
149
154
  for (const textStreams of textStreamsPerPeriod) {
150
- textStreams.push(shaka.util.PeriodCombiner.dummyTextStream_());
155
+ textStreams.push(shaka.util.PeriodCombiner.dummyStream_(
156
+ ContentType.TEXT));
157
+ }
158
+ for (const imageStreams of imageStreamsPerPeriod) {
159
+ imageStreams.push(shaka.util.PeriodCombiner.dummyStream_(
160
+ ContentType.IMAGE));
151
161
  }
152
162
 
153
163
  await shaka.util.PeriodCombiner.combine_(
@@ -342,6 +352,36 @@ shaka.util.PeriodCombiner = class {
342
352
  }
343
353
  }
344
354
 
355
+ /**
356
+ * @param {!Array.<shaka.util.PeriodCombiner.Period>} periods
357
+ * @private
358
+ */
359
+ static filterOutImageStreamDuplicates_(periods) {
360
+ // Two image streams are considered to be duplicates of
361
+ // one another if their ids are different, but all the other
362
+ // information is the same.
363
+ for (const period of periods) {
364
+ const filteredImages = [];
365
+ for (const i1 of period.imageStreams) {
366
+ let duplicate = false;
367
+ for (const i2 of filteredImages) {
368
+ if (i1.id != i2.id &&
369
+ i1.width == i2.width &&
370
+ i1.codecs == i2.codecs &&
371
+ i1.mimeType == i2.mimeType) {
372
+ duplicate = true;
373
+ }
374
+ }
375
+
376
+ if (!duplicate) {
377
+ filteredImages.push(i1);
378
+ }
379
+ }
380
+
381
+ period.imageStreams = filteredImages;
382
+ }
383
+ }
384
+
345
385
  /**
346
386
  * Stitch together DB streams across periods, taking a mix of stream types.
347
387
  * The offline database does not separate these by type.
@@ -368,13 +408,21 @@ shaka.util.PeriodCombiner = class {
368
408
  (streams) => streams.filter((s) => s.type == ContentType.VIDEO));
369
409
  const textStreamDbsPerPeriod = streamDbsPerPeriod.map(
370
410
  (streams) => streams.filter((s) => s.type == ContentType.TEXT));
371
-
372
- // It's okay to have a period with no text, but our algorithm fails on any
373
- // period without matching streams. So we add dummy text streams to each
374
- // period. Since we combine text streams by language, we might need a
375
- // dummy even in periods with text streams already.
411
+ const imageStreamDbsPerPeriod = streamDbsPerPeriod.map(
412
+ (streams) => streams.filter((s) => s.type == ContentType.IMAGE));
413
+
414
+ // It's okay to have a period with no text or images, but our algorithm
415
+ // fails on any period without matching streams. So we add dummy streams to
416
+ // each period. Since we combine text streams by language and image streams
417
+ // by resolution, we might need a dummy even in periods with these streams
418
+ // already.
376
419
  for (const textStreams of textStreamDbsPerPeriod) {
377
- textStreams.push(shaka.util.PeriodCombiner.dummyTextStreamDB_());
420
+ textStreams.push(shaka.util.PeriodCombiner.dummyStreamDB_(
421
+ ContentType.TEXT));
422
+ }
423
+ for (const imageStreams of imageStreamDbsPerPeriod) {
424
+ imageStreams.push(shaka.util.PeriodCombiner.dummyStreamDB_(
425
+ ContentType.IMAGE));
378
426
  }
379
427
 
380
428
  const combinedAudioStreamDbs = await shaka.util.PeriodCombiner.combine_(
@@ -398,6 +446,13 @@ shaka.util.PeriodCombiner = class {
398
446
  shaka.util.PeriodCombiner.cloneStreamDB_,
399
447
  shaka.util.PeriodCombiner.concatenateStreamDBs_);
400
448
 
449
+ const combinedImageStreamDbs = await shaka.util.PeriodCombiner.combine_(
450
+ /* outputStreams= */ [],
451
+ imageStreamDbsPerPeriod,
452
+ /* firstNewPeriodIndex= */ 0,
453
+ shaka.util.PeriodCombiner.cloneStreamDB_,
454
+ shaka.util.PeriodCombiner.concatenateStreamDBs_);
455
+
401
456
  // Recreate variantIds from scratch in the output.
402
457
  // HLS content is always single-period, so the early return at the top of
403
458
  // this method would catch all HLS content. DASH content stored with v3.0
@@ -426,7 +481,8 @@ shaka.util.PeriodCombiner = class {
426
481
 
427
482
  return combinedVideoStreamDbs
428
483
  .concat(combinedAudioStreamDbs)
429
- .concat(combinedTextStreamDbs);
484
+ .concat(combinedTextStreamDbs)
485
+ .concat(combinedImageStreamDbs);
430
486
  }
431
487
 
432
488
  /**
@@ -506,9 +562,12 @@ shaka.util.PeriodCombiner = class {
506
562
 
507
563
  for (const unusedStreams of unusedStreamsPerPeriod) {
508
564
  for (const stream of unusedStreams) {
509
- if (stream.type == ContentType.TEXT && !stream.language) {
510
- // This is one of our dummy text streams, so ignore it. We may not
511
- // use them all, and that's fine.
565
+ const isDummyText = stream.type == ContentType.TEXT && !stream.language;
566
+ const isDummyImage = stream.type == ContentType.IMAGE &&
567
+ !stream.tilesLayout;
568
+ if (isDummyText || isDummyImage) {
569
+ // This is one of our dummy streams, so ignore it. We may not use
570
+ // them all, and that's fine.
512
571
  continue;
513
572
  }
514
573
  // If this stream has a different codec/MIME than any other stream,
@@ -525,6 +584,8 @@ shaka.util.PeriodCombiner = class {
525
584
 
526
585
  // Any other unused stream is likely a bug in our algorithm, so throw
527
586
  // an error.
587
+ shaka.log.error('Unused stream in period-flattening!',
588
+ stream, outputStreams);
528
589
  throw new shaka.util.Error(
529
590
  shaka.util.Error.Severity.CRITICAL,
530
591
  shaka.util.Error.Category.MANIFEST,
@@ -563,6 +624,8 @@ shaka.util.PeriodCombiner = class {
563
624
 
564
625
  if (!matches) {
565
626
  // We were unable to extend this output stream.
627
+ shaka.log.error('No matches extending output stream!',
628
+ outputStream, streamsPerPeriod);
566
629
  return false;
567
630
  }
568
631
 
@@ -1066,7 +1129,7 @@ shaka.util.PeriodCombiner = class {
1066
1129
  return true;
1067
1130
  }
1068
1131
 
1069
- // Otherwise, compare the streams' characteristics to detecrmine the best
1132
+ // Otherwise, compare the streams' characteristics to determine the best
1070
1133
  // match.
1071
1134
 
1072
1135
  // The most important thing is language. In some cases, we will accept a
@@ -1164,6 +1227,7 @@ shaka.util.PeriodCombiner = class {
1164
1227
  }
1165
1228
  }
1166
1229
 
1230
+ // If the result of each comparison was inconclusive, default to false.
1167
1231
  return false;
1168
1232
  }
1169
1233
 
@@ -1188,7 +1252,7 @@ shaka.util.PeriodCombiner = class {
1188
1252
  return true;
1189
1253
  }
1190
1254
 
1191
- // Otherwise, compare the streams' characteristics to detecrmine the best
1255
+ // Otherwise, compare the streams' characteristics to determine the best
1192
1256
  // match.
1193
1257
 
1194
1258
  // Take the video with the closest resolution to the output.
@@ -1234,6 +1298,7 @@ shaka.util.PeriodCombiner = class {
1234
1298
  }
1235
1299
  }
1236
1300
 
1301
+ // If the result of each comparison was inconclusive, default to false.
1237
1302
  return false;
1238
1303
  }
1239
1304
 
@@ -1258,7 +1323,7 @@ shaka.util.PeriodCombiner = class {
1258
1323
  return true;
1259
1324
  }
1260
1325
 
1261
- // Otherwise, compare the streams' characteristics to detecrmine the best
1326
+ // Otherwise, compare the streams' characteristics to determine the best
1262
1327
  // match.
1263
1328
 
1264
1329
  // The most important thing is language. In some cases, we will accept a
@@ -1319,6 +1384,7 @@ shaka.util.PeriodCombiner = class {
1319
1384
  return true;
1320
1385
  }
1321
1386
 
1387
+ // If the result of each comparison was inconclusive, default to false.
1322
1388
  return false;
1323
1389
  }
1324
1390
 
@@ -1334,6 +1400,8 @@ shaka.util.PeriodCombiner = class {
1334
1400
  * @private
1335
1401
  */
1336
1402
  static isImageStreamBetterMatch_(outputStream, best, candidate) {
1403
+ const {BETTER, EQUAL, WORSE} = shaka.util.PeriodCombiner.BetterOrWorse;
1404
+
1337
1405
  // If the output stream was based on the candidate stream, the candidate
1338
1406
  // stream should be considered a better match. We can check this by
1339
1407
  // comparing their ids.
@@ -1341,31 +1409,38 @@ shaka.util.PeriodCombiner = class {
1341
1409
  return true;
1342
1410
  }
1343
1411
 
1344
- // If the candidate has the same MIME type, upgrade to the
1345
- // candidate. It's not required that image streams use the same format
1346
- // across periods, but it's a helpful signal.
1347
- if (candidate.mimeType == outputStream.mimeType) {
1412
+ // Take the image with the closest resolution to the output.
1413
+ const resolutionBetterOrWorse =
1414
+ shaka.util.PeriodCombiner.compareClosestPreferLower(
1415
+ outputStream.width * outputStream.height,
1416
+ best.width * best.height,
1417
+ candidate.width * candidate.height);
1418
+ if (resolutionBetterOrWorse == BETTER) {
1348
1419
  return true;
1420
+ } else if (resolutionBetterOrWorse == WORSE) {
1421
+ return false;
1349
1422
  }
1350
1423
 
1424
+ // If the result of each comparison was inconclusive, default to false.
1351
1425
  return false;
1352
1426
  }
1353
1427
 
1354
1428
  /**
1355
- * Create a dummy text StreamDB to fill in periods with no text, to avoid
1356
- * failing the general flattening algorithm.
1429
+ * Create a dummy StreamDB to fill in periods that are missing a certain type,
1430
+ * to avoid failing the general flattening algorithm. This won't be used for
1431
+ * audio or video, since those are strictly required in all periods if they
1432
+ * exist in any period.
1357
1433
  *
1434
+ * @param {shaka.util.ManifestParserUtils.ContentType} type
1358
1435
  * @return {shaka.extern.StreamDB}
1359
1436
  * @private
1360
1437
  */
1361
- static dummyTextStreamDB_() {
1362
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
1363
-
1438
+ static dummyStreamDB_(type) {
1364
1439
  return {
1365
1440
  id: 0,
1366
1441
  originalId: '',
1367
1442
  primary: false,
1368
- type: ContentType.TEXT,
1443
+ type,
1369
1444
  mimeType: '',
1370
1445
  codecs: '',
1371
1446
  language: '',
@@ -1386,15 +1461,16 @@ shaka.util.PeriodCombiner = class {
1386
1461
  }
1387
1462
 
1388
1463
  /**
1389
- * Create a dummy text Stream to fill in periods with no text, to avoid
1390
- * failing the general flattening algorithm.
1464
+ * Create a dummy Stream to fill in periods that are missing a certain type,
1465
+ * to avoid failing the general flattening algorithm. This won't be used for
1466
+ * audio or video, since those are strictly required in all periods if they
1467
+ * exist in any period.
1391
1468
  *
1469
+ * @param {shaka.util.ManifestParserUtils.ContentType} type
1392
1470
  * @return {shaka.extern.Stream}
1393
1471
  * @private
1394
1472
  */
1395
- static dummyTextStream_() {
1396
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
1397
-
1473
+ static dummyStream_(type) {
1398
1474
  return {
1399
1475
  id: 0,
1400
1476
  originalId: '',
@@ -1407,7 +1483,7 @@ shaka.util.PeriodCombiner = class {
1407
1483
  keyIds: new Set(),
1408
1484
  language: '',
1409
1485
  label: null,
1410
- type: ContentType.TEXT,
1486
+ type,
1411
1487
  primary: false,
1412
1488
  trickModeVideo: null,
1413
1489
  emsgSchemeIdUris: null,
@@ -163,7 +163,6 @@ shaka.util.Platform = class {
163
163
  !shaka.util.Platform.isEdge();
164
164
  }
165
165
 
166
-
167
166
  /**
168
167
  * Check if the current platform is from Apple.
169
168
  *
@@ -179,6 +178,19 @@ shaka.util.Platform = class {
179
178
  && !shaka.util.Platform.isTizen();
180
179
  }
181
180
 
181
+ /**
182
+ * Check if the current platform is Playstation 5.
183
+ *
184
+ * Returns true on Playstation 5 browsers.
185
+ *
186
+ * Returns false for Playstation 5 browsers
187
+ *
188
+ * @return {boolean}
189
+ */
190
+ static isPS5() {
191
+ return shaka.util.Platform.userAgentContains_('PlayStation 5');
192
+ }
193
+
182
194
  /**
183
195
  * Returns a major version number for Safari, or Safari-based iOS browsers.
184
196
  *
@@ -315,7 +315,9 @@ shaka.util.StreamUtils = class {
315
315
  variant);
316
316
  return false;
317
317
  }
318
+ return true;
318
319
  }
320
+
319
321
  shaka.log.debug('DrmEngine is not initialized yet.');
320
322
  return true;
321
323
  });
@@ -335,9 +337,44 @@ shaka.util.StreamUtils = class {
335
337
  'MediaCapabilities should be valid.');
336
338
 
337
339
  await shaka.util.StreamUtils.getDecodingInfosForVariants(
338
- manifest.variants, usePersistentLicenses);
340
+ manifest.variants, usePersistentLicenses, /* srcEquals= */ false);
339
341
  manifest.variants = manifest.variants.filter((variant) => {
342
+ // See: https://github.com/google/shaka-player/issues/3860
340
343
  const video = variant.video;
344
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
345
+ if (video) {
346
+ let videoCodecs = shaka.util.StreamUtils.patchVp9(video.codecs);
347
+ // For multiplexed streams. Here we must check the audio of the
348
+ // stream to see if it is compatible.
349
+ if (video.codecs.includes(',')) {
350
+ const allCodecs = video.codecs.split(',');
351
+ videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
352
+ ContentType.VIDEO, allCodecs);
353
+ const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
354
+ ContentType.AUDIO, allCodecs);
355
+ const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
356
+ video.mimeType, audioCodecs, ContentType.AUDIO);
357
+ if (!MediaSource.isTypeSupported(audioFullType)) {
358
+ return false;
359
+ }
360
+ }
361
+ const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
362
+ video.mimeType, videoCodecs, ContentType.VIDEO);
363
+ if (!MediaSource.isTypeSupported(fullType)) {
364
+ return false;
365
+ }
366
+ }
367
+ const audio = variant.audio;
368
+ if (audio) {
369
+ const codecs =
370
+ shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
371
+ const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
372
+ audio.mimeType, codecs, ContentType.AUDIO);
373
+ if (!MediaSource.isTypeSupported(fullType)) {
374
+ return false;
375
+ }
376
+ }
377
+
341
378
  // See: https://github.com/google/shaka-player/issues/3380
342
379
  if (shaka.util.Platform.isXboxOne() && video &&
343
380
  ((video.width && video.width > 1920) ||
@@ -408,9 +445,11 @@ shaka.util.StreamUtils = class {
408
445
  *
409
446
  * @param {!Array.<shaka.extern.Variant>} variants
410
447
  * @param {boolean} usePersistentLicenses
448
+ * @param {boolean} srcEquals
411
449
  * @exportDoc
412
450
  */
413
- static async getDecodingInfosForVariants(variants, usePersistentLicenses) {
451
+ static async getDecodingInfosForVariants(variants, usePersistentLicenses,
452
+ srcEquals) {
414
453
  const gotDecodingInfo = variants.some((variant) =>
415
454
  variant.decodingInfos.length);
416
455
  if (gotDecodingInfo) {
@@ -434,7 +473,7 @@ shaka.util.StreamUtils = class {
434
473
  for (const variant of variants) {
435
474
  /** @type {!Array.<!MediaDecodingConfiguration>} */
436
475
  const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_(
437
- variant, usePersistentLicenses);
476
+ variant, usePersistentLicenses, srcEquals);
438
477
 
439
478
  for (const config of decodingConfigs) {
440
479
  operations.push(getVariantDecodingInfos(variant, config));
@@ -449,17 +488,18 @@ shaka.util.StreamUtils = class {
449
488
  * results for each variant.
450
489
  * @param {!shaka.extern.Variant} variant
451
490
  * @param {boolean} usePersistentLicenses
491
+ * @param {boolean} srcEquals
452
492
  * @return {!Array.<!MediaDecodingConfiguration>}
453
493
  * @private
454
494
  */
455
- static getDecodingConfigs_(variant, usePersistentLicenses) {
495
+ static getDecodingConfigs_(variant, usePersistentLicenses, srcEquals) {
456
496
  const audio = variant.audio;
457
497
  const video = variant.video;
458
498
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
459
499
 
460
500
  /** @type {!MediaDecodingConfiguration} */
461
501
  const mediaDecodingConfig = {
462
- type: 'media-source',
502
+ type: srcEquals ? 'file' : 'media-source',
463
503
  };
464
504
 
465
505
  if (video) {
@@ -496,14 +536,23 @@ shaka.util.StreamUtils = class {
496
536
  // framerate must be greater than 0, otherwise the config is invalid.
497
537
  framerate: video.frameRate || 1,
498
538
  };
539
+ if (video.hdr) {
540
+ switch (video.hdr) {
541
+ case 'SDR':
542
+ mediaDecodingConfig.video.transferFunction = 'srgb';
543
+ break;
544
+ case 'PQ':
545
+ mediaDecodingConfig.video.transferFunction = 'pq';
546
+ break;
547
+ case 'HLG':
548
+ mediaDecodingConfig.video.transferFunction = 'hlg';
549
+ break;
550
+ }
551
+ }
499
552
  }
500
553
  if (audio) {
501
- // Some Tizen devices seem to misreport AC-3 support, but correctly
502
- // report EC-3 support. So query EC-3 as a fallback for AC-3.
503
- // See https://github.com/google/shaka-player/issues/2989 for details.
504
554
  const codecs =
505
- (audio.codecs.toLowerCase() == 'ac-3' &&
506
- shaka.util.Platform.isTizen()) ? 'ec-3' : audio.codecs;
555
+ shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
507
556
  const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
508
557
  audio.mimeType, codecs, ContentType.AUDIO);
509
558
 
@@ -614,6 +663,26 @@ shaka.util.StreamUtils = class {
614
663
  return configs;
615
664
  }
616
665
 
666
+
667
+ /**
668
+ * Generates the correct audio codec for MediaDecodingConfiguration and
669
+ * for MediaSource.isTypeSupported.
670
+ * @param {string} codecs
671
+ * @return {string}
672
+ * @private
673
+ */
674
+ static getCorrectAudioCodecs_(codecs) {
675
+ // Some Tizen devices seem to misreport AC-3 support, but correctly
676
+ // report EC-3 support. So query EC-3 as a fallback for AC-3.
677
+ // See https://github.com/google/shaka-player/issues/2989 for details.
678
+ if (shaka.util.Platform.isTizen()) {
679
+ return codecs.toLowerCase() == 'ac-3' ? 'ec-3' : codecs;
680
+ } else {
681
+ return codecs;
682
+ }
683
+ }
684
+
685
+
617
686
  /**
618
687
  * MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate vp9
619
688
  * codec strings into 'vp09...', to allow such content to play with
@@ -102,6 +102,25 @@ shaka.util.XmlUtils = class {
102
102
  }
103
103
 
104
104
 
105
+ /**
106
+ * Gets a namespace-qualified attribute.
107
+ * @param {!Element} elem The element to get from.
108
+ * @param {!Array.<string>} nsList The lis of namespace URIs.
109
+ * @param {string} name The local name of the attribute.
110
+ * @return {?string} The attribute's value, or null if not present.
111
+ */
112
+ static getAttributeNSList(elem, nsList, name) {
113
+ // Some browsers return the empty string when the attribute is missing,
114
+ // so check if it exists first. See: https://mzl.la/2L7F0UK
115
+ for (const ns of nsList) {
116
+ if (elem.hasAttributeNS(ns, name)) {
117
+ return elem.getAttributeNS(ns, name);
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+
105
124
  /**
106
125
  * Gets the text contents of a node.
107
126
  * @param {!Node} elem The XML element.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "3.1.1",
4
+ "version": "3.1.5",
5
5
  "homepage": "https://github.com/google/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -12,6 +12,7 @@
12
12
  ],
13
13
  "devDependencies": {
14
14
  "@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
15
+ "@onslip/karma-safari-launcher": "^1.0.1",
15
16
  "@teppeis/clutz": "^1.0.29-4c95e12.v20190929",
16
17
  "awesomplete": "^1.1.4",
17
18
  "babel-core": "^6.26.3",
@@ -44,7 +45,6 @@
44
45
  "karma-jasmine": "^3.3.1",
45
46
  "karma-jasmine-ajax": "^0.1.13",
46
47
  "karma-opera-launcher": "^1.0.0",
47
- "karma-safari-launcher": "^1.0.0",
48
48
  "karma-sourcemap-loader": "^0.3.7",
49
49
  "karma-spec-reporter": "0.0.32",
50
50
  "karma-webdriver-launcher": "^1.0.8",
@@ -297,7 +297,8 @@ describe('CastProxy', () => {
297
297
  'timeupdate', Util.spyFunc(proxyListener));
298
298
 
299
299
  expect(proxyListener).not.toHaveBeenCalled();
300
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
300
+ const fakeEvent = new FakeEvent(
301
+ 'timeupdate', (new Map()).set('detail', 8675309));
301
302
  mockVideo.on['timeupdate'](fakeEvent);
302
303
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
303
304
  type: 'timeupdate',
@@ -315,7 +316,8 @@ describe('CastProxy', () => {
315
316
  mockSender.hasRemoteProperties.and.returnValue(true);
316
317
 
317
318
  expect(proxyListener).not.toHaveBeenCalled();
318
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
319
+ const fakeEvent = new FakeEvent(
320
+ 'timeupdate', (new Map()).set('detail', 8675309));
319
321
  mockVideo.on['timeupdate'](fakeEvent);
320
322
  expect(proxyListener).not.toHaveBeenCalled();
321
323
  });
@@ -332,7 +334,8 @@ describe('CastProxy', () => {
332
334
  mockSender.hasRemoteProperties.and.returnValue(true);
333
335
 
334
336
  expect(proxyListener).not.toHaveBeenCalled();
335
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
337
+ const fakeEvent = new FakeEvent(
338
+ 'timeupdate', (new Map()).set('detail', 8675309));
336
339
  mockSender.onRemoteEvent('video', fakeEvent);
337
340
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
338
341
  type: 'timeupdate',
@@ -446,7 +449,8 @@ describe('CastProxy', () => {
446
449
  'buffering', Util.spyFunc(proxyListener));
447
450
 
448
451
  expect(proxyListener).not.toHaveBeenCalled();
449
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
452
+ const fakeEvent = new FakeEvent(
453
+ 'buffering', (new Map()).set('detail', 8675309));
450
454
  mockPlayer.listeners['buffering'](fakeEvent);
451
455
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
452
456
  type: 'buffering',
@@ -464,7 +468,8 @@ describe('CastProxy', () => {
464
468
  mockSender.hasRemoteProperties.and.returnValue(true);
465
469
 
466
470
  expect(proxyListener).not.toHaveBeenCalled();
467
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
471
+ const fakeEvent = new FakeEvent(
472
+ 'buffering', (new Map()).set('detail', 8675309));
468
473
  mockPlayer.listeners['buffering'](fakeEvent);
469
474
  expect(proxyListener).not.toHaveBeenCalled();
470
475
  });
@@ -481,7 +486,8 @@ describe('CastProxy', () => {
481
486
  mockSender.hasRemoteProperties.and.returnValue(true);
482
487
 
483
488
  expect(proxyListener).not.toHaveBeenCalled();
484
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
489
+ const fakeEvent = new FakeEvent(
490
+ 'buffering', (new Map()).set('detail', 8675309));
485
491
  mockSender.onRemoteEvent('player', fakeEvent);
486
492
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
487
493
  type: 'buffering',
@@ -170,7 +170,7 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
170
170
  // Check that the update message is of a reasonable size. From previous
171
171
  // testing we found that the socket would silently reject data that got
172
172
  // too big. 6KB is safely below the limit.
173
- expect(message.length).toBeLessThan(6 * 1024);
173
+ expect(message.length).toBeLessThan(7 * 1024);
174
174
  });
175
175
 
176
176
  drmIt('has reasonable average message size', async () => {
@@ -122,7 +122,7 @@ describe('CastUtils', () => {
122
122
  expect(typeof deserialized).toBe('object');
123
123
 
124
124
  // The object can be used to construct a FakeEvent.
125
- const fakeEvent = new FakeEvent(deserialized['type'], deserialized);
125
+ const fakeEvent = FakeEvent.fromRealEvent(deserialized);
126
126
 
127
127
  // The fake event has the same type and properties as the original.
128
128
  const asObj = /** @type {!Object} */ (fakeEvent);
@@ -27,6 +27,32 @@ describe('Mp4CeaParser', () => {
27
27
  ceaSegment = responses[1];
28
28
  });
29
29
 
30
+ /**
31
+ * Test only the functionality of removing EPB
32
+ * Expect removeEmu() to return the NALU with correct length
33
+ *
34
+ * Chromium VDA has a strict standard on NALU length
35
+ * It will complain about conformance if the array is malformed
36
+ *
37
+ * If EPB is removed by shifting bytes, it will return the original NALU
38
+ * length, which will fail this test
39
+ *
40
+ * Note that the CEA-608 packet in this test is incomplete
41
+ */
42
+ it('parses CEA-608 SEI data from MP4 H.264 stream', () => {
43
+ const seiProcessor = new shaka.cea.SeiProcessor();
44
+
45
+ const cea608Packet = new Uint8Array([
46
+ 0x00, 0x00, 0x03, // Emulation prevention byte
47
+ ]);
48
+
49
+ const naluData = seiProcessor.removeEmu(cea608Packet);
50
+ expect(naluData).toBeDefined();
51
+
52
+ // EPB should be removed by returning new array, not by shifting bytes
53
+ expect(naluData.length).toBe(2);
54
+ });
55
+
30
56
  it('parses cea data from mp4 stream', () => {
31
57
  const cea708Parser = new shaka.cea.Mp4CeaParser();
32
58
 
@@ -276,6 +276,7 @@ describe('DashParser Live', () => {
276
276
  ref.timestampOffset = pStart;
277
277
  ref.startTime += pStart;
278
278
  ref.endTime += pStart;
279
+ ref.trueEndTime += pStart;
279
280
  }
280
281
  /** @const {!Array.<!shaka.media.SegmentReference>} */
281
282
  const allRefs = period1Refs.concat(period2Refs);