shaka-player 4.2.7 → 4.2.9

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 (67) 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 +72 -11
  4. package/.github/workflows/update-screenshots.yaml +142 -0
  5. package/CHANGELOG.md +39 -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 +29 -27
  14. package/dist/demo.compiled.debug.map +3 -3
  15. package/dist/demo.compiled.js +29 -27
  16. package/dist/demo.compiled.map +3 -3
  17. package/dist/deps.js +4 -3
  18. package/dist/locales.js +31 -31
  19. package/dist/receiver.compiled.debug.js +21 -21
  20. package/dist/receiver.compiled.debug.map +2 -2
  21. package/dist/receiver.compiled.js +21 -21
  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 +114 -106
  26. package/dist/shaka-player.compiled.debug.map +4 -4
  27. package/dist/shaka-player.compiled.js +587 -584
  28. package/dist/shaka-player.compiled.map +4 -4
  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 +126 -118
  32. package/dist/shaka-player.ui.debug.map +4 -4
  33. package/dist/shaka-player.ui.js +709 -705
  34. package/dist/shaka-player.ui.map +4 -4
  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/dash/segment_template.js +17 -5
  42. package/lib/hls/hls_parser.js +3 -1
  43. package/lib/media/drm_engine.js +4 -2
  44. package/lib/media/transmuxer.js +13 -7
  45. package/lib/media/video_wrapper.js +3 -3
  46. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  47. package/lib/player.js +3 -1
  48. package/lib/polyfill/media_capabilities.js +6 -9
  49. package/lib/polyfill/mediasource.js +24 -2
  50. package/lib/text/simple_text_displayer.js +2 -64
  51. package/lib/text/text_utils.js +88 -0
  52. package/lib/text/ui_text_displayer.js +1 -5
  53. package/lib/text/vtt_text_parser.js +257 -97
  54. package/lib/text/web_vtt_generator.js +2 -64
  55. package/lib/util/mime_utils.js +1 -1
  56. package/lib/util/platform.js +8 -0
  57. package/lib/util/stream_utils.js +26 -14
  58. package/package.json +1 -1
  59. package/support.html +1 -0
  60. package/test/dash/dash_parser_manifest_unit.js +0 -36
  61. package/test/polyfill/media_capabilities_unit.js +20 -22
  62. package/test/test/util/layout_tests.js +433 -0
  63. package/test/test/util/util.js +0 -120
  64. package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
  65. package/test/text/ui_text_displayer_unit.js +2 -4
  66. package/test/text/vtt_text_parser_unit.js +147 -6
  67. package/test/text/web_vtt_layout_integration.js +525 -0
@@ -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
+ ```
@@ -46,12 +46,12 @@ you.
46
46
  const req = await fetch('https://example.com/cert.der');
47
47
  const cert = await req.arrayBuffer();
48
48
 
49
- player.configure('drm.advanced.com\\.apple\\.fps\\.serverCertificate',
49
+ player.configure('drm.advanced.com\\.apple\\.fps.serverCertificate',
50
50
  new Uint8Array(cert));
51
51
  ```
52
52
 
53
53
  ```js
54
- player.configure('drm.advanced.com\\.apple\\.fps\\.serverCertificateUri',
54
+ player.configure('drm.advanced.com\\.apple\\.fps.serverCertificateUri',
55
55
  'https://example.com/cert.der');
56
56
  ```
57
57
 
@@ -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
@@ -501,6 +501,15 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
501
501
  google.ima.dai.api.StreamRequest.prototype.format;
502
502
 
503
503
 
504
+ /**
505
+ * @enum {string}
506
+ */
507
+ google.ima.dai.api.StreamRequest.StreamFormat = {
508
+ DASH: 'dash',
509
+ HLS: 'hls',
510
+ };
511
+
512
+
504
513
  /** @const */
505
514
  google.ima.dai.api.VODStreamRequest =
