shaka-player 4.0.0 → 4.1.0

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 (114) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/CHANGELOG.md +33 -0
  4. package/CONTRIBUTORS +1 -0
  5. package/README.md +4 -4
  6. package/build/misspellings.txt +1 -0
  7. package/demo/common/message_ids.js +1 -0
  8. package/demo/config.js +3 -1
  9. package/demo/demo.less +0 -1
  10. package/demo/locales/en.json +1 -0
  11. package/demo/locales/source.json +4 -0
  12. package/dist/controls.css +3 -3
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +4 -4
  15. package/dist/demo.compiled.debug.map +2 -2
  16. package/dist/demo.compiled.js +4 -4
  17. package/dist/demo.compiled.map +2 -2
  18. package/dist/demo.css +1 -1
  19. package/dist/demo.css.map +1 -1
  20. package/dist/deps.js +3 -3
  21. package/dist/locales.js +32 -32
  22. package/dist/receiver.compiled.debug.js +2 -2
  23. package/dist/receiver.compiled.debug.map +2 -2
  24. package/dist/receiver.compiled.js +2 -2
  25. package/dist/receiver.compiled.map +2 -2
  26. package/dist/shaka-player.compiled.d.ts +15 -8
  27. package/dist/shaka-player.compiled.debug.d.ts +15 -8
  28. package/dist/shaka-player.compiled.debug.externs.js +11 -2
  29. package/dist/shaka-player.compiled.debug.js +266 -253
  30. package/dist/shaka-player.compiled.debug.map +3 -3
  31. package/dist/shaka-player.compiled.externs.js +11 -2
  32. package/dist/shaka-player.compiled.js +792 -784
  33. package/dist/shaka-player.compiled.map +3 -3
  34. package/dist/shaka-player.ui.d.ts +16 -9
  35. package/dist/shaka-player.ui.debug.d.ts +16 -9
  36. package/dist/shaka-player.ui.debug.externs.js +11 -2
  37. package/dist/shaka-player.ui.debug.js +299 -282
  38. package/dist/shaka-player.ui.debug.map +3 -3
  39. package/dist/shaka-player.ui.externs.js +11 -2
  40. package/dist/shaka-player.ui.js +948 -941
  41. package/dist/shaka-player.ui.map +3 -3
  42. package/docs/design/chromecast.md +3 -1
  43. package/docs/tutorials/drm-config.md +16 -0
  44. package/docs/tutorials/ui.md +26 -0
  45. package/docs/tutorials/upgrade.md +4 -2
  46. package/externs/chromecast.js +9 -2
  47. package/externs/shaka/manifest.js +7 -0
  48. package/externs/shaka/player.js +32 -2
  49. package/externs/shaka/text.js +7 -0
  50. package/karma.conf.js +4 -0
  51. package/lib/abr/simple_abr_manager.js +2 -1
  52. package/lib/cast/cast_proxy.js +19 -8
  53. package/lib/cast/cast_receiver.js +3 -3
  54. package/lib/cast/cast_sender.js +12 -2
  55. package/lib/dash/dash_parser.js +2 -2
  56. package/lib/hls/hls_parser.js +46 -15
  57. package/lib/media/drm_engine.js +36 -8
  58. package/lib/media/gap_jumping_controller.js +23 -1
  59. package/lib/media/media_source_engine.js +91 -37
  60. package/lib/media/playhead.js +47 -4
  61. package/lib/media/stall_detector.js +19 -2
  62. package/lib/media/streaming_engine.js +9 -3
  63. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  64. package/lib/offline/manifest_converter.js +1 -0
  65. package/lib/player.js +339 -175
  66. package/lib/text/cue.js +6 -0
  67. package/lib/text/ui_text_displayer.js +1 -0
  68. package/lib/text/vtt_text_parser.js +11 -7
  69. package/lib/util/config_utils.js +25 -0
  70. package/lib/util/error.js +13 -0
  71. package/lib/util/fake_event.js +39 -0
  72. package/lib/util/manifest_parser_utils.js +1 -1
  73. package/lib/util/mime_utils.js +62 -6
  74. package/lib/util/periods.js +5 -4
  75. package/lib/util/player_configuration.js +16 -14
  76. package/lib/util/stats.js +28 -1
  77. package/lib/util/stream_utils.js +11 -2
  78. package/package.json +5 -2
  79. package/test/cast/cast_proxy_unit.js +3 -1
  80. package/test/cast/cast_sender_unit.js +21 -6
  81. package/test/demo/demo_unit.js +2 -1
  82. package/test/hls/hls_live_unit.js +79 -7
  83. package/test/hls/manifest_text_parser_unit.js +19 -0
  84. package/test/media/adaptation_set_unit.js +1 -0
  85. package/test/media/playhead_unit.js +114 -25
  86. package/test/media/stall_detector_unit.js +20 -2
  87. package/test/media/streaming_engine_integration.js +2 -1
  88. package/test/media/streaming_engine_unit.js +1 -0
  89. package/test/offline/indexeddb_storage_unit.js +1 -1
  90. package/test/offline/storage_compatibility_unit.js +1 -1
  91. package/test/player_external.js +7 -1
  92. package/test/player_integration.js +9 -10
  93. package/test/player_src_equals_integration.js +81 -83
  94. package/test/player_unit.js +240 -2
  95. package/test/test/boot.js +9 -0
  96. package/test/test/externs/filters.js +4 -0
  97. package/test/test/util/manifest_generator.js +2 -0
  98. package/test/test/util/simple_fakes.js +45 -2
  99. package/test/test/util/streaming_engine_util.js +1 -0
  100. package/test/text/vtt_text_parser_unit.js +68 -5
  101. package/test/ui/ui_unit.js +18 -5
  102. package/test/util/mime_utils_unit.js +54 -0
  103. package/test/util/periods_unit.js +79 -0
  104. package/test/util/stream_utils_unit.js +108 -0
  105. package/ui/controls.js +12 -8
  106. package/ui/externs/ui.js +10 -1
  107. package/ui/less/containers.less +1 -1
  108. package/ui/less/general.less +1 -1
  109. package/ui/less/range_elements.less +1 -0
  110. package/ui/resolution_selection.js +1 -1
  111. package/ui/statistics_button.js +10 -0
  112. package/ui/ui.js +12 -1
  113. package/.github/pull_request_template.md +0 -10
  114. package/docs/tutorials/gsoc-ideas-list.md +0 -3
