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
package/lib/text/cue.js CHANGED
@@ -144,6 +144,12 @@ shaka.text.Cue = class {
144
144
  */
145
145
  this.border = '';
146
146
 
147
+ /**
148
+ * @override
149
+ * @exportInterface
150
+ */
151
+ this.textShadow = '';
152
+
147
153
  /**
148
154
  * @override
149
155
  * @exportInterface
@@ -411,6 +411,7 @@ shaka.text.UITextDisplayer = class {
411
411
  style.paddingRight =
412
412
  shaka.text.UITextDisplayer.convertLengthValue_(
413
413
  cue.linePadding, cue, this.videoContainer_);
414
+ style.textShadow = cue.textShadow;
414
415
 
415
416
  if (cue.backgroundImage) {
416
417
  style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
@@ -68,13 +68,15 @@ shaka.text.VttTextParser = class {
68
68
  // to the beginning of each segment.
69
69
  // NOTE: "periodStart" is the timestamp offset applied via TextEngine.
70
70
  // It is no longer closely tied to periods, but the name stuck around.
71
+ // NOTE: This offset and the flag choosing its meaning have no effect on
72
+ // HLS content, which should use X-TIMESTAMP-MAP and periodStart instead.
71
73
  let offset = time.vttOffset;
72
74
 
73
- // Do not honor the 'X-TIMESTAMP-MAP' value when in sequence mode.
74
- // That is because it is used mainly (solely?) to account for the timestamp
75
- // offset of the video/audio; when in sequence mode, we normalize that
76
- // timestamp offset to 0, so we should not account for it.
77
- if (blocks[0].includes('X-TIMESTAMP-MAP') && !this.sequenceMode_) {
75
+ // Only use 'X-TIMESTAMP-MAP' in sequence mode, as that is currently
76
+ // shorthand for HLS. Note that an offset based on the first video
77
+ // timestamp has already been extracted, and appears in periodStart.
78
+ // The relative offset from X-TIMESTAMP-MAP will be added to that for HLS.
79
+ if (blocks[0].includes('X-TIMESTAMP-MAP') && this.sequenceMode_) {
78
80
  // https://bit.ly/2K92l7y
79
81
  // The 'X-TIMESTAMP-MAP' header is used in HLS to align text with
80
82
  // the rest of the media.
@@ -109,8 +111,6 @@ shaka.text.VttTextParser = class {
109
111
  mpegTime += shaka.text.VttTextParser.TS_ROLLOVER_;
110
112
  }
111
113
 
112
- // Apple-encoded HLS content uses absolute timestamps, so assume the
113
- // presence of the map tag means the content uses absolute timestamps.
114
114
  offset = time.periodStart + mpegTime / mpegTimescale - cueTime;
115
115
  }
116
116
  }
@@ -293,6 +293,10 @@ shaka.text.VttTextParser = class {
293
293
  validStyle = true;
294
294
  cue.opacity = parseFloat(value);
295
295
  break;
296
+ case 'text-shadow':
297
+ validStyle = true;
298
+ cue.textShadow = value;
299
+ break;
296
300
  case 'white-space':
297
301
  validStyle = true;
298
302
  cue.wrapLine = value != 'noWrap';
@@ -127,4 +127,29 @@ shaka.util.ConfigUtils = class {
127
127
  last[fieldName.substring(nameStart).replace(/\\\./g, '.')] = value;
128
128
  return configObject;
129
129
  }
130
+
131
+ /**
132
+ * Reference the input parameters so the compiler doesn't remove them from
133
+ * the calling function. Return whatever value is specified.
134
+ *
135
+ * This allows an empty or default implementation of a config callback that
136
+ * still bears the complete function signature even in compiled mode.
137
+ *
138
+ * The caller should look something like this:
139
+ *
140
+ * const callback = (a, b, c, d) => {
141
+ * return referenceParametersAndReturn(
142
+ [a, b, c, d],
143
+ a); // Can be anything, doesn't need to be one of the parameters
144
+ * };
145
+ *
146
+ * @param {!Array.<?>} parameters
147
+ * @param {T} returnValue
148
+ * @return {T}
149
+ * @template T
150
+ * @noinline
151
+ */
152
+ static referenceParametersAndReturn(parameters, returnValue) {
153
+ return parameters && returnValue;
154
+ }
130
155
  };