506
515
  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
  /**
@@ -427,6 +427,7 @@ shaka.ads.ClientSideAdManager = class {
427
427
  if (this.ad_.isLinear()) {
428
428
  this.adContainer_.setAttribute('ad-active', 'true');
429
429
  this.video_.pause();
430
+ this.ad_.setVolume(this.video_.muted ? 0 : this.video_.volume);
430
431
  }
431
432
  }
432
433
 
@@ -439,7 +440,9 @@ shaka.ads.ClientSideAdManager = class {
439
440
  (new Map()).set('originalEvent', e)));
440
441
  if (this.ad_ && this.ad_.isLinear()) {
441
442
  this.adContainer_.removeAttribute('ad-active');
442
- this.video_.play();
443
+ if (!this.video_.ended) {
444
+ this.video_.play();
445
+ }
443
446
  }
444
447
  }
445
448
  };
@@ -102,11 +102,23 @@ shaka.dash.SegmentTemplate = class {
102
102
  const periodEnd = context.periodInfo.duration ?
103
103
  context.periodInfo.start + context.periodInfo.duration : Infinity;
104
104
 
105
- // Don't fit live content, since it might receive more segments.
106
- // Unless that live content is multi-period; it's safe to fit every period
107
- // but the last one, since only the last period might receive new
108
- // segments.
109
- const shouldFit = periodEnd != Infinity;
105
+ /* When to fit segments. All refactors should honor/update this table:
106
+ *
107
+ * | dynamic | infinite | last | should | notes |
108
+ * | | period | period | fit | |
109
+ * | ------- | -------- | ------ | ------ | ------------------------- |
110
+ * | F | F | X | T | typical VOD |
111
+ * | F | T | X | X | impossible: infinite VOD |
112
+ * | T | F | F | T | typical live, old period |
113
+ * | T | F | T | F | typical IPR |
114
+ * | T | T | F | X | impossible: old, infinite |
115
+ * | T | T | T | F | typical live, new period |
116
+ */
117
+
118
+ // We never fit the final period of dynamic content, which could be
119
+ // infinite live (with no limit to fit to) or IPR (which would expand the
120
+ // most recent segment to the end of the presentation).
121
+ const shouldFit = !(context.dynamic && context.periodInfo.isLastPeriod);
110
122
 
111
123
  if (segmentIndex) {
112
124
  if (shouldFit) {
@@ -2903,6 +2903,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2903
2903
  'cmfa': 'audio/mp4',
2904
2904
  // MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
2905
2905
  'ts': 'video/mp2t',
2906
+ 'tsa': 'video/mp2t',
2906
2907
  };
2907
2908
 
2908
2909
 
@@ -2919,6 +2920,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
2919
2920
  'm4f': 'video/mp4',
2920
2921
  'cmfv': 'video/mp4',
2921
2922
  'ts': 'video/mp2t',
2923
+ 'tsv': 'video/mp2t',
2922
2924
  };
2923
2925
 
2924
2926
 
@@ -2933,7 +2935,7 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
2933
2935
  'm4f': 'application/mp4',
2934
2936
  'cmft': 'application/mp4',
2935
2937
  'vtt': 'text/vtt',
2936
- 'webvtt': 'txt/vtt',
2938
+ 'webvtt': 'text/vtt',
2937
2939
  'ttml': 'application/ttml+xml',
2938
2940
  };
2939
2941
 
@@ -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:
@@ -29,9 +29,7 @@ shaka.media.Transmuxer = class {
29
29
  this.muxjs_ = shaka.dependencies.muxjs();
30
30
 
31
31
  /** @private {muxjs.mp4.Transmuxer} */
32
- this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
33
- 'keepOriginalTimestamps': true,
34
- });
32
+ this.muxTransmuxer_ = null;
35
33
 
36
34
  /** @private {shaka.util.PublicPromise} */
37
35
  this.transmuxPromise_ = null;
@@ -48,16 +46,22 @@ shaka.media.Transmuxer = class {
48
46
  /** @private {boolean} */
49
47
  this.isTransmuxing_ = false;
50
48
 
51
- this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
52
-
53
- this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
49
+ if (this.muxjs_) {
50
+ this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
51
+ 'keepOriginalTimestamps': true,
52
+ });
53
+ this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
54
+ this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
55
+ }
54
56
  }
55
57
 
56
58
  /**
57
59
  * @override
58
60
  */
59
61
  destroy() {
60
- this.muxTransmuxer_.dispose();
62
+ if (this.muxTransmuxer_) {
63
+ this.muxTransmuxer_.dispose();
64
+ }
61
65
  this.muxTransmuxer_ = null;
62
66
  return Promise.resolve();
63
67
  }
@@ -156,6 +160,8 @@ shaka.media.Transmuxer = class {
156
160
  * metadata: !Array.<!Object>}>}
157
161
  */
158
162
  transmux(data) {
163
+ goog.asserts.assert(this.muxTransmuxer_,
164
+ 'mux.js should be available.');
159
165
  goog.asserts.assert(!this.isTransmuxing_,
160
166
  'No transmuxing should be in progress.');
161
167
  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
@@ -2433,6 +2433,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2433
2433
  // goes wrong.
2434
2434
  new shaka.util.Timer(resolve).tickAfter(1);
2435
2435
  });
2436
+ } else if (textTracks.length > 0) {
2437
+ this.isTextVisible_ = true;
2436
2438
  }
2437
2439
 
2438
2440
  // If we have moved on to another piece of content while waiting for
@@ -6605,7 +6607,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6605
6607
  * @define {string} A version number taken from git at compile time.
6606
6608
  * @export
6607
6609
  */
6608
- shaka.Player.version = 'v4.2.7-uncompiled';
6610
+ shaka.Player.version = 'v4.2.9-uncompiled';
6609
6611
 