@@ -996,6 +996,85 @@ describe('PeriodCombiner', () => {
996
996
  expect(audio2.originalId).toBe('2,4');
997
997
  });
998
998
 
999
+ it('Matches streams with related codecs', async () => {
1000
+ const stream1 = makeVideoStream(1080);
1001
+ stream1.originalId = '1';
1002
+ stream1.bandwidth = 120000;
1003
+ stream1.codecs = 'hvc1.1.4.L126.B0';
1004
+
1005
+ const stream2 = makeVideoStream(1080);
1006
+ stream2.originalId = '2';
1007
+ stream2.bandwidth = 120000;
1008
+ stream2.codecs = 'hev1.2.4.L123.B0';
1009
+
1010
+ const stream3 = makeVideoStream(1080);
1011
+ stream3.originalId = '3';
1012
+ stream3.bandwidth = 120000;
1013
+ stream3.codecs = 'dvhe.05.01';
1014
+
1015
+ const stream4 = makeVideoStream(1080);
1016
+ stream4.originalId = '4';
1017
+ stream4.bandwidth = 120000;
1018
+ stream4.codecs = 'dvh1.05.01';
1019
+
1020
+ const stream5 = makeVideoStream(1080);
1021
+ stream5.originalId = '5';
1022
+ stream5.bandwidth = 120000;
1023
+ stream5.codecs = 'avc1.42001f';
1024
+
1025
+ const stream6 = makeVideoStream(1080);
1026
+ stream6.originalId = '6';
1027
+ stream6.bandwidth = 120000;
1028
+ stream6.codecs = 'avc3.42001f';
1029
+
1030
+ const stream7 = makeVideoStream(1080);
1031
+ stream7.originalId = '7';
1032
+ stream7.bandwidth = 120000;
1033
+ stream7.codecs = 'vp09.00.10.08';
1034
+
1035
+ const stream8 = makeVideoStream(1080);
1036
+ stream8.originalId = '8';
1037
+ stream8.bandwidth = 120000;
1038
+ stream8.codecs = 'vp09.01.20.08.01';
1039
+
1040
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
1041
+ const periods = [
1042
+ {
1043
+ id: '0',
1044
+ videoStreams: [
1045
+ stream1, stream3, stream5, stream7,
1046
+ ],
1047
+ audioStreams: [],
1048
+ textStreams: [],
1049
+ imageStreams: [],
1050
+ },
1051
+ {
1052
+ id: '1',
1053
+ videoStreams: [
1054
+ stream2, stream4, stream6, stream8,
1055
+ ],
1056
+ audioStreams: [],
1057
+ textStreams: [],
1058
+ imageStreams: [],
1059
+ },
1060
+ ];
1061
+
1062
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
1063
+ const variants = combiner.getVariants();
1064
+ expect(variants.length).toBe(4);
1065
+ // We can use the originalId field to see what each track is composed of.
1066
+ const video1 = variants[0].video;
1067
+ expect(video1.originalId).toBe('1,2');
1068
+
1069
+ const video2 = variants[1].video;
1070
+ expect(video2.originalId).toBe('3,4');
1071
+
1072
+ const video3 = variants[2].video;
1073
+ expect(video3.originalId).toBe('5,6');
1074
+
1075
+ const video4 = variants[3].video;
1076
+ expect(video4.originalId).toBe('7,8');
1077
+ });
999
1078
 
