shaka-player 4.8.3 → 4.8.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.
@@ -97,6 +97,7 @@ shaka.cea.CeaDecoder = class {
97
97
  * @override
98
98
  */
99
99
  clear() {
100
+ shaka.log.debug('Clearing CEA decoder');
100
101
  this.badFrames_ = 0;
101
102
  this.cea608DataArray_ = [];
102
103
  this.cea708DataArray_ = [];
@@ -113,6 +114,7 @@ shaka.cea.CeaDecoder = class {
113
114
  * Resets the decoder.
114
115
  */
115
116
  reset() {
117
+ shaka.log.debug('Resetting CEA decoder');
116
118
  this.currentField1Channel_ = 0;
117
119
  this.currentField2Channel_ = 0;
118
120
  for (const stream of this.cea608ModeToStream_.values()) {
@@ -128,6 +130,7 @@ shaka.cea.CeaDecoder = class {
128
130
  */
129
131
  extract(userDataSeiMessage, pts) {
130
132
  if (this.waitingForFirstPacket_) {
133
+ shaka.log.debug('Setting first pts value to', pts);
131
134
  for (const stream of this.cea608ModeToStream_.values()) {
132
135
  stream.firstPts(pts);
133
136
  }
@@ -9,6 +9,7 @@ goog.provide('shaka.media.IClosedCaptionParser');
9
9
 
10
10
  goog.require('shaka.cea.DummyCaptionDecoder');
11
11
  goog.require('shaka.cea.DummyCeaParser');
12
+ goog.require('shaka.log');
12
13
  goog.require('shaka.util.BufferUtils');
13
14
 
14
15
 
@@ -22,7 +23,8 @@ goog.require('shaka.util.BufferUtils');
22
23
  */
23
24
  shaka.media.IClosedCaptionParser = class {
24
25
  /**
25
- * Initialize the caption parser. This should be called only once.
26
+ * Initialize the caption parser. This should be called whenever new init
27
+ * segment arrives.
26
28
  * @param {BufferSource} initSegment
27
29
  */
28
30
  init(initSegment) {}
@@ -88,6 +90,10 @@ shaka.media.ClosedCaptionParser = class {
88
90
  * @override
89
91
  */
90
92
  init(initSegment) {
93
+ shaka.log.debug('Passing new init segment to CEA parser');
94
+ // Reset underlying decoder when new init segment arrives
95
+ // to clear stored pts values.
96
+ this.reset();
91
97
  this.ceaParser_.init(initSegment);
92
98
  }
93
99
 
@@ -347,14 +347,11 @@ shaka.media.MediaSourceEngine = class {
347
347
 
348
348
  const support = {};
349
349
  for (const type of testMimeTypes) {
350
- if (shaka.util.Platform.supportsMediaSource()) {
351
- // Our TextEngine is only effective for MSE platforms at the moment.
352
- if (shaka.text.TextEngine.isTypeSupported(type)) {
353
- support[type] = true;
354
- } else {
355
- support[type] = shaka.media.Capabilities.isTypeSupported(type) ||
356
- shaka.transmuxer.TransmuxerEngine.isSupported(type);
357
- }
350
+ if (shaka.text.TextEngine.isTypeSupported(type)) {
351
+ support[type] = true;
352
+ } else if (shaka.util.Platform.supportsMediaSource()) {
353
+ support[type] = shaka.media.Capabilities.isTypeSupported(type) ||
354
+ shaka.transmuxer.TransmuxerEngine.isSupported(type);
358
355
  } else {
359
356
  support[type] = shaka.util.Platform.supportsMediaType(type);
360
357
  }
@@ -470,7 +470,7 @@ shaka.media.SegmentUtils = class {
470
470
  if (!dolbyVision) {
471
471
  return codecs;
472
472
  }
473
- const type = `video/mp4; codecs="${dolbyVision}"'`;
473
+ const type = `video/mp4; codecs="${dolbyVision}"`;
474
474
  if (shaka.media.Capabilities.isTypeSupported(type)) {
475
475
  return [dolbyVision];
476
476
  }
package/lib/player.js CHANGED
@@ -715,6 +715,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
715
715
  this.manifestFilterer_ = new shaka.media.ManifestFilterer(
716
716
  this.config_, this.maxHwRes_, null);
717
717
 
718
+ /** @private {!Array.<shaka.media.PreloadManager>} */
719
+ this.createdPreloadManagers_ = [];
720
+
718
721
  /** @private {shaka.util.Stats} */
719
722
  this.stats_ = null;
720
723
 
@@ -881,6 +884,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
881
884
 
882
885
  await detachPromise;
883
886
 
887
+ // A PreloadManager can only be used with the Player instance that created
888
+ // it, so all PreloadManagers this Player has created are now useless.
889
+ // Destroy any remaining managers now, to help prevent memory leaks.
890
+ const preloadManagerDestroys = [];
891
+ for (const preloadManager of this.createdPreloadManagers_) {
892
+ if (!preloadManager.isDestroyed()) {
893
+ preloadManagerDestroys.push(preloadManager.destroy());
894
+ }
895
+ }
896
+ this.createdPreloadManagers_ = [];
897
+ await Promise.all(preloadManagerDestroys);
898
+
884
899
  // Tear-down the event managers to ensure handlers stop firing.
885
900
  if (this.globalEventManager_) {
886
901
  this.globalEventManager_.release();
@@ -1972,6 +1987,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1972
1987
  };
1973
1988
  preloadManager = new shaka.media.PreloadManager(
1974
1989
  assetUri, mimeType, startTimeOfLoad, startTime, playerInterface);
1990
+ this.createdPreloadManagers_.push(preloadManager);
1975
1991
  return preloadManager;
1976
1992
  }
1977
1993
 
@@ -7201,7 +7217,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7201
7217
  * @export
7202
7218
  */
7203
7219
  // eslint-disable-next-line no-useless-concat
7204
- shaka.Player.version = 'v4.8.3' + '-uncompiled'; // x-release-please-version
7220
+ shaka.Player.version = 'v4.8.4' + '-uncompiled'; // x-release-please-version
7205
7221
 
7206
7222
  // Initialize the deprecation system using the version string we just set
7207
7223
  // on the player.
@@ -88,6 +88,7 @@ shaka.polyfill.MediaCapabilities = class {
88
88
  * @private
89
89
  */
90
90
  static async decodingInfo_(mediaDecodingConfig) {
91
+ /** @type {!MediaCapabilitiesDecodingInfo} */
91
92
  const res = {
92
93
  supported: false,
93
94
  powerEfficient: true,
@@ -96,60 +97,26 @@ shaka.polyfill.MediaCapabilities = class {
96
97
  configuration: mediaDecodingConfig,
97
98
  };
98
99
 
99
- if (!mediaDecodingConfig) {
100
- return res;
101
- }
102
-
103
100
  const videoConfig = mediaDecodingConfig['video'];
104
101
  const audioConfig = mediaDecodingConfig['audio'];
105
102
 
106
- const Capabilities = shaka.media.Capabilities;
107
-
108
103
  if (mediaDecodingConfig.type == 'media-source') {
109
104
  if (!shaka.util.Platform.supportsMediaSource()) {
110
105
  return res;
111
106
  }
112
- // Use 'shaka.media.Capabilities.isTypeSupported'to check if
113
- // the stream is supported.
114
- // Cast platforms will additionally check canDisplayType(), which
115
- // accepts extended MIME type parameters.
116
- // See: https://github.com/shaka-project/shaka-player/issues/4726
107
+
117
108
  if (videoConfig) {
118
- let isSupported;
119
- if (shaka.util.Platform.isChromecast()) {
120
- isSupported =
121
- shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
122
- } else if (shaka.util.Platform.isTizen()) {
123
- let extendedType = videoConfig.contentType;
124
- if (videoConfig.width && videoConfig.height) {
125
- extendedType += `; width=${videoConfig.width}`;
126
- extendedType += `; height=${videoConfig.height}`;
127
- }
128
- if (videoConfig.framerate) {
129
- extendedType += `; framerate=${videoConfig.framerate}`;
130
- }
131
- if (videoConfig.bitrate) {
132
- extendedType += `; bitrate=${videoConfig.bitrate}`;
133
- }
134
- isSupported = Capabilities.isTypeSupported(extendedType);
135
- } else {
136
- isSupported = Capabilities.isTypeSupported(videoConfig.contentType);
137
- }
109
+ const isSupported =
110
+ await shaka.polyfill.MediaCapabilities.checkVideoSupport_(
111
+ videoConfig);
138
112
  if (!isSupported) {
139
113
  return res;
140
114
  }
141
115
  }
142
116
 
143
117
  if (audioConfig) {
144
- let isSupported;
145
- if (shaka.util.Platform.isChromecast() &&
146
- audioConfig.spatialRendering) {
147
- const extendedType =
148
- audioConfig.contentType + '; spatialRendering=true';
149
- isSupported = Capabilities.isTypeSupported(extendedType);
150
- } else {
151
- isSupported = Capabilities.isTypeSupported(audioConfig.contentType);
152
- }
118
+ const isSupported =
119
+ shaka.polyfill.MediaCapabilities.checkAudioSupport_(audioConfig);
153
120
  if (!isSupported) {
154
121
  return res;
155
122
  }
@@ -178,122 +145,172 @@ shaka.polyfill.MediaCapabilities = class {
178
145
  if (!mediaDecodingConfig.keySystemConfiguration) {
179
146
  // The variant is supported if it's unencrypted.
180
147
  res.supported = true;
181
- return Promise.resolve(res);
148
+ return res;
182
149
  } else {
183
- // Get the MediaKeySystemAccess for the key system.
184
- // Convert the MediaDecodingConfiguration object to a
185
- // MediaKeySystemConfiguration object.
186
-
187
- /** @type {MediaCapabilitiesKeySystemConfiguration} */
188
- const mediaCapkeySystemConfig =
189
- mediaDecodingConfig.keySystemConfiguration;
190
- const audioCapabilities = [];
191
- const videoCapabilities = [];
192
-
193
- if (mediaCapkeySystemConfig.audio) {
194
- let capability = {
195
- robustness: mediaCapkeySystemConfig.audio.robustness || '',
196
- contentType: mediaDecodingConfig.audio.contentType,
197
- };
198
-
199
- // Some Tizen devices seem to misreport AC-3 support, but correctly
200
- // report EC-3 support. So query EC-3 as a fallback for AC-3.
201
- // See https://github.com/shaka-project/shaka-player/issues/2989 for
202
- // details.
203
- if (shaka.util.Platform.isTizen() &&
204
- mediaDecodingConfig.audio.contentType.includes('codecs="ac-3"')) {
205
- capability = {
206
- robustness: capability.robustness,
207
- contentType: 'audio/mp4; codecs="ec-3"',
208
- };
209
- }
150
+ const mcapKeySystemConfig = mediaDecodingConfig.keySystemConfiguration;
151
+ const keySystemAccess =
152
+ await shaka.polyfill.MediaCapabilities.checkDrmSupport_(
153
+ videoConfig, audioConfig, mcapKeySystemConfig);
154
+ if (keySystemAccess) {
155
+ res.supported = true;
156
+ res.keySystemAccess = keySystemAccess;
157
+ }
158
+ }
210
159
 
211
- if (mediaCapkeySystemConfig.audio.encryptionScheme) {
212
- capability = {
213
- robustness: capability.robustness,
214
- contentType: capability.contentType,
215
- encryptionScheme: mediaCapkeySystemConfig.audio.encryptionScheme,
216
- };
217
- }
160
+ return res;
161
+ }
218
162
 
219
- audioCapabilities.push(capability);
163
+ /**
164
+ * @param {!VideoConfiguration} videoConfig The 'video' field of the
165
+ * MediaDecodingConfiguration.
166
+ * @return {!Promise<boolean>}
167
+ * @private
168
+ */
169
+ static async checkVideoSupport_(videoConfig) {
170
+ // Use 'shaka.media.Capabilities.isTypeSupported' to check if
171
+ // the stream is supported.
172
+ // Cast platforms will additionally check canDisplayType(), which
173
+ // accepts extended MIME type parameters.
174
+ // See: https://github.com/shaka-project/shaka-player/issues/4726
175
+ if (shaka.util.Platform.isChromecast()) {
176
+ const isSupported =
177
+ await shaka.polyfill.MediaCapabilities.canCastDisplayType_(
178
+ videoConfig);
179
+ return isSupported;
180
+ } else if (shaka.util.Platform.isTizen()) {
181
+ let extendedType = videoConfig.contentType;
182
+ if (videoConfig.width && videoConfig.height) {
183
+ extendedType += `; width=${videoConfig.width}`;
184
+ extendedType += `; height=${videoConfig.height}`;
220
185
  }
221
-
222
- if (mediaCapkeySystemConfig.video) {
223
- let capability = {
224
- robustness: mediaCapkeySystemConfig.video.robustness || '',
225
- contentType: mediaDecodingConfig.video.contentType,
226
- };
227
- if (mediaCapkeySystemConfig.video.encryptionScheme) {
228
- capability = {
229
- robustness: capability.robustness,
230
- contentType: capability.contentType,
231
- encryptionScheme: mediaCapkeySystemConfig.video.encryptionScheme,
232
- };
233
- }
234
- videoCapabilities.push(capability);
186
+ if (videoConfig.framerate) {
187
+ extendedType += `; framerate=${videoConfig.framerate}`;
188
+ }
189
+ if (videoConfig.bitrate) {
190
+ extendedType += `; bitrate=${videoConfig.bitrate}`;
235
191
  }
192
+ return shaka.media.Capabilities.isTypeSupported(extendedType);
193
+ }
194
+ return shaka.media.Capabilities.isTypeSupported(videoConfig.contentType);
195
+ }
196
+
197
+ /**
198
+ * @param {!AudioConfiguration} audioConfig The 'audio' field of the
199
+ * MediaDecodingConfiguration.
200
+ * @return {boolean}
201
+ * @private
202
+ */
203
+ static checkAudioSupport_(audioConfig) {
204
+ let extendedType = audioConfig.contentType;
205
+ if (shaka.util.Platform.isChromecast() && audioConfig.spatialRendering) {
206
+ extendedType += '; spatialRendering=true';
207
+ }
208
+ return shaka.media.Capabilities.isTypeSupported(extendedType);
209
+ }
236
210
 
237
- /** @type {MediaKeySystemConfiguration} */
238
- const mediaKeySystemConfig = {
239
- initDataTypes: [mediaCapkeySystemConfig.initDataType],
240
- distinctiveIdentifier: mediaCapkeySystemConfig.distinctiveIdentifier,
241
- persistentState: mediaCapkeySystemConfig.persistentState,
242
- sessionTypes: mediaCapkeySystemConfig.sessionTypes,
211
+ /**
212
+ * @param {VideoConfiguration} videoConfig The 'video' field of the
213
+ * MediaDecodingConfiguration.
214
+ * @param {AudioConfiguration} audioConfig The 'audio' field of the
215
+ * MediaDecodingConfiguration.
216
+ * @param {!MediaCapabilitiesKeySystemConfiguration} mcapKeySystemConfig The
217
+ * 'keySystemConfiguration' field of the MediaDecodingConfiguration.
218
+ * @return {Promise<MediaKeySystemAccess>}
219
+ * @private
220
+ */
221
+ static async checkDrmSupport_(videoConfig, audioConfig, mcapKeySystemConfig) {
222
+ const audioCapabilities = [];
223
+ const videoCapabilities = [];
224
+
225
+ if (mcapKeySystemConfig.audio) {
226
+ const capability = {
227
+ robustness: mcapKeySystemConfig.audio.robustness || '',
228
+ contentType: audioConfig.contentType,
243
229
  };
244
230
 
245
- // Only add the audio video capabilities if they have valid data.
246
- // Otherwise the query will fail.
247
- if (audioCapabilities.length) {
248
- mediaKeySystemConfig.audioCapabilities = audioCapabilities;
231
+ // Some Tizen devices seem to misreport AC-3 support, but correctly
232
+ // report EC-3 support. So query EC-3 as a fallback for AC-3.
233
+ // See https://github.com/shaka-project/shaka-player/issues/2989 for
234
+ // details.
235
+ if (shaka.util.Platform.isTizen() &&
236
+ audioConfig.contentType.includes('codecs="ac-3"')) {
237
+ capability.contentType = 'audio/mp4; codecs="ec-3"';
249
238
  }
250
- if (videoCapabilities.length) {
251
- mediaKeySystemConfig.videoCapabilities = videoCapabilities;
239
+
240
+ if (mcapKeySystemConfig.audio.encryptionScheme) {
241
+ capability.encryptionScheme =
242
+ mcapKeySystemConfig.audio.encryptionScheme;
252
243
  }
253
244
 
254
- const cacheKey = shaka.polyfill.MediaCapabilities
255
- .generateKeySystemCacheKey_(
256
- mediaDecodingConfig.video ?
257
- mediaDecodingConfig.video.contentType : '',
258
- mediaDecodingConfig.audio ?
259
- mediaDecodingConfig.audio.contentType : '',
260
- mediaDecodingConfig.keySystemConfiguration.keySystem);
261
-
262
- let keySystemAccess;
263
- try {
264
- if (cacheKey in shaka.polyfill.MediaCapabilities
265
- .memoizedMediaKeySystemAccessRequests_) {
266
- keySystemAccess = shaka.polyfill.MediaCapabilities
267
- .memoizedMediaKeySystemAccessRequests_[cacheKey];
268
- } else {
269
- keySystemAccess = await navigator.requestMediaKeySystemAccess(
270
- mediaCapkeySystemConfig.keySystem, [mediaKeySystemConfig]);
271
- shaka.polyfill.MediaCapabilities
272
- .memoizedMediaKeySystemAccessRequests_[cacheKey] =
273
- keySystemAccess;
274
- }
275
- } catch (e) {
276
- shaka.log.info('navigator.requestMediaKeySystemAccess failed.');
245
+ audioCapabilities.push(capability);
246
+ }
247
+
248
+ if (mcapKeySystemConfig.video) {
249
+ const capability = {
250
+ robustness: mcapKeySystemConfig.video.robustness || '',
251
+ contentType: videoConfig.contentType,
252
+ };
253
+ if (mcapKeySystemConfig.video.encryptionScheme) {
254
+ capability.encryptionScheme =
255
+ mcapKeySystemConfig.video.encryptionScheme;
277
256
  }
257
+ videoCapabilities.push(capability);
258
+ }
278
259
 
279
- if (keySystemAccess) {
280
- res.supported = true;
281
- res.keySystemAccess = keySystemAccess;
260
+ /** @type {MediaKeySystemConfiguration} */
261
+ const mediaKeySystemConfig = {
262
+ initDataTypes: [mcapKeySystemConfig.initDataType],
263
+ distinctiveIdentifier: mcapKeySystemConfig.distinctiveIdentifier,
264
+ persistentState: mcapKeySystemConfig.persistentState,
265
+ sessionTypes: mcapKeySystemConfig.sessionTypes,
266
+ };
267
+
268
+ // Only add audio / video capabilities if they have valid data.
269
+ // Otherwise the query will fail.
270
+ if (audioCapabilities.length) {
271
+ mediaKeySystemConfig.audioCapabilities = audioCapabilities;
272
+ }
273
+ if (videoCapabilities.length) {
274
+ mediaKeySystemConfig.videoCapabilities = videoCapabilities;
275
+ }
276
+
277
+ const cacheKey = shaka.polyfill.MediaCapabilities
278
+ .generateKeySystemCacheKey_(
279
+ videoConfig ? videoConfig.contentType : '',
280
+ audioConfig ? audioConfig.contentType : '',
281
+ mcapKeySystemConfig.keySystem);
282
+
283
+ /** @type {MediaKeySystemAccess} */
284
+ let keySystemAccess = null;
285
+ try {
286
+ if (cacheKey in shaka.polyfill.MediaCapabilities
287
+ .memoizedMediaKeySystemAccessRequests_) {
288
+ keySystemAccess = shaka.polyfill.MediaCapabilities
289
+ .memoizedMediaKeySystemAccessRequests_[cacheKey];
290
+ } else {
291
+ keySystemAccess = await navigator.requestMediaKeySystemAccess(
292
+ mcapKeySystemConfig.keySystem, [mediaKeySystemConfig]);
293
+ shaka.polyfill.MediaCapabilities
294
+ .memoizedMediaKeySystemAccessRequests_[cacheKey] =
295
+ keySystemAccess;
282
296
  }
297
+ } catch (e) {
298
+ shaka.log.info('navigator.requestMediaKeySystemAccess failed.');
283
299
  }
284
300
 
285
- return res;
301
+ return keySystemAccess;
286
302
  }
287
303
 
288
304
  /**
289
305
  * Checks if the given media parameters of the video or audio streams are
290
306
  * supported by the Cast platform.
291
307
  * @param {!VideoConfiguration} videoConfig The 'video' field of the
292
- * MediaDecodingConfiguration.
293
- * @return {boolean} `true` when the stream can be displayed on a Cast device.
308
+ * MediaDecodingConfiguration.
309
+ * @return {!Promise<boolean>} `true` when the stream can be displayed on a
310
+ * Cast device.
294
311
  * @private
295
312
  */
296
- static canCastDisplayType_(videoConfig) {
313
+ static async canCastDisplayType_(videoConfig) {
297
314
  if (!(window.cast &&
298
315
  cast.__platform__ && cast.__platform__.canDisplayType)) {
299
316
  shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
@@ -321,7 +338,7 @@ shaka.polyfill.MediaCapabilities = class {
321
338
  result = shaka.polyfill.MediaCapabilities
322
339
  .memoizedCanDisplayTypeRequests_[displayType];
323
340
  } else {
324
- result = cast.__platform__.canDisplayType(displayType);
341
+ result = await cast.__platform__.canDisplayType(displayType);
325
342
  shaka.polyfill.MediaCapabilities
326
343
  .memoizedCanDisplayTypeRequests_[displayType] = result;
327
344
  }
@@ -22,6 +22,7 @@ goog.require('shaka.util.Id3Utils');
22
22
  goog.require('shaka.util.ManifestParserUtils');
23
23
  goog.require('shaka.util.MimeUtils');
24
24
  goog.require('shaka.util.Mp4Generator');
25
+ goog.require('shaka.util.StreamUtils');
25
26
  goog.require('shaka.util.TsParser');
26
27
  goog.require('shaka.util.Uint8ArrayUtils');
27
28
 
@@ -149,7 +150,7 @@ shaka.transmuxer.TsTransmuxer = class {
149
150
  // MediaSource.isTypeSupported('audio/mp4; codecs="mp3"') -> true
150
151
  const codecs = shaka.util.MimeUtils.getCodecs(mimeType)
151
152
  .replace('mp4a.40.34', 'mp3').split(',')
152
- .map(this.getCorrectVideoCodecs_).join(',');
153
+ .map(shaka.util.StreamUtils.getCorrectVideoCodecs).join(',');
153
154
  if (contentType == ContentType.AUDIO) {
154
155
  return `audio/mp4; codecs="${codecs}"`;
155
156
  }
@@ -159,29 +160,6 @@ shaka.transmuxer.TsTransmuxer = class {
159
160
  }
160
161
 
161
162
 
162
- /**
163
- * @param {string} codec
164
- * @return {string}
165
- * @private
166
- */
167
- getCorrectVideoCodecs_(codec) {
168
- if (codec.includes('avc1')) {
169
- // Convert avc1 codec string from RFC-4281 to RFC-6381 for
170
- // MediaSource.isTypeSupported
171
- // Example, convert avc1.66.30 to avc1.42001e (0x42 == 66 and 0x1e == 30)
172
- const avcdata = codec.split('.');
173
- if (avcdata.length == 3) {
174
- let result = avcdata.shift() + '.';
175
- result += parseInt(avcdata.shift(), 10).toString(16);
176
- result +=
177
- ('000' + parseInt(avcdata.shift(), 10).toString(16)).slice(-4);
178
- return result;
179
- }
180
- }
181
- return codec;
182
- }
183
-
184
-
185
163
  /**
186
164
  * @override
187
165
  * @export
@@ -197,6 +197,17 @@ shaka.util.Platform = class {
197
197
  return Platform.isChromecast() && Platform.isAndroid();
198
198
  }
199
199
 
200
+ /**
201
+ * Check if the current platform is a Google Chromecast with Android
202
+ * (i.e. Chromecast with GoogleTV).
203
+ *
204
+ * @return {boolean}
205
+ */
206
+ static isFuchsiaCastDevice() {
207
+ const Platform = shaka.util.Platform;
208
+ return Platform.isChromecast() && Platform.isFuchsia();
209
+ }
210
+
200
211
  /**
201
212
  * Returns a major version number for Chrome, or Chromium-based browsers.
202
213
  *
@@ -459,6 +470,15 @@ shaka.util.Platform = class {
459
470
  return shaka.util.Platform.userAgentContains_('Android');
460
471
  }
461
472
 
473
+ /**
474
+ * Return true if the platform is a Fuchsia, regardless of the browser.
475
+ *
476
+ * @return {boolean}
477
+ */
478
+ static isFuchsia() {
479
+ return shaka.util.Platform.userAgentContains_('Fuchsia');
480
+ }
481
+
462
482
  /**
463
483
  * Return true if the platform is controlled by a remote control.
464
484
  *
@@ -611,19 +631,32 @@ shaka.util.Platform = class {
611
631
 
612
632
  if (Platform.isChromecast()) {
613
633
  // In our tests, the original Chromecast seems to have trouble decoding
614
- // above 1080p. It would be a waste to select a higher res anyway, given
634
+ // above 1080p. It would be a waste to select a higher res anyway, given
615
635
  // that the device only outputs 1080p to begin with.
616
636
  // Chromecast has an extension to query the device/display's resolution.
617
- if (window.cast && cast.__platform__ &&
618
- cast.__platform__.canDisplayType && cast.__platform__.canDisplayType(
619
- 'video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
620
- // The device and display can both do 4k. Assume a 4k limit.
621
- maxResolution.width = 3840;
622
- maxResolution.height = 2160;
623
- } else {
624
- // Chromecast has always been able to do 1080p. Assume a 1080p limit.
625
- maxResolution.width = 1920;
626
- maxResolution.height = 1080;
637
+ const hasCanDisplayType = window.cast && cast.__platform__ &&
638
+ cast.__platform__.canDisplayType;
639
+
640
+ // Some hub devices can only do 720p. Default to that.
641
+ maxResolution.width = 1280;
642
+ maxResolution.height = 720;
643
+
644
+ try {
645
+ if (hasCanDisplayType && await cast.__platform__.canDisplayType(
646
+ 'video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
647
+ // The device and display can both do 4k. Assume a 4k limit.
648
+ maxResolution.width = 3840;
649
+ maxResolution.height = 2160;
650
+ } else if (hasCanDisplayType && await cast.__platform__.canDisplayType(
651
+ 'video/mp4; codecs="avc1.640028"; width=1920; height=1080')) {
652
+ // Most Chromecasts can do 1080p.
653
+ maxResolution.width = 1920;
654
+ maxResolution.height = 1080;
655
+ }
656
+ } catch (error) {
657
+ // This shouldn't generally happen. Log the error.
658
+ shaka.log.alwaysError('Failed to check canDisplayType:', error);
659
+ // Now ignore the error and let the 720p default stand.
627
660
  }
628
661
  } else if (Platform.isTizen()) {
629
662
  maxResolution.width = 1920;
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.8.3",
4
+ "version": "4.8.4",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -63,6 +63,11 @@
63
63
  "tippy.js": "^4.3.5",
64
64
  "which": "^2.0.2"
65
65
  },
66
+ "overrides": {
67
+ "karma": {
68
+ "ua-parser-js": "github:joeyparrish/ua-parser-js#preview"
69
+ }
70
+ },
66
71
  "shakaCustom": {
67
72
  "purposeOfDemoDeps": "These are the files from node_modules that need to be deployed with the demo app.",
68
73
  "demoDeps": [