package/lib/util/error.js CHANGED
@@ -267,6 +267,12 @@ shaka.util.Error.Code = {
267
267
  */
268
268
  'ATTEMPTS_EXHAUSTED': 1010,
269
269
 
270
+ /**
271
+ * The segment is missing.
272
+ * <br> error.data[0] is the URI.
273
+ */
274
+ 'SEGMENT_MISSING': 1011,
275
+
270
276
 
271
277
  /** The text parser failed to parse a text stream due to an invalid header. */
272
278
  'INVALID_TEXT_HEADER': 2000,
@@ -345,6 +351,13 @@ shaka.util.Error.Code = {
345
351
  */
346
352
  'MISSING_TEXT_PLUGIN': 2014,
347
353
 
354
+ /**
355
+ * The chapters track failed to load. The browser does not provide any
356
+ * information in this case to identify why it failed, but there may be
357
+ * details in the JavaScript console.
358
+ */
359
+ 'CHAPTERS_TRACK_FAILED': 2015,
360
+
348
361
  /**
349
362
  * Some component tried to read past the end of a buffer. The segment index,
350
363
  * init segment, or PSSH may be malformed.
@@ -144,3 +144,42 @@ shaka.util.FakeEvent = class {
144
144
  */
145
145
  stopPropagation() {}
146
146
  };
147
+
148
+
149
+ /**
150
+ * An internal enum that contains the string values of all of the player events.
151
+ * This exists primarily to act as an implicit list of events, for tests.
152
+ *
153
+ * @enum {string}
154
+ */
155
+ shaka.util.FakeEvent.EventName = {
156
+ AbrStatusChanged: 'abrstatuschanged',
157
+ Adaptation: 'adaptation',
158
+ Buffering: 'buffering',
159
+ DownloadFailed: 'downloadfailed',
160
+ DownloadHeadersReceived: 'downloadheadersreceived',
161
+ DrmSessionUpdate: 'drmsessionupdate',
162
+ Emsg: 'emsg',
163
+ Error: 'error',
164
+ ExpirationUpdated: 'expirationupdated',
165
+ GapJumped: 'gapjumped',
166
+ Loaded: 'loaded',
167
+ Loading: 'loading',
168
+ ManifestParsed: 'manifestparsed',
169
+ MediaQualityChanged: 'mediaqualitychanged',
170
+ Metadata: 'metadata',
171
+ OnStateChange: 'onstatechange',
172
+ OnStateIdle: 'onstateidle',
173
+ RateChange: 'ratechange',
174
+ SessionDataEvent: 'sessiondata',
175
+ StallDetected: 'stalldetected',
176
+ Streaming: 'streaming',
177
+ TextChanged: 'textchanged',
178
+ TextTrackVisibility: 'texttrackvisibility',
179
+ TimelineRegionAdded: 'timelineregionadded',
180
+ TimelineRegionEnter: 'timelineregionenter',
181
+ TimelineRegionExit: 'timelineregionexit',
182
+ TracksChanged: 'trackschanged',
183
+ Unloading: 'unloading',
184
+ VariantChanged: 'variantchanged',
185
+ };
@@ -167,7 +167,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
167
167
  /^hev/,
168
168
  /^hvc/,
169
169
  /^vp0?[89]/,
170
- /^av1$/,
170
+ /^av01/,
171
171
  ];
172
172
 
173
173
 
@@ -8,6 +8,7 @@ goog.provide('shaka.util.MimeUtils');
8
8
 
9
9
  goog.require('shaka.dependencies');
10
10
  goog.require('shaka.media.Transmuxer');
11
+ goog.require('shaka.util.ManifestParserUtils');
11
12
 
12
13
  /**
13
14
  * @summary A set of utility functions for dealing with MIME types.
@@ -34,6 +35,8 @@ shaka.util.MimeUtils = class {
34
35
  /**
35
36
  * Takes a MIME type and a codecs string and produces the full MIME
36
37
  * type. If it's a transport stream, convert its codecs to MP4 codecs.
38
+ * Otherwise for multiplexed content, convert the video MIME types to
39
+ * their audio equivalents if the content type is audio.
37
40
  *
38
41
  * @param {string} mimeType
39
42
  * @param {string} codecs
@@ -42,13 +45,19 @@ shaka.util.MimeUtils = class {
42
45
  */