1000
1079
  it('Matches streams with most roles in common', async () => {
1001
1080
  const makeAudioStreamWithRoles = (roles) => {
@@ -902,4 +902,112 @@ describe('StreamUtils', () => {
902
902
  expect(manifest.variants[0].video.id).toBe(2);
903
903
  });
904
904
  });
905
+
906
+ describe('meetsRestrictions', () => {
907
+ const oldDateNow = Date.now;
908
+ /* @param {shaka.extern.Restrictions} */
909
+ const restrictions = {
910
+ minWidth: 10,
911
+ maxWidth: 20,
912
+ minHeight: 10,
913
+ maxHeight: 20,
914
+ minPixels: 10,
915
+ maxPixels: 20,
916
+ minFrameRate: 21,
917
+ maxFrameRate: 25,
918
+ minBandwidth: 1000,
919
+ maxBandwidth: 3000,
920
+ };
921
+ /* @param {{width: number, height: number}} */
922
+ const maxHwRes = {width: 123, height: 456};
923
+ /* @param {shaka.extern.Variant} */
924
+ let variant;
925
+ /* @param {boolean} */
926
+ let meetsRestrictionsResult;
927
+
928
+ beforeEach(() => {
929
+ Date.now = () => 123 * 1000;
930
+ });
931
+
932
+ afterEach(() => {
933
+ Date.now = oldDateNow;
934
+ });
935
+
936
+ /*
937
+ * @param {number} disabledUntilTime
938
+ */
939
+ const checkRestrictions = ({disabledUntilTime = 0}) => {
940
+ /* @param {shaka.extern.Variant} */
941
+ variant = {
942
+ id: 1,
943
+ language: 'es',
944
+ disabledUntilTime,
945
+ video: null,
946
+ audio: null,
947
+ primary: false,
948
+ bandwidth: 2000,
949
+ allowedByApplication: true,
950
+ allowedByKeySystem: true,
951
+ decodingInfos: [],
952
+ };
953
+ meetsRestrictionsResult = StreamUtils.meetsRestrictions(variant,
954
+ restrictions, maxHwRes);
955
+ };
956
+
957
+ describe('when disabledUntilTime > now', () => {
958
+ beforeEach(() => {
959
+ checkRestrictions({disabledUntilTime: 124});
960
+ });
961
+
962
+ it('does not meet the restrictions', () => {
963
+ expect(meetsRestrictionsResult).toBeFalsy();
964
+ });
965
+
966
+ it('does not reset disabledUntilTime', () => {
967
+ expect(variant.disabledUntilTime).toBe(124);
968
+ });
969
+ });
970
+
971
+ describe('when disabledUntilTime == now', () => {
972
+ beforeEach(() => {
973
+ checkRestrictions({disabledUntilTime: 123});
974
+ });
975
+
976
+ it('meets the restrictions', () => {
977
+ expect(meetsRestrictionsResult).toBeTruthy();
978
+ });
979
+
980
+ it('resets disabledUntilTime', () => {
981
+ expect(variant.disabledUntilTime).toBe(0);
982
+ });
983
+ });
984
+
985
+ describe('when disabledUntilTime == now', () => {
986
+ beforeEach(() => {
987
+ checkRestrictions({disabledUntilTime: 122});
988
+ });
989
+
990
+ it('meets the restrictions', () => {
991
+ expect(meetsRestrictionsResult).toBeTruthy();
992
+ });
993
+
994
+ it('resets disabledUntilTime', () => {
995
+ expect(variant.disabledUntilTime).toBe(0);
996
+ });
997
+ });
998
+
999
+ describe('when disabledUntilTime == 0', () => {
1000
+ beforeEach(() => {
1001
+ checkRestrictions({disabledUntilTime: 0});
1002
+ });
1003
+
1004
+ it('meets the restrictions', () => {
1005
+ expect(meetsRestrictionsResult).toBeTruthy();
1006
+ });
1007
+
1008
+ it('leaves disabledUntilTime = 0', () => {
1009
+ expect(variant.disabledUntilTime).toBe(0);
1010
+ });
1011
+ });
1012
+ });
905
1013
  });
