shaka-player 4.3.4 → 4.3.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 (56) hide show
  1. package/.github/workflows/build-and-test.yaml +4 -4
  2. package/.github/workflows/demo-version-index.yaml +6 -0
  3. package/.github/workflows/selenium-lab-tests.yaml +55 -16
  4. package/.github/workflows/update-screenshots.yaml +142 -0
  5. package/CHANGELOG.md +24 -0
  6. package/demo/common/asset.js +15 -0
  7. package/demo/common/assets.js +4 -2
  8. package/demo/common/message_ids.js +1 -0
  9. package/demo/custom.js +19 -0
  10. package/demo/locales/en.json +1 -0
  11. package/demo/locales/source.json +4 -0
  12. package/demo/main.js +14 -0
  13. package/dist/demo.compiled.debug.js +30 -28
  14. package/dist/demo.compiled.debug.map +3 -3
  15. package/dist/demo.compiled.js +30 -28
  16. package/dist/demo.compiled.map +3 -3
  17. package/dist/deps.js +1 -1
  18. package/dist/locales.js +30 -30
  19. package/dist/receiver.compiled.debug.js +22 -22
  20. package/dist/receiver.compiled.debug.map +2 -2
  21. package/dist/receiver.compiled.js +22 -22
  22. package/dist/receiver.compiled.map +2 -2
  23. package/dist/shaka-player.compiled.d.ts +7 -0
  24. package/dist/shaka-player.compiled.debug.d.ts +7 -0
  25. package/dist/shaka-player.compiled.debug.js +33 -31
  26. package/dist/shaka-player.compiled.debug.map +3 -3
  27. package/dist/shaka-player.compiled.js +786 -785
  28. package/dist/shaka-player.compiled.map +3 -3
  29. package/dist/shaka-player.ui.d.ts +7 -0
  30. package/dist/shaka-player.ui.debug.d.ts +7 -0
  31. package/dist/shaka-player.ui.debug.js +35 -33
  32. package/dist/shaka-player.ui.debug.map +3 -3
  33. package/dist/shaka-player.ui.js +892 -890
  34. package/dist/shaka-player.ui.map +3 -3
  35. package/docs/tutorials/errors.md +94 -0
  36. package/docs/tutorials/fairplay.md +2 -2
  37. package/docs/tutorials/index.json +2 -0
  38. package/docs/tutorials/widevine-service-certs.md +45 -0
  39. package/externs/ima.js +9 -0
  40. package/lib/ads/client_side_ad_manager.js +6 -3
  41. package/lib/hls/hls_parser.js +6 -1
  42. package/lib/media/drm_engine.js +4 -2
  43. package/lib/media/transmuxer.js +13 -7
  44. package/lib/media/video_wrapper.js +3 -3
  45. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  46. package/lib/player.js +3 -1
  47. package/lib/polyfill/media_capabilities.js +2 -8
  48. package/lib/polyfill/mediasource.js +24 -2
  49. package/lib/text/ui_text_displayer.js +1 -0
  50. package/lib/text/vtt_text_parser.js +4 -5
  51. package/lib/util/mime_utils.js +1 -1
  52. package/lib/util/stream_utils.js +26 -14
  53. package/package.json +1 -1
  54. package/test/polyfill/media_capabilities_unit.js +21 -22
  55. package/test/text/vtt_text_parser_unit.js +26 -2
  56. package/test/text/web_vtt_layout_integration.js +0 -3