43
46
  static getFullOrConvertedType(mimeType, codecs, contentType) {
44
47
  const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
48
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
45
49
 
46
- if (!shaka.dependencies.muxjs() ||
47
- !shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
48
- return fullMimeType;
50
+ if (shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
51
+ if (shaka.dependencies.muxjs()) {
52
+ return shaka.media.Transmuxer.convertTsCodecs(
53
+ contentType, fullMimeType);
54
+ }
55
+ } else if (contentType == ContentType.AUDIO) {
56
+ // video/mp2t is the correct mime type for TS audio, so only replace the
57
+ // word "video" with "audio" for non-TS audio content.
58
+ return fullMimeType.replace('video', 'audio');
49
59
  }
50
-
51
- return shaka.media.Transmuxer.convertTsCodecs(contentType, fullMimeType);
60
+ return fullMimeType;
52
61
  }
53
62
 
54
63
 
@@ -96,6 +105,53 @@ shaka.util.MimeUtils = class {
96
105
  return codecs.split(',');
97
106
  }
98
107
 
108
+ /**
109
+ * Get the normalized codec from a codec string,
110
+ * independently of their container.
111
+ *
112
+ * @param {string} codecString
113
+ * @return {string}
114
+ */
115
+ static getNormalizedCodec(codecString) {
116
+ const parts =
117
+ shaka.util.MimeUtils.getCodecParts_(codecString);
118
+ const base = parts[0];
119
+ const profile = parts[1].toLowerCase();
120
+ switch (true) {
121
+ case base === 'mp4a' && profile === '69':
122
+ case base === 'mp4a' && profile === '6b':
123
+ return 'mp3';
124
+ case base === 'mp4a' && profile === '66':
125
+ case base === 'mp4a' && profile === '67':
126
+ case base === 'mp4a' && profile === '68':
127
+ case base === 'mp4a' && profile === '40.2':
128
+ case base === 'mp4a' && profile === '40.02':
129
+ case base === 'mp4a' && profile === '40.5':
130
+ case base === 'mp4a' && profile === '40.05':
131
+ case base === 'mp4a' && profile === '40.29':
132
+ case base === 'mp4a' && profile === '40.42': // Extended HE-AAC
133
+ return 'aac';
134
+ case base === 'mp4a' && profile === 'a5':
135
+ return 'ac-3'; // Dolby Digital
136
+ case base === 'mp4a' && profile === 'a6':
137
+ return 'ec-3'; // Dolby Digital Plus
138
+ case base === 'mp4a' && profile === 'b2':
139
+ return 'dtsx'; // DTS:X
140
+ case base === 'mp4a' && profile === 'a9':
141
+ return 'dtsc'; // DTS Digital Surround
142
+ case base === 'avc1':
143
+ case base === 'avc3':
144
+ return 'avc'; // H264
145
+ case base === 'hvc1':
146
+ case base === 'hev1':
147
+ return 'hevc'; // H265
148
+ case base === 'dvh1':
149
+ case base === 'dvhe':
150
+ return 'dovi'; // Dolby Vision
151
+ }
152
+ return base;
153
+ }
154
+
99
155
  /**
100
156
  * Get the base codec from a codec string.
101
157
  *
@@ -152,7 +208,7 @@ shaka.util.MimeUtils = class {
152
208
 
153
209
  const base = parts[0];
154
210
 
155
- parts.pop();
211
+ parts.shift();
156
212
  const profile = parts.join('.');
157
213
 
158
214
  // Make sure that we always return a "base" and "profile".
@@ -573,8 +573,8 @@ shaka.util.PeriodCombiner = class {
573
573
  // TODO(#1528): Consider changing this when we support codec switching.
574
574
  const hasCodec = outputStreams.some((s) => {
575
575
  return s.mimeType == stream.mimeType &&
576
- shaka.util.MimeUtils.getCodecBase(s.codecs) ==
577
- shaka.util.MimeUtils.getCodecBase(stream.codecs);
576
+ shaka.util.MimeUtils.getNormalizedCodec(s.codecs) ==
577
+ shaka.util.MimeUtils.getNormalizedCodec(stream.codecs);
578
578
  });
579
579
  if (!hasCodec) {
580
580
  continue;
@@ -1050,10 +1050,11 @@ shaka.util.PeriodCombiner = class {
1050
1050
  * @private
1051
1051
  */
1052
1052
  static areAVStreamsCompatible_(outputStream, candidate) {
1053
- const getCodecBase = (codecs) => shaka.util.MimeUtils.getCodecBase(codecs);
1053
+ const getCodec = (codecs) =>
1054
+ shaka.util.MimeUtils.getNormalizedCodec(codecs);
1054
1055
  // Check MIME type and codecs, which should always be the same.
1055
1056
  if (candidate.mimeType != outputStream.mimeType ||
1056
- getCodecBase(candidate.codecs) != getCodecBase(outputStream.codecs)) {
1057
+ getCodec(candidate.codecs) != getCodec(outputStream.codecs)) {
1057
1058
  return false;
1058
1059
  }
1059
1060
 
@@ -16,14 +16,6 @@ goog.require('shaka.util.ManifestParserUtils');
16
16
  goog.require('shaka.util.Platform');
17
17
 
18
18
 
19
- // TODO(vaage): Many times in our configs, we need to create an empty
20
- // implementation of a method, but to avoid closure from removing unused
21
- // parameters (and breaking our merge config code) we need to use each
22
- // parameter. Is there a better solution to this problem than what we are
23
- // doing now?
24
- //
25
- // NOTE: Chrome App Content Security Policy prohibits usage of new Function()
26
-
27
19
  /**
28
20
  * @final
29
21
  * @export
@@ -74,9 +66,15 @@ shaka.util.PlayerConfiguration = class {
74
66
  clearKeys: {}, // key is arbitrary key system ID, value must be string
75
67
  advanced: {}, // key is arbitrary key system ID, value is a record type
76
68
  delayLicenseRequestUntilPlayed: false,
69
+ initDataTransform: (initData, initDataType, drmInfo) => {
70
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
71
+ [initData, initDataType, drmInfo],
72
+ initData);
73
+ },
77
74
  logLicenseExchange: false,
78
75
  updateExpirationTime: 1,
79
76
  preferredKeySystems: [],
77
+ keySystemsMapping: {},
80
78
  };
81
79
 
82
80
  const manifest = {
@@ -113,11 +111,10 @@ shaka.util.PlayerConfiguration = class {
113
111
  'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb':
114
112
  'com.adobe.primetime',
115
113
  },
116
- // Need some operation in the callback or else closure may remove calls
117
- // to the function as it would be a no-op. The operation can't just be
118
- // a log message, because those are stripped in the compiled build.
119
114
  manifestPreprocessor: (element) => {
120
- return element;
115
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
116
+ [element],
117
+ element);
121
118
  },
122
119
  },
123
120
  hls: {
@@ -138,7 +135,9 @@ shaka.util.PlayerConfiguration = class {
138
135
  // log message, because those are stripped in the compiled build.
139
136
  failureCallback: (error) => {
140
137
  shaka.log.error('Unhandled streaming error', error);
141
- return [error];
138
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
139
+ [error],
140
+ undefined);
142
141
  },
143
142
  // When low latency streaming is enabled, rebufferingGoal will default to
144
143
  // 0.01 if not specified.
@@ -171,6 +170,7 @@ shaka.util.PlayerConfiguration = class {
171
170
  updateIntervalSeconds: 1,
172
171
  dispatchAllEmsgBoxes: false,
173
172
  observeQualityChanges: false,
173
+ maxDisabledTime: 30,
174
174
  };
175
175
 
176
176
  // Some browsers will stop earlier than others before a gap (e.g., Edge
@@ -214,7 +214,9 @@ shaka.util.PlayerConfiguration = class {
214
214
  // to the function as it would be a no-op. The operation can't just be a
215
215
  // log message, because those are stripped in the compiled build.
216
216
  progressCallback: (content, progress) => {
217
- return [content, progress];
217
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
218
+ [content, progress],
219
+ undefined);
218
220
  },
219
221
 
220
222
  // By default we use persistent licenses as forces errors to surface if
package/lib/util/stats.js CHANGED
@@ -31,6 +31,11 @@ shaka.util.Stats = class {
31
31
  /** @private {number} */
32
32
  this.totalCorruptedFrames_ = NaN;
33
33
 
34
+ /** @private {number} */
35
+ this.totalStallsDetected_ = NaN;
36
+ /** @private {number} */
37
+ this.totalGapsJumped_ = NaN;
38
+
34
39
  /** @private {number} */
35
40
  this.completionPercent_ = NaN;
36
41
 
@@ -76,7 +81,6 @@ shaka.util.Stats = class {
76
81
  this.totalDecodedFrames_ = decoded;
77
82
  }
78
83
 
79
-
80
84
  /**
81
85
  * Update corrupted frames. This will replace the previous values.
82
86
  *
@@ -86,6 +90,25 @@ shaka.util.Stats = class {
86
90
  this.totalCorruptedFrames_ = corrupted;
87
91
  }
88
92
 
93
+ /**
94
+ * Update number of stalls detected. This will replace the previous value.
95
+ *
96
+ * @param {number} stallsDetected
97
+ */
98
+ setStallsDetected(stallsDetected) {
99
+ this.totalStallsDetected_ = stallsDetected;
100
+ }
101
+
102
+ /**
103
+ * Update number of playback gaps jumped over. This will replace the previous
104
+ * value.
105
+ *
106
+ * @param {number} gapsJumped
107
+ */
108
+ setGapsJumped(gapsJumped) {
109
+ this.totalGapsJumped_ = gapsJumped;
110
+ }
111
+
89
112
  /**
90
113
  * Set the width and height of the video we are currently playing.
91
114
  *
@@ -208,6 +231,8 @@ shaka.util.Stats = class {
208
231
  decodedFrames: this.totalDecodedFrames_,
209
232
  droppedFrames: this.totalDroppedFrames_,
210
233
  corruptedFrames: this.totalCorruptedFrames_,
234
+ stallsDetected: this.totalStallsDetected_,
235
+ gapsJumped: this.totalGapsJumped_,
211
236
  estimatedBandwidth: this.bandwidthEstimate_,
212
237
  completionPercent: this.completionPercent_,
213
238
  loadLatency: this.loadLatencySeconds_,
@@ -238,6 +263,8 @@ shaka.util.Stats = class {
238
263
  decodedFrames: NaN,
239
264
  droppedFrames: NaN,
240
265
  corruptedFrames: NaN,
266
+ stallsDetected: NaN,
267
+ gapsJumped: NaN,
241
268
  estimatedBandwidth: NaN,
242
269
  completionPercent: NaN,
243
270
  loadLatency: NaN,
@@ -283,12 +283,14 @@ shaka.util.StreamUtils = class {
283
283
  // both be considered the same codec: avc1.42c01e, avc1.4d401f
284
284
  let baseVideoCodec = '';
285
285
  if (variant.video) {
286
- baseVideoCodec = shaka.util.MimeUtils.getCodecBase(variant.video.codecs);
286
+ baseVideoCodec =
287
+ shaka.util.MimeUtils.getNormalizedCodec(variant.video.codecs);
287
288
  }
288
289
 
289
290
  let baseAudioCodec = '';
290
291
  if (variant.audio) {
291
- baseAudioCodec = shaka.util.MimeUtils.getCodecBase(variant.audio.codecs);
292
+ baseAudioCodec =
293
+ shaka.util.MimeUtils.getNormalizedCodec(variant.audio.codecs);
292
294
  }
293
295
 
294
296
  return baseVideoCodec + '-' + baseAudioCodec;
@@ -327,6 +329,13 @@ shaka.util.StreamUtils = class {
327
329
 
328
330
  const video = variant.video;
329
331
 
332
+ if (variant.disabledUntilTime != 0) {
333
+ if (variant.disabledUntilTime > Date.now() / 1000) {
334
+ return false;
335
+ }
336
+ variant.disabledUntilTime = 0;
337
+ }
338
+
330
339
  // |video.width| and |video.height| can be undefined, which breaks
331
340
  // the math, so make sure they are there first.
332
341
  if (video && video.width && video.height) {
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": "4.0.0",
4
+ "version": "4.1.0",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -50,7 +50,7 @@
50
50
  "less": "https://gitpkg.now.sh/joeyparrish/less.js/packages/less?28c63a43",
51
51
  "less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
52
52
  "material-design-lite": "^1.3.0",
53
- "mux.js": "^5.14.1",
53
+ "mux.js": "^6.1.0",
54
54
  "open-sans-fonts": "^1.6.2",
55
55
  "postcss-less": "^6.0.0",
56
56
  "pwacompat": "^2.0.17",
@@ -97,5 +97,8 @@
97
97
  },
98
98
  "dependencies": {
99
99
  "eme-encryption-scheme-polyfill": "^2.0.3"
100
+ },
101
+ "engines": {
102
+ "node": ">=14"
100
103
  }
101
104
  }
@@ -11,6 +11,7 @@ describe('CastProxy', () => {
11
11
 
12
12
  const originalCastSender = shaka.cast.CastSender;
13
13
  const fakeAppId = 'fake app ID';
14
+ const fakeAndroidReceiverCompatible = false;
14
15
 
15
16
  let mockPlayer;
16
17
  let mockSender;
@@ -31,7 +32,8 @@ describe('CastProxy', () => {
31
32
  mockPlayer = createMockPlayer();
32
33
  mockSender = null;
33
34
 
34
- proxy = new CastProxy(mockVideo, mockPlayer, fakeAppId);
35
+ proxy = new CastProxy(mockVideo, mockPlayer, fakeAppId,
36
+ fakeAndroidReceiverCompatible);
35
37
  });
36
38
 
37
39
  afterEach(async () => {
@@ -13,6 +13,7 @@ describe('CastSender', () => {
13
13
  const originalStatusDelay = shaka.cast.CastSender.STATUS_DELAY;
14
14
 
15
15
  const fakeAppId = 'asdf';
16
+ const fakeAndroidReceiverCompatible = false;
16
17
  const fakeInitState = {
17
18
  manifest: null,
18
19
  player: null,
@@ -52,7 +53,8 @@ describe('CastSender', () => {
52
53
  sender = new CastSender(
53
54
  fakeAppId, Util.spyFunc(onStatusChanged),
54
55
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
55
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
56
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
57
+ fakeAndroidReceiverCompatible);
56
58
  });
57
59
 
58
60
  afterEach(async () => {
@@ -87,7 +89,12 @@ describe('CastSender', () => {
87
89
  expect(sender.apiReady()).toBe(true);
88
90
  expect(sender.hasReceivers()).toBe(false);
89
91
  expect(onStatusChanged).toHaveBeenCalled();
90
- expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(fakeAppId);
92
+ expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
93
+ fakeAppId,
94
+ /* capabilities= */ [],
95
+ /* timeout= */ null,
96
+ fakeAndroidReceiverCompatible,
97
+ /* credentialsData= */ null);
91
98
  expect(mockCastApi.initialize).toHaveBeenCalled();
92
99
  });