package/ui/controls.js CHANGED
@@ -53,7 +53,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
53
53
 
54
54
  /** @private {shaka.cast.CastProxy} */
55
55
  this.castProxy_ = new shaka.cast.CastProxy(
56
- video, player, this.config_.castReceiverAppId);
56
+ video, player, this.config_.castReceiverAppId,
57
+ this.config_.castAndroidReceiverCompatible);
57
58
 
58
59
  /** @private {boolean} */
59
60
  this.castAllowed_ = true;
@@ -315,7 +316,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
315
316
  configure(config) {
316
317
  this.config_ = config;
317
318
 
318
- this.castProxy_.changeReceiverId(config.castReceiverAppId);
319
+ this.castProxy_.changeReceiverId(config.castReceiverAppId,
320
+ config.castAndroidReceiverCompatible);
319
321
 
320
322
  // Deconstruct the old layout if applicable
321
323
  if (this.seekBar_) {
@@ -1258,21 +1260,23 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1258
1260
  return;
1259
1261
  }
1260
1262
 
1263
+ const keyboardSeekDistance = this.config_.keyboardSeekDistance;
1264
+
1261
1265
  switch (event.key) {
1262
1266
  case 'ArrowLeft':
1263
1267
  // If it's not focused on the volume bar, move the seek time backward
1264
- // for 5 sec. Otherwise, the volume will be adjusted automatically.
1265
- if (this.seekBar_ && !isVolumeBar) {
1268
+ // for a few sec. Otherwise, the volume will be adjusted automatically.
1269
+ if (this.seekBar_ && !isVolumeBar && keyboardSeekDistance > 0) {
1266
1270
  event.preventDefault();
1267
- this.seek_(this.seekBar_.getValue() - 5);
1271
+ this.seek_(this.seekBar_.getValue() - keyboardSeekDistance);
1268
1272
  }
1269
1273
  break;
1270
1274
  case 'ArrowRight':
1271
1275
  // If it's not focused on the volume bar, move the seek time forward
1272
- // for 5 sec. Otherwise, the volume will be adjusted automatically.
1273
- if (this.seekBar_ && !isVolumeBar) {
1276
+ // for a few sec. Otherwise, the volume will be adjusted automatically.
1277
+ if (this.seekBar_ && !isVolumeBar && keyboardSeekDistance > 0) {
1274
1278
  event.preventDefault();
1275
- this.seek_(this.seekBar_.getValue() + 5);
1279
+ this.seek_(this.seekBar_.getValue() + keyboardSeekDistance);
1276
1280
  }
1277
1281
  break;
1278
1282
  // Jump to the beginning of the video's seek range.
package/ui/externs/ui.js CHANGED
@@ -72,6 +72,7 @@ shaka.extern.UIVolumeBarColors;
72
72
  * addBigPlayButton: boolean,
73
73
  * customContextMenu: boolean,
74
74
  * castReceiverAppId: string,
75
+ * castAndroidReceiverCompatible: boolean,
75
76
  * clearBufferOnQualityChange: boolean,
76
77
  * showUnbufferedStart: boolean,
77
78
  * seekBarColors: shaka.extern.UISeekBarColors,
@@ -83,7 +84,8 @@ shaka.extern.UIVolumeBarColors;
83
84
  * enableKeyboardPlaybackControls: boolean,
84
85
  * enableFullscreenOnRotation: boolean,
85
86
  * forceLandscapeOnFullscreen: boolean,
86
- * enableTooltips: boolean
87
+ * enableTooltips: boolean,
88
+ * keyboardSeekDistance: number
87
89
  * }}
88
90
  *
89
91
  * @property {!Array.<string>} controlPanelElements
@@ -109,6 +111,8 @@ shaka.extern.UIVolumeBarColors;
109
111
  * Whether or not a custom context menu replaces the default.
110
112
  * @property {string} castReceiverAppId
111
113
  * Receiver app id to use for the Chromecast support.
114
+ * @property {boolean} castAndroidReceiverCompatible
115
+ * Indicates if the app is compatible with an Android Cast Receiver.
112
116
  * @property {boolean} clearBufferOnQualityChange
113
117
  * Only applicable if the resolution selection is part of the UI.
114
118
  * Whether buffer should be cleared when changing resolution
@@ -174,6 +178,11 @@ shaka.extern.UIVolumeBarColors;
174
178
  * Whether or not buttons in the control panel display tooltips that contain
175
179
  * information about their function.
176
180
  * Defaults to false.
181
+ * @property {number} keyboardSeekDistance
182
+ * The time interval, in seconds, to seek when the user presses the left or
183
+ * right keyboard keys when the video is selected. If less than or equal to 0,
184
+ * no seeking will occur.
185
+ * Defaults to 5 seconds.
177
186
  * @exportDoc
178
187
  */
179
188
  shaka.extern.UIConfiguration;
@@ -161,7 +161,7 @@
161
161
  /* Consistent margins (external) and padding (internal) between controls. */
162
162
  .bottom-panels-elements-margin();
163
163
 
164
- padding: 0;
164
+ padding: 0 5px;
165
165
 
166
166
  /* Transparent backgrounds, no borders, and a pointer when you mouse over
167
167
  * them. */
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  .bottom-panels-elements-margin() {
40
- margin: 1px 6px;
40
+ margin: 1px;
41
41
  }
42
42
 
43
43
  /* For containers which host elements overlaying other things. */
@@ -140,6 +140,7 @@
140
140
 
141
141
  .shaka-volume-bar-container {
142
142
  width: 100px;
143
+ padding: 0;
143
144
  }
144
145
 
145
146
  .shaka-range-element {
@@ -90,7 +90,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
90
90
  return otherIdx == idx;
91
91
  });
92
92
 
93
- // Sort the tracks by height or bandwith depending on content type.
93
+ // Sort the tracks by height or bandwidth depending on content type.
94
94
  if (this.player.isAudioOnly()) {
95
95
  tracks.sort((t1, t2) => {
96
96
  goog.asserts.assert(t1.bandwidth != null, 'Null bandwidth');
@@ -109,6 +109,14 @@ shaka.ui.StatisticsButton = class extends shaka.ui.Element {
109
109
  this.currentStats_[name], false) + ' (m)';
110
110
  };
111
111
 
112
+ const parseGaps = (name) => {
113
+ return this.currentStats_[name] + ' (gaps)';
114
+ };
115
+
116
+ const parseStalls = (name) => {
117
+ return this.currentStats_[name] + ' (stalls)';
118
+ };
119
+
112
120
  /** @private {!Object.<string, function(string):string>} */
113
121
  this.parseFrom_ = {
114
122
  'width': parsePx,
@@ -128,6 +136,8 @@ shaka.ui.StatisticsButton = class extends shaka.ui.Element {
128
136
  'corruptedFrames': parseFrames,
129
137
  'decodedFrames': parseFrames,
130
138
  'droppedFrames': parseFrames,
139
+ 'stallsDetected': parseStalls,
140
+ 'gapsJumped': parseGaps,
131
141
  };
132
142
 
133
143
  /** @private {shaka.util.Timer} */
package/ui/ui.js CHANGED
@@ -218,6 +218,7 @@ shaka.ui.Overlay = class {
218
218
  addBigPlayButton: false,
219
219
  customContextMenu: false,
220
220
  castReceiverAppId: '',
221
+ castAndroidReceiverCompatible: false,
221
222
  clearBufferOnQualityChange: true,
222
223
  showUnbufferedStart: false,
223
224
  seekBarColors: {
@@ -238,6 +239,7 @@ shaka.ui.Overlay = class {
238
239
  enableFullscreenOnRotation: true,
239
240
  forceLandscapeOnFullscreen: true,
240
241
  enableTooltips: false,
242
+ keyboardSeekDistance: 5,
241
243
  };
242
244
 
243
245
  // Check AirPlay support
@@ -388,19 +390,28 @@ shaka.ui.Overlay = class {
388
390
  // Get and configure cast app id.
389
391
  let castAppId = '';
390
392
 
393
+ // Get and configure cast Android Receiver Compatibility
394
+ let castAndroidReceiverCompatible = false;
395
+
391
396
  // Cast receiver id can be specified on either container or video.
392
397
  // It should not be provided on both. If it was, we will use the last
393
398
  // one we saw.
394
399
  if (container['dataset'] &&
395
400
  container['dataset']['shakaPlayerCastReceiverId']) {
396
401
  castAppId = container['dataset']['shakaPlayerCastReceiverId'];
402
+ castAndroidReceiverCompatible =
403
+ container['dataset']['shakaPlayerCastAndroidReceiverCompatible'] ===
404
+ 'true';
397
405
  } else if (video['dataset'] &&
398
406
  video['dataset']['shakaPlayerCastReceiverId']) {
399
407
  castAppId = video['dataset']['shakaPlayerCastReceiverId'];
408
+ castAndroidReceiverCompatible =
409
+ video['dataset']['shakaPlayerCastAndroidReceiverCompatible'] === 'true';
400
410
  }
401
411
 
402
412
  if (castAppId.length) {
403
- ui.configure({castReceiverAppId: castAppId});
413
+ ui.configure({castReceiverAppId: castAppId,
414
+ castAndroidReceiverCompatible: castAndroidReceiverCompatible});
404
415
  }
405
416
 
406
417
  if (shaka.util.Dom.asHTMLMediaElement(video).controls) {
@@ -1,10 +0,0 @@
1
- <!--
2
- Please remember to:
3
-
4
- 1. Use Conventional Commits syntax (fix: ..., feat: ..., etc.) in commits and
5
- PR title (https://www.conventionalcommits.org/)
6
- 2. Tag any related or fixed issues ("Issue #123", "Closes #420")
7
- 3. Sign the Google CLA if you haven't (https://cla.developers.google.com)
8
-
9
- You may delete this comment from the PR description.
10
- -->
@@ -1,3 +0,0 @@
1
- # Google Summer of Code Ideas List
2
-
3
- Please see the latest project ideas list [here](https://storage.googleapis.com/wvtemp/shaka/Shaka%20Player%20GSoC%20Projects%20List.pdf).