@@ -0,0 +1,94 @@
1
+ # Error Handling
2
+
3
+ The basics for handling native Error and {@link shaka.util.Error} thrown by Shaka.
4
+
5
+ ## Listening To and Handling Errors
6
+
7
+ ```javascript
8
+ const handleError = (error) => {
9
+ if (error instanceof Error) {
10
+ // shaka crashed with an unhandled native error
11
+ }
12
+
13
+ if (error.severity === shaka.util.Error.Severity.CRITICAL) {
14
+ // handle fatal error, playback can not continue
15
+ } else {
16
+ // handle non-fatal error, playback can continue
17
+ }
18
+ };
19
+
20
+ const player = new shaka.Player(video);
21
+
22
+ // handle errors that occur after load
23
+ player.addEventListener('error', handleError);
24
+
25
+ // there are two options for catching errors that occur during load
26
+
27
+ // it's possible to listen directly on the promise
28
+ player.load(url).catch(handleError);
29
+
30
+ // or to use async/await with a try/catch
31
+ try {
32
+ await player.load(url);
33
+ } catch (e) {
34
+ handleError(e);
35
+ }
36
+ ```
37
+
38
+ ## Custom Handling of Streaming Errors
39
+
40
+ The `streaming.failureCallback` property of {@link shaka.extern.PlayerConfiguration} can be used to add custom handling or error conversion of errors that occur during streaming.
41
+
42
+ ```javascript
43
+ player.configure('streaming.failureCallback', (error) => {
44
+ if (error.severity === shaka.util.Error.Severity.CRITICAL) {
45
+ // custom handling of critical error
46
+ // e.g. player.retryStreaming();
47
+ } else {
48
+ // custom handling of recoverable error
49
+ }
50
+ });
51
+ ```
52
+
53
+ ## Custom Handling of Retries
54
+
55
+ When configuring retry parameters in Shaka there may be known error codes that should not be retried, or the need to break out of an infinite retry loop in a live context. Retries for failed network requests are not reported as `RECOVERABLE` errors.
56
+
57
+ For example, if a VOD manifest is missing, unlike when a LIVE manifest is missing, it can be expected to not show up and there is no need to retry.
58
+
59
+ This is how to convert a retry into a critical error, see {@link shaka.net.NetworkingEngine.RetryEvent} and {@link shaka.util.Error} to decipher how the event can be parsed:
60
+
61
+ ```javascript
62
+ const nwEngine = player.getNetworkingEngine();
63
+
64
+ const vodManifestNotFoundHandler = (event /* shaka.net.NetworkingEngine.RetryEvent */) => {
65
+ const code = event.error.code;
66
+ const data = event.error.data;
67
+
68
+ if (code === shaka.util.Error.Code.BAD_HTTP_STATUS) {
69
+ if (
70
+ // each type of error has its own data structure (or none at all), tread with care
71
+ Array.isArray(data) &&
72
+ data[1] === 404 &&
73
+ data[4] === shaka.net.NetworkingEngine.RequestType.MANIFEST
74
+ ) {
75
+ // Throwing inside a retry callback will immediately stop retries
76
+ throw error;
77
+
78
+ // A proprietary error code can also be thrown
79
+ // throw new shaka.util.Error(
80
+ // shaka.util.Error.Severity.CRITICAL,
81
+ // shaka.util.Error.Category.NETWORK,
82
+ // 'RECOGNIZABLE_ERROR_MESSAGE'
83
+ // );
84
+ }
85
+ }
86
+ };
87
+
88
+ nwEngine.addEventListener('retry', vodManifestNotFoundHandler);
89
+
90
+ player.addEventListener('load'() => {
91
+ nwEngine.removeEventListener('retry', vodManifestNotFoundHandler);
92
+ });
93
+
94
+ ```
@@ -53,12 +53,12 @@ you.
53
53
  const req = await fetch('https://example.com/cert.der');
54
54
  const cert = await req.arrayBuffer();
55
55
 