93
100
 
@@ -97,7 +104,12 @@ describe('CastSender', () => {
97
104
  expect(sender.apiReady()).toBe(true);
98
105
  expect(sender.hasReceivers()).toBe(false);
99
106
  expect(onStatusChanged).toHaveBeenCalled();
100
- expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(fakeAppId);
107
+ expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
108
+ fakeAppId,
109
+ /* capabilities= */ [],
110
+ /* timeout= */ null,
111
+ fakeAndroidReceiverCompatible,
112
+ /* credentialsData= */ null);
101
113
  expect(mockCastApi.initialize).toHaveBeenCalled();
102
114
  });
103
115
  });
@@ -122,7 +134,8 @@ describe('CastSender', () => {
122
134
  sender = new CastSender(
123
135
  fakeAppId, Util.spyFunc(onStatusChanged),
124
136
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
125
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
137
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
138
+ /* androidReceiverCompatible= */ false);
126
139
  sender.init();
127
140
  // You get an initial call to onStatusChanged when it initializes.
128
141
  expect(onStatusChanged).toHaveBeenCalledTimes(3);
@@ -256,7 +269,8 @@ describe('CastSender', () => {
256
269
  sender = new CastSender(
257
270
  fakeAppId, Util.spyFunc(onStatusChanged),
258
271
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
259
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
272
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
273
+ /* androidReceiverCompatible= */ false);
260
274
  sender.init();
261
275
 
262
276
  // The sender should automatically rejoin the session, without needing
@@ -288,7 +302,8 @@ describe('CastSender', () => {
288
302
  sender = new CastSender(
289
303
  fakeAppId, Util.spyFunc(onStatusChanged),
290
304
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
291
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
305
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
306
+ /* androidReceiverCompatible= */ false);
292
307
  sender.init();
293
308
 
294
309
  expect(sender.isCasting()).toBe(false);
@@ -94,7 +94,8 @@ describe('Demo', () => {
94
94
  .add('playRangeStart')
95
95
  .add('playRangeEnd')
96
96
  .add('manifest.dash.keySystemsByURI')
97
- .add('manifest.hls.mediaPlaylistFullMimeType');
97
+ .add('manifest.hls.mediaPlaylistFullMimeType')
98
+ .add('drm.keySystemsMapping');
98
99
 
99
100
  /**
100
101
  * @param {!Object} section