6610
6612
  // Initialize the deprecation system using the version string we just set
6611
6613
  // on the player.
@@ -8,7 +8,6 @@ goog.provide('shaka.polyfill.MediaCapabilities');
8
8
 
9
9
  goog.require('shaka.log');
10
10
  goog.require('shaka.polyfill');
11
- goog.require('shaka.util.Error');
12
11
  goog.require('shaka.util.Platform');
13
12
 
14
13
 
@@ -47,6 +46,8 @@ shaka.polyfill.MediaCapabilities = class {
47
46
  // should always install polyfill for WebOS browsers.
48
47
  // Since MediaCapabilities implementation is buggy in EOS browsers, we
49
48
  // should always install polyfill for EOS browsers.
49
+ // Since MediaCapabilities implementation is buggy in Hisense browsers, we
50
+ // should always install polyfill for Hisense browsers.
50
51
  let canUseNativeMCap = true;
51
52
  if (shaka.util.Platform.isApple() ||
52
53
  shaka.util.Platform.isPS5() ||
@@ -54,7 +55,8 @@ shaka.polyfill.MediaCapabilities = class {
54
55
  shaka.util.Platform.isWebOS() ||
55
56
  shaka.util.Platform.isTizen() ||
56
57
  shaka.util.Platform.isChromecast() ||
57
- shaka.util.Platform.isEOS()) {
58
+ shaka.util.Platform.isEOS() ||
59
+ shaka.util.Platform.isHisense()) {
58
60
  canUseNativeMCap = false;
59
61
  }
60
62
  if (canUseNativeMCap && navigator.mediaCapabilities) {
@@ -223,13 +225,8 @@ shaka.polyfill.MediaCapabilities = class {
223
225
  * @private
224
226
  */
225
227
  static canCastDisplayType_(videoConfig) {
226
- if (!(window.cast)) {
227
- shaka.log.error('Expected cast namespace to be available!');
228
- throw new shaka.util.Error(
229
- shaka.util.Error.Severity.CRITICAL,
230
- shaka.util.Error.Category.CAST,
231
- shaka.util.Error.Code.CAST_API_UNAVAILABLE);
232
- } else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
228
+ if (!(window.cast &&
229
+ cast.__platform__ && cast.__platform__.canDisplayType)) {
233
230
  shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
234
231
  'MediaSource.isTypeSupported() for type support.');
235
232
  return MediaSource.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
@@ -14,6 +14,7 @@ goog.provide('shaka.text.SimpleTextDisplayer');
14
14
  goog.require('goog.asserts');
15
15
  goog.require('shaka.log');
16
16
  goog.require('shaka.text.Cue');
17
+ goog.require('shaka.text.Utils');
17
18
 
18
19
 
19
20
  /**
@@ -79,70 +80,7 @@ shaka.text.SimpleTextDisplayer = class {
79
80
  * @export
80
81
  */
81
82
  append(cues) {
82
- // Flatten nested cue payloads recursively. If a cue has nested cues,
83
- // their contents should be combined and replace the payload of the parent.
84
- const flattenPayload = (cue) => {
85
- // Handle styles (currently bold/italics/underline).
86
- // TODO add support for color rendering.
87
- const openStyleTags = [];
88
- const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
89
- const italics = cue.fontStyle == shaka.text.Cue.fontStyle.ITALIC;
90
- const underline = cue.textDecoration.includes(
91
- shaka.text.Cue.textDecoration.UNDERLINE);
92
- if (bold) {
93
- openStyleTags.push('b');
94
- }
95
- if (italics) {
96
- openStyleTags.push('i');
97
- }
98
- if (underline) {
99
- openStyleTags.push('u');
100
- }
101
-
102
- // Prefix opens tags, suffix closes tags in reverse order of opening.
103
- const prefixStyleTags = openStyleTags.reduce((acc, tag) => {
104
- return `${acc}<${tag}>`;
105
- }, '');
106
- const suffixStyleTags = openStyleTags.reduceRight((acc, tag) => {
107
- return `${acc}</${tag}>`;
108
- }, '');
109
-
110
- if (cue.lineBreak) {
111
- // This is a vertical lineBreak, so insert a newline.
112
- return '\n';
113
- } else if (cue.nestedCues.length) {
114
- return cue.nestedCues.map(flattenPayload).join('');
115
- } else {
116
- // This is a real cue.
117
- return prefixStyleTags + cue.payload + suffixStyleTags;
118
- }
119
- };
120
-
121
- // We don't want to modify the array or objects passed in, since we don't
122
- // technically own them. So we build a new array and replace certain items
123
- // in it if they need to be flattened.
124
- // We also don't want to flatten the text payloads starting at a container
125
- // element; otherwise, for containers encapsulating multiple caption lines,
126
- // the lines would merge into a single cue. This is undesirable when a
127
- // subset of the captions are outside of the append time window. To fix
128
- // this, we only call flattenPayload() starting at elements marked as
129
- // isContainer = false.
130
- const getCuesToFlatten = (cues, result) => {
131
- for (const cue of cues) {
132
- if (cue.isContainer) {
133
- // Recurse to find the actual text payload cues.
134
- getCuesToFlatten(cue.nestedCues, result);
135
- } else {
136
- // Flatten the payload.
137
- const flatCue = cue.clone();
138
- flatCue.nestedCues = [];
139
- flatCue.payload = flattenPayload(cue);
140
- result.push(flatCue);
141
- }
142
- }
143
- return result;
144
- };
145
- const flattenedCues = getCuesToFlatten(cues, []);
83
+ const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues, []);
146
84
 
147
85
  // Convert cues.
148
86
  const textTrackCues = [];
@@ -0,0 +1,88 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.text.Utils');
8
+
9
+ goog.require('shaka.text.Cue');
10
+
11
+
12
+ shaka.text.Utils = class {
13
+ /**
14
+ * Flatten nested cue payloads recursively. If a cue has nested cues,
15
+ * their contents should be combined and replace the payload of the parent.
16
+ *
17
+ * @param {!shaka.text.Cue} cue
18
+ * @return {string}
19
+ * @private
20
+ */
21
+ static flattenPayload_(cue) {
22
+ // Handle styles (currently bold/italics/underline).
23
+ // TODO: add support for color rendering.
24
+ const openStyleTags = [];
25
+ const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
26
+ const italics = cue.fontStyle == shaka.text.Cue.fontStyle.ITALIC;
27
+ const underline = cue.textDecoration.includes(
28
+ shaka.text.Cue.textDecoration.UNDERLINE);
29
+ if (bold) {
30
+ openStyleTags.push('b');
31
+ }
32
+ if (italics) {
33
+ openStyleTags.push('i');
34
+ }
35
+ if (underline) {
36
+ openStyleTags.push('u');
37
+ }
38
+
39
+ // Prefix opens tags, suffix closes tags in reverse order of opening.
40
+ const prefixStyleTags = openStyleTags.reduce((acc, tag) => {
41
+ return `${acc}<${tag}>`;
42
+ }, '');
43
+ const suffixStyleTags = openStyleTags.reduceRight((acc, tag) => {
44
+ return `${acc}</${tag}>`;
45
+ }, '');
46
+
47
+ if (cue.lineBreak) {
48
+ // This is a vertical lineBreak, so insert a newline.
49
+ return '\n';
50
+ } else if (cue.nestedCues.length) {
51
+ return cue.nestedCues.map(shaka.text.Utils.flattenPayload_).join('');
52
+ } else {
53
+ // This is a real cue.
54
+ return prefixStyleTags + cue.payload + suffixStyleTags;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * We don't want to modify the array or objects passed in, since we don't
60
+ * technically own them. So we build a new array and replace certain items
61
+ * in it if they need to be flattened.
62
+ * We also don't want to flatten the text payloads starting at a container
63
+ * element; otherwise, for containers encapsulating multiple caption lines,
64
+ * the lines would merge into a single cue. This is undesirable when a
65
+ * subset of the captions are outside of the append time window. To fix
66
+ * this, we only call flattenPayload() starting at elements marked as
67
+ * isContainer = false.
68
+ *
69
+ * @param {!Array.<!shaka.text.Cue>} cues
70
+ * @param {!Array.<!shaka.text.Cue>} result
71
+ * @return {!Array.<!shaka.text.Cue>}
72
+ */
73
+ static getCuesToFlatten(cues, result) {
74
+ for (const cue of cues) {
75
+ if (cue.isContainer) {
76
+ // Recurse to find the actual text payload cues.
77
+ shaka.text.Utils.getCuesToFlatten(cue.nestedCues, result);
78
+ } else {
79
+ // Flatten the payload.
80
+ const flatCue = cue.clone();
81
+ flatCue.nestedCues = [];
82
+ flatCue.payload = shaka.text.Utils.flattenPayload_(cue);
83
+ result.push(flatCue);
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+ };
@@ -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
 
@@ -617,12 +618,7 @@ shaka.text.UITextDisplayer = class {
617
618
  // TODO: Implement lineAlignment of 'CENTER'.
618
619
  if (cue.line != null) {
619
620
  if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
620
- // When setting absolute positioning, you need to set x/y/width/height
621
- // so the element is positioned correctly. Set these as default and
622
- // other settings will override them.
623
621
  style.position = 'absolute';
624
- style.left = '0';
625
- style.top = '0';
626
622
  if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
627
623
  style.width = '100%';
628
624
  if (cue.lineAlign == Cue.lineAlign.START) {