56
- player.configure('drm.advanced.com\\.apple\\.fps\\.serverCertificate',
56
+ player.configure('drm.advanced.com\\.apple\\.fps.serverCertificate',
57
57
  new Uint8Array(cert));
58
58
  ```
59
59
 
60
60
  ```js
61
- player.configure('drm.advanced.com\\.apple\\.fps\\.serverCertificateUri',
61
+ player.configure('drm.advanced.com\\.apple\\.fps.serverCertificateUri',
62
62
  'https://example.com/cert.der');
63
63
  ```
64
64
 
@@ -9,6 +9,7 @@
9
9
  { "license-wrapping": { "title": "License Wrapping" } },
10
10
  { "ui": { "title": "UI Library" } },
11
11
  { "ui-customization": { "title": "Configuring the UI" } },
12
+ { "errors": { "title": "Error Handling" } },
12
13
  { "a11y": { "title": "Creating accessible buttons" } },
13
14
  { "ad_monetization": { "title": "Serving ads with our IMA SDK integration" } },
14
15
  { "plugins": { "title": "Plugins and Customizing the Build" } },
@@ -16,6 +17,7 @@
16
17
  { "architecture": { "title": "Architecture Diagrams" } },
17
18
  { "service-worker": { "title": "Service Worker Caching" } },
18
19
  { "offline": { "title": "Offline Storage and Playback" } },
20
+ { "widevine-service-certs": { "title": "Widevine Service Certificates" } },
19
21
  { "fairplay": { "title": "FairPlay support" } },
20
22
  { "application-level-redirects": { "title": "Application-Level Redirects" } },
21
23
  { "blob-url": { "title": "Blob URL" } },
@@ -0,0 +1,45 @@
1
+ # Widevine Service Certificates
2
+
3
+ With Widevine, you can eliminate one round-trip to your license server or proxy
4
+ per session by preloading a Widevine service certificate. You can either provide
5
+ it directly or set a URI and let Shaka fetch it for you. Do this before calling
6
+ `player.load()`. The values will persist across multiple calls to `load()` on
7
+ the same `shaka.Player` instance.
8
+
9
+ ```js
10
+ // This is an example of loading the certificate from your site at runtime.
11
+ // You could also choose to bundle it into your JavaScript as a Uint8Array.
12
+ const req = await fetch('https://example.com/service.cert');
13
+ const cert = new Uint8Array(await req.arrayBuffer());
14
+
15
+ // This is the short form for configuration of a certificate:
16
+ player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificate',
17
+ cert);
18
+
19
+ // This is the long form:
20
+ player.configure({
21
+ drm: {
22
+ advanced: {
23
+ 'com.widevine.alpha': {
24
+ 'serverCertificate': cert,
25
+ },
26
+ },
27
+ },
28
+ });
29
+
30
+
31
+ // This is the short form for configuration of a certificate URI:
32
+ player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificateUri',
33
+ 'https://example.com/service.cert');
34
+
35
+ // This is the long form:
36
+ player.configure({
37
+ drm: {
38
+ advanced: {
39
+ 'com.widevine.alpha': {
40
+ 'serverCertificateUri': 'https://example.com/service.cert',
41
+ },
42
+ },
43
+ },
44
+ });
45
+ ```
package/externs/ima.js CHANGED
@@ -513,6 +513,15 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
513
513
  google.ima.dai.api.StreamRequest.prototype.format;
514
514
 
515
515
 
516
+ /**
517
+ * @enum {string}
518
+ */
519
+ google.ima.dai.api.StreamRequest.StreamFormat = {
520
+ DASH: 'dash',
521
+ HLS: 'hls',
522
+ };
523
+
524
+
516
525
  /** @const */
517
526
  google.ima.dai.api.VODStreamRequest =
518
527
  class extends google.ima.dai.api.StreamRequest {};
@@ -79,9 +79,9 @@ shaka.ads.ClientSideAdManager = class {
79
79
  });
80
80
 
81
81
  // Notify the SDK when the video has ended, so it can play post-roll ads.
82
- this.video_.onended = () => {
82
+ this.eventManager_.listen(this.video_, 'ended', () => {
83
83
  this.adsLoader_.contentComplete();
84
- };
84
+ });
85
85
  }
86
86
 
87
87
  /**
@@ -440,6 +440,7 @@ shaka.ads.ClientSideAdManager = class {
440
440
  if (this.ad_.isLinear()) {
441
441
  this.adContainer_.setAttribute('ad-active', 'true');
442
442
  this.video_.pause();
443
+ this.ad_.setVolume(this.video_.muted ? 0 : this.video_.volume);
443
444
  }
444
445
  }
445
446
 
@@ -452,7 +453,9 @@ shaka.ads.ClientSideAdManager = class {
452
453
  (new Map()).set('originalEvent', e)));
453
454
  if (this.ad_ && this.ad_.isLinear()) {
454
455
  this.adContainer_.removeAttribute('ad-active');
455
- this.video_.play();
456
+ if (!this.video_.ended) {
457
+ this.video_.play();
458
+ }
456
459
  }
457
460
  }
458
461
  };
@@ -311,7 +311,10 @@ shaka.hls.HlsParser = class {
311
311
  const PresentationType = shaka.hls.HlsParser.PresentationType_;
312
312
  this.setPresentationType_(PresentationType.VOD);
313
313
 
314
- const maxTimestamps = streamInfos.map((s) => s.maxTimestamp);
314
+ // The duration is the minimum of the end times of all active streams.
315
+ // Non-active streams are not guaranteed to have useful maxTimestamp
316
+ // values, due to the lazy-loading system, so they are ignored.
317
+ const maxTimestamps = activeStreamInfos.map((s) => s.maxTimestamp);
315
318
  // The duration is the minimum of the end times of all streams.
316
319
  this.presentationTimeline_.setDuration(Math.min(...maxTimestamps));
317
320
  this.playerInterface_.updateDuration();
@@ -3317,6 +3320,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
3317
3320
  'cmfa': 'audio/mp4',
3318
3321
  // MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
3319
3322
  'ts': 'video/mp2t',
3323
+ 'tsa': 'video/mp2t',
3320
3324
  };
3321
3325
 
3322
3326
 
@@ -3333,6 +3337,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
3333
3337
  'm4f': 'video/mp4',
3334
3338
  'cmfv': 'video/mp4',
3335
3339
  'ts': 'video/mp2t',
3340
+ 'tsv': 'video/mp2t',
3336
3341
  };
3337
3342
 
3338
3343
 
@@ -480,8 +480,10 @@ shaka.media.DrmEngine = class {
480
480
  () => this.closeOpenSessions_());
481
481
  }
482
482
 
483
- const manifestInitData = this.currentDrmInfo_.initData.find(
484
- (initDataOverride) => initDataOverride.initData.length > 0);
483
+ const manifestInitData = this.currentDrmInfo_ ?
484
+ this.currentDrmInfo_.initData.find(
485
+ (initDataOverride) => initDataOverride.initData.length > 0,
486
+ ) : null;
485
487
 
486
488
  /**
487
489
  * We can attach media keys before the playback actually begins when:
@@ -35,9 +35,7 @@ shaka.media.Transmuxer = class {
35
35
  this.muxjs_ = shaka.dependencies.muxjs();
36
36
 
37
37
  /** @private {muxjs.mp4.Transmuxer} */
38
- this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
39
- 'keepOriginalTimestamps': true,
40
- });
38
+ this.muxTransmuxer_ = null;
41
39
 
42
40
  /** @private {shaka.util.PublicPromise} */
43
41
  this.transmuxPromise_ = null;
@@ -51,16 +49,22 @@ shaka.media.Transmuxer = class {
51
49
  /** @private {boolean} */
52
50
  this.isTransmuxing_ = false;
53
51
 
54
- this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
55
-
56
- this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
52
+ if (this.muxjs_) {
53
+ this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
54
+ 'keepOriginalTimestamps': true,
55
+ });
56
+ this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
57
+ this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
58
+ }
57
59
  }
58
60
 
59
61
  /**
60
62
  * @override
61
63
  */
62
64
  destroy() {
63
- this.muxTransmuxer_.dispose();
65
+ if (this.muxTransmuxer_) {
66
+ this.muxTransmuxer_.dispose();
67
+ }
64
68
  this.muxTransmuxer_ = null;
65
69
  return Promise.resolve();
66
70
  }
@@ -216,6 +220,8 @@ shaka.media.Transmuxer = class {
216
220
  * captions: !Array.<!muxjs.mp4.ClosedCaption>}>}
217
221
  */
218
222
  transmux(data) {
223
+ goog.asserts.assert(this.muxTransmuxer_,
224
+ 'mux.js should be available.');
219
225
  goog.asserts.assert(!this.isTransmuxing_,
220
226
  'No transmuxing should be in progress.');
221
227
  this.isTransmuxing_ = true;
@@ -135,15 +135,15 @@ shaka.media.VideoWrapper = class {
135
135
  // calling |Player.load|, meaning that |currentTime| is more meaningful than
136
136
  // |startTime|.
137
137
  //
138
- // Seeking to the current time is a work around for Issue 1298. If we don't
139
- // do this, the video may get stuck and not play.
138
+ // Seeking to the current time is a work around for Issue 1298 and 4888.
139
+ // If we don't do this, the video may get stuck and not play.
140
140
  //
141
141
  // TODO: Need further investigation why it happens. Before and after
142
142
  // setting the current time, video.readyState is 1, video.paused is true,
143
143
  // and video.buffered's TimeRanges length is 0.
144
144
  // See: https://github.com/shaka-project/shaka-player/issues/1298
145
145
  this.mover_.moveTo(
146
- this.video_.currentTime == 0 ?
146
+ (!this.video_.currentTime || this.video_.currentTime == 0) ?
147
147
  startTime :
148
148
  this.video_.currentTime);
149
149
  }
@@ -67,7 +67,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
67
67
  shaka.util.Error.Category.STORAGE,
68
68
  shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
69
69
  });
70
- timeOutTimer.tickAfter(2);
70
+ timeOutTimer.tickAfter(5);
71
71
 
72
72
  const open = window.indexedDB.open(name, version);
73
73
  open.onsuccess = (event) => {
package/lib/player.js CHANGED
@@ -2584,6 +2584,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2584
2584
  // goes wrong.
2585
2585
  new shaka.util.Timer(resolve).tickAfter(1);
2586
2586
  });
2587
+ } else if (textTracks.length > 0) {
2588
+ this.isTextVisible_ = true;
2587
2589
  }
2588
2590
 
2589
2591
  // If we have moved on to another piece of content while waiting for
@@ -6984,7 +6986,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6984
6986
  * @define {string} A version number taken from git at compile time.
6985
6987
  * @export
6986
6988
  */
6987
- shaka.Player.version = 'v4.3.4-uncompiled';
6989
+ shaka.Player.version = 'v4.3.5-uncompiled';
6988
6990
 
6989
6991
  // Initialize the deprecation system using the version string we just set
6990
6992
  // on the player.
@@ -9,7 +9,6 @@ goog.provide('shaka.polyfill.MediaCapabilities');
9
9
  goog.require('shaka.log');
10
10
  goog.require('shaka.media.Capabilities');
11
11
  goog.require('shaka.polyfill');
12
- goog.require('shaka.util.Error');
13
12
  goog.require('shaka.util.Platform');
14
13
 
15
14
 
@@ -230,13 +229,8 @@ shaka.polyfill.MediaCapabilities = class {
230
229
  * @private
231
230
  */
232
231
  static canCastDisplayType_(videoConfig) {
233
- if (!(window.cast)) {
234
- shaka.log.error('Expected cast namespace to be available!');
235
- throw new shaka.util.Error(
236
- shaka.util.Error.Severity.CRITICAL,
237
- shaka.util.Error.Category.CAST,
238
- shaka.util.Error.Code.CAST_API_UNAVAILABLE);
239
- } else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
232
+ if (!(window.cast &&
233
+ cast.__platform__ && cast.__platform__.canDisplayType)) {
240
234
  shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
241
235
  'shaka.media.Capabilities.isTypeSupported() for type support.');
242
236
  return shaka.media.Capabilities.isTypeSupported(videoConfig.contentType);
@@ -53,14 +53,19 @@ shaka.polyfill.MediaSource = class {
53
53
  // Offsetting the end of the removal range seems to help.
54
54
  // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
55
55
  shaka.polyfill.MediaSource.patchRemovalRange_();
56
- } else {
57
- shaka.log.info('Patching Safari 13 MSE bugs.');
56
+ } else if (safariVersion <= 15) {
57
+ shaka.log.info('Patching Safari 13 & 14 & 15 MSE bugs.');
58
58
  // Safari 13 does not correctly implement abort() on SourceBuffer.
59
59
  // Calling abort() before appending a segment causes that segment to be
60
60
  // incomplete in the buffer.
61
61
  // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
62
62
  shaka.polyfill.MediaSource.stubAbort_();
63
63
  }
64
+ } else if (shaka.util.Platform.isEdge()) {
65
+ shaka.log.info('Rejecting TS container.');
66
+ // TS content is broken on Edge in general.
67
+ // See https://github.com/shaka-project/shaka-player/issues/4955
68
+ shaka.polyfill.MediaSource.rejectContainer_('mp2t');
64
69
  } else if (shaka.util.Platform.isTizen2() ||
65
70
  shaka.util.Platform.isTizen3() ||
66
71
  shaka.util.Platform.isTizen4()) {
@@ -122,6 +127,23 @@ shaka.polyfill.MediaSource = class {
122
127
  };
123
128
  }
124
129
 
130
+ /**
131
+ * Patch |MediaSource.isTypeSupported| to always reject |container|. This is
132
+ * used when we know that we are on a platform that does not work well with
133
+ * a given container.
134
+ *
135
+ * @param {string} container
136
+ * @private
137
+ */
138
+ static rejectContainer_(container) {
139
+ const isTypeSupported = MediaSource.isTypeSupported;
140
+
141
+ MediaSource.isTypeSupported = (mimeType) => {
142
+ const actualContainer = shaka.util.MimeUtils.getContainerType(mimeType);
143
+ return actualContainer != container && isTypeSupported(mimeType);
144
+ };
145
+ }
146
+
125
147
  /**
126
148
  * Patch |MediaSource.isTypeSupported| to always reject |codec|. This is used
127
149
  * when we know that we are on a platform that does not work well with a given
@@ -474,6 +474,7 @@ shaka.text.UITextDisplayer = class {
474
474
  wrapper = shaka.util.Dom.createHTMLElement('span');
475
475
  wrapper.classList.add('shaka-text-wrapper');
476
476
  wrapper.style.backgroundColor = cue.backgroundColor;
477
+ wrapper.style.lineHeight = 'normal';
477
478
  cueElement.appendChild(wrapper);
478
479
  }
479
480
 
@@ -391,8 +391,7 @@ shaka.text.VttTextParser = class {
391
391
  end += timeOffset;
392
392
 
393
393
  // Get the payload.
394
- const payload = VttTextParser.htmlUnescape_(
395
- text.slice(1).join('\n').trim());
394
+ const payload = text.slice(1).join('\n').trim();
396
395
 
397
396
  let cue = null;
398
397
  if (styles.has('global')) {
@@ -450,7 +449,7 @@ shaka.text.VttTextParser = class {
450
449
  const childNode = childNodes[0];
451
450
  if (childNode.nodeType == Node.TEXT_NODE ||
452
451
  childNode.nodeType == Node.CDATA_SECTION_NODE) {
453
- rootCue.payload = payload;
452
+ rootCue.payload = VttTextParser.htmlUnescape_(payload);
454
453
  return;
455
454
  }
456
455
  }
@@ -461,7 +460,7 @@ shaka.text.VttTextParser = class {
461
460
  rootCue.nestedCues = cues;
462
461
  } else {
463
462
  shaka.log.warning('The cue\'s markup could not be parsed: ', payload);
464
- rootCue.payload = payload;
463
+ rootCue.payload = VttTextParser.htmlUnescape_(payload);
465
464
  }
466
465
  }
467
466
 
@@ -755,7 +754,7 @@ shaka.text.VttTextParser = class {
755
754
  }
756
755
  if (text.length > 0) {
757
756
  const textCue = nestedCue.clone();
758
- textCue.payload = text;
757
+ textCue.payload = VttTextParser.htmlUnescape_(text);
759
758
  cues.push(textCue);
760
759
  }
761
760
  isFirst = false;
@@ -49,7 +49,7 @@ shaka.util.MimeUtils = class {
49
49
 
50
50
  if (Transmuxer.isSupported(fullMimeType, contentType)) {
51
51
  return shaka.media.Transmuxer.convertCodecs(contentType, fullMimeType);
52
- } else if (contentType == ContentType.AUDIO) {
52
+ } else if (mimeType != 'video/mp2t' && contentType == ContentType.AUDIO) {
53
53
  // video/mp2t is the correct mime type for TS audio, so only replace the
54
54
  // word "video" with "audio" for non-TS audio content.
55
55
  return fullMimeType.replace('video', 'audio');
@@ -712,26 +712,38 @@ shaka.util.StreamUtils = class {
712
712
  }
713
713
 
714
714
  if (audio) {
715
- if (!keySystemConfig.audio) {
715
+ // See: https://github.com/shaka-project/shaka-player/issues/4659
716
+ if (info.audioRobustness != '') {
717
+ if (!keySystemConfig.audio) {
718
+ // KeySystemTrackConfiguration
719
+ keySystemConfig.audio = {
720
+ robustness: info.audioRobustness,
721
+ };
722
+ } else {
723
+ keySystemConfig.audio.robustness =
724
+ keySystemConfig.audio.robustness || info.audioRobustness;
725
+ }
726
+ } else if (!keySystemConfig.audio) {
716
727
  // KeySystemTrackConfiguration
717
- keySystemConfig.audio = {
718
- robustness: info.audioRobustness,
719
- };
720
- } else {
721
- keySystemConfig.audio.robustness =
722
- keySystemConfig.audio.robustness || info.audioRobustness;
728
+ keySystemConfig.audio = {};
723
729
  }
724
730
  }
725
731
 
726
732
  if (video) {
727
- if (!keySystemConfig.video) {
733
+ // See: https://github.com/shaka-project/shaka-player/issues/4659
734
+ if (info.videoRobustness != '') {
735
+ if (!keySystemConfig.video) {
736
+ // KeySystemTrackConfiguration
737
+ keySystemConfig.video = {
738
+ robustness: info.videoRobustness,
739
+ };
740
+ } else {
741
+ keySystemConfig.video.robustness =
742
+ keySystemConfig.video.robustness || info.videoRobustness;
743
+ }
744
+ } else if (!keySystemConfig.video) {
728
745
  // KeySystemTrackConfiguration
729
- keySystemConfig.video = {
730
- robustness: info.videoRobustness,
731
- };
732
- } else {
733
- keySystemConfig.video.robustness =
734
- keySystemConfig.video.robustness || info.videoRobustness;
746
+ keySystemConfig.video = {};
735
747
  }
736
748
  }
737
749
  }
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.3.4",
4
+ "version": "4.3.5",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  describe('MediaCapabilities', () => {
8
- const Util = shaka.test.Util;
9
8
  const originalCast = window['cast'];
10
9
  const originalVendor = navigator.vendor;
11
10
  const originalUserAgent = navigator.userAgent;
@@ -163,30 +162,30 @@ describe('MediaCapabilities', () => {
163
162
  expect(result.keySystemAccess).toEqual(mockResult);
164
163
  });
165
164
 
166
- it('throws when the cast namespace is not available', async () => {
167
- // Temporarily remove window.cast to trigger error. It's restored after
168
- // every test.
169
- delete window['cast'];
165
+ it('falls back to isTypeSupported() when cast namespace is not available',
166
+ async () => {
167
+ // Temporarily remove window.cast to trigger error. It's restored
168
+ // after every test.
169
+ delete window['cast'];
170
170
 
171
- const isChromecastSpy =
172
- spyOn(shaka['util']['Platform'],
173
- 'isChromecast').and.returnValue(true);
174
- const expected = Util.jasmineError(new shaka.util.Error(
175
- shaka.util.Error.Severity.CRITICAL,
176
- shaka.util.Error.Category.CAST,
177
- shaka.util.Error.Code.CAST_API_UNAVAILABLE));
178
- const isTypeSupportedSpy =
179
- spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
171
+ const isChromecastSpy =
172
+ spyOn(shaka['util']['Platform'],
173
+ 'isChromecast').and.returnValue(true);
174
+ expect(window['MediaSource']['isTypeSupported']).toBeDefined();
180
175
 
181
- shaka.polyfill.MediaCapabilities.install();
182
- await expectAsync(
183
- navigator.mediaCapabilities.decodingInfo(mockDecodingConfig))
184
- .toBeRejectedWith(expected);
176
+ shaka.polyfill.MediaCapabilities.install();
177
+ await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
185
178
 
186
- expect(isTypeSupportedSpy).not.toHaveBeenCalled();
187
- // 1 (during install()) + 1 (for video config check).
188
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
189
- });
179
+ expect(mockCanDisplayType).not.toHaveBeenCalled();
180
+ // 1 (during install()) + 1 (for video config check).
181
+ expect(isChromecastSpy).toHaveBeenCalledTimes(2);
182
+ // 1 (fallback in canCastDisplayType()) +
183
+ // 1 (mockDecodingConfig.audio).
184
+ expect(supportMap.has(mockDecodingConfig.video.contentType))
185
+ .toBe(true);
186
+ expect(supportMap.has(mockDecodingConfig.audio.contentType))
187
+ .toBe(true);
188
+ });
190
189
 
191
190
  it('falls back to isTypeSupported() when canDisplayType() missing',
192
191
  async () => {
@@ -852,11 +852,35 @@ describe('VttTextParser', () => {
852
852
  it('support escaped html payload', () => {
853
853
  verifyHelper(
854
854
  [
855
- {startTime: 20.1, endTime: 40.505, payload: '"Test & 1"\u{a0}'},
855
+ {
856
+ startTime: 20.1,
857
+ endTime: 40.505,
858
+ payload: '"Test & 1"\u{a0}',
859
+ },
860
+ {
861
+ startTime: 41,
862
+ endTime: 42,
863
+ payload: '',
864
+ nestedCues: [
865
+ {
866
+ startTime: 41,
867
+ endTime: 42,
868
+ payload: 'Test',
869
+ fontStyle: Cue.fontStyle.ITALIC,
870
+ },
871
+ {
872
+ startTime: 41,
873
+ endTime: 42,
874
+ payload: '&',
875
+ },
876
+ ],
877
+ },
856
878
  ],
857
879
  'WEBVTT\n\n' +
858
880
  '00:00:20.100 --> 00:00:40.505\n' +
859
- '&quot;Test &amp; 1&quot;&nbsp;',
881
+ '&quot;Test &amp; 1&quot;&nbsp;\n\n' +
882
+ '00:00:41.000 --> 00:00:42.000\n' +
883
+ '<i>Test</i>&amp;',
860
884
  {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
861
885
  });
862
886