shaka-player 4.6.2 → 4.6.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/demo/common/asset.js +0 -1
  4. package/demo/common/assets.js +0 -1
  5. package/demo/custom.js +2 -0
  6. package/dist/demo.compiled.debug.js +3 -3
  7. package/dist/demo.compiled.debug.map +1 -1
  8. package/dist/demo.compiled.js +3 -3
  9. package/dist/demo.compiled.map +1 -1
  10. package/dist/deps.js +4 -4
  11. package/dist/locales.js +32 -32
  12. package/dist/receiver.compiled.debug.map +1 -1
  13. package/dist/receiver.compiled.map +1 -1
  14. package/dist/shaka-player.compiled.d.ts +1 -1
  15. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  16. package/dist/shaka-player.compiled.debug.externs.js +5 -2
  17. package/dist/shaka-player.compiled.debug.js +286 -282
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.externs.js +5 -2
  20. package/dist/shaka-player.compiled.js +423 -419
  21. package/dist/shaka-player.compiled.map +3 -3
  22. package/dist/shaka-player.ui.d.ts +1 -1
  23. package/dist/shaka-player.ui.debug.d.ts +1 -1
  24. package/dist/shaka-player.ui.debug.externs.js +5 -2
  25. package/dist/shaka-player.ui.debug.js +237 -234
  26. package/dist/shaka-player.ui.debug.map +3 -3
  27. package/dist/shaka-player.ui.externs.js +5 -2
  28. package/dist/shaka-player.ui.js +517 -512
  29. package/dist/shaka-player.ui.map +3 -3
  30. package/docs/tutorials/ui.md +3 -1
  31. package/externs/lcevc.js +6 -0
  32. package/externs/shaka/codecs.js +6 -0
  33. package/externs/shaka/transmuxer.js +6 -0
  34. package/lib/ads/client_side_ad_manager.js +4 -0
  35. package/lib/ads/media_tailor_ad_manager.js +5 -0
  36. package/lib/ads/server_side_ad_manager.js +4 -0
  37. package/lib/dash/dash_parser.js +2 -2
  38. package/lib/hls/hls_parser.js +19 -6
  39. package/lib/media/drm_engine.js +0 -3
  40. package/lib/media/media_source_engine.js +11 -5
  41. package/lib/media/streaming_engine.js +3 -2
  42. package/lib/media/video_wrapper.js +4 -3
  43. package/lib/player.js +4 -6
  44. package/lib/text/simple_text_displayer.js +8 -7
  45. package/lib/text/text_utils.js +39 -13
  46. package/lib/text/ui_text_displayer.js +70 -15
  47. package/lib/text/web_vtt_generator.js +1 -1
  48. package/lib/util/mp4_box_parsers.js +39 -5
  49. package/package.json +1 -1
  50. package/test/media/drm_engine_unit.js +27 -0
  51. package/test/test/assets/audio-xhe-aac.mp4 +0 -0
  52. package/test/test/util/layout_tests.js +2 -1
  53. package/test/text/simple_text_displayer_unit.js +1 -1
  54. package/test/text/web_vtt_generator_unit.js +3 -3
  55. package/test/transmuxer/transmuxer_integration.js +3 -3
  56. package/test/util/mp4_box_parsers_unit.js +36 -0
@@ -204,11 +204,13 @@ constructor.
204
204
  // "local" because it is for local playback only, as opposed to the player proxy
205
205
  // object, which will route your calls to the ChromeCast receiver as necessary.
206
206
  const localPlayer = new shaka.Player();
207
- await localPlayer.attach(videoElement);
208
207
  // "Overlay" because the UI will add DOM elements inside the container,
209
208
  // to visually overlay the video element
210
209
  const ui = new shaka.ui.Overlay(localPlayer, videoContainerElement,
211
210
  videoElement);
211
+ // Now that the player has been configured to be part of a UI, attach it to the
212
+ // video.
213
+ await localPlayer.attach(videoElement);
212
214
 
213
215
  // As with DOM-based setup, get access to the UI controls and player from the
214
216
  // UI.
package/externs/lcevc.js CHANGED
@@ -1,3 +1,9 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2023 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  /**
2
8
  * @fileoverview Externs for LcevcDec
3
9
  * compiler.
@@ -1,3 +1,9 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2023 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  /**
2
8
  * @typedef {{
3
9
  * data: !Uint8Array,
@@ -1,3 +1,9 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2023 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  /**
2
8
  * @fileoverview Externs for Transmuxer.
3
9
  *
@@ -13,6 +13,10 @@ goog.provide('shaka.ads.ClientSideAdManager');
13
13
 
14
14
  goog.require('goog.asserts');
15
15
  goog.require('shaka.ads.ClientSideAd');
16
+ goog.require('shaka.log');
17
+ goog.require('shaka.util.Dom');
18
+ goog.require('shaka.util.EventManager');
19
+ goog.require('shaka.util.FakeEvent');
16
20
  goog.require('shaka.util.IReleasable');
17
21
 
18
22
  /**
@@ -15,7 +15,12 @@ goog.require('goog.asserts');
15
15
  goog.require('shaka.ads.MediaTailorAd');
16
16
  goog.require('shaka.log');
17
17
  goog.require('shaka.net.NetworkingEngine');
18
+ goog.require('goog.Uri');
19
+ goog.require('shaka.util.EventManager');
20
+ goog.require('shaka.util.Error');
21
+ goog.require('shaka.util.FakeEvent');
18
22
  goog.require('shaka.util.IReleasable');
23
+ goog.require('shaka.util.PublicPromise');
19
24
  goog.require('shaka.util.StringUtils');
20
25
 
21
26
 
@@ -14,7 +14,11 @@ goog.provide('shaka.ads.ServerSideAdManager');
14
14
  goog.require('goog.asserts');
15
15
  goog.require('shaka.ads.ServerSideAd');
16
16
  goog.require('shaka.log');
17
+ goog.require('shaka.util.EventManager');
18
+ goog.require('shaka.util.Error');
19
+ goog.require('shaka.util.FakeEvent');
17
20
  goog.require('shaka.util.IReleasable');
21
+ goog.require('shaka.util.PublicPromise');
18
22
 
19
23
 
20
24
  /**
@@ -1433,11 +1433,11 @@ shaka.dash.DashParser = class {
1433
1433
  'Must have Segment* with non-text streams.');
1434
1434
 
1435
1435
  const duration = context.periodInfo.duration || 0;
1436
+ const getBaseUris = context.representation.getBaseUris;
1436
1437
  streamInfo = {
1437
1438
  generateSegmentIndex: () => {
1438
- const baseUris = context.representation.getBaseUris();
1439
1439
  return Promise.resolve(shaka.media.SegmentIndex.forSingleSegment(
1440
- periodStart, duration, baseUris));
1440
+ periodStart, duration, getBaseUris()));
1441
1441
  },
1442
1442
  };
1443
1443
  }
@@ -934,7 +934,8 @@ shaka.hls.HlsParser = class {
934
934
  return basicInfo;
935
935
  }
936
936
  } else if (extension == 'mp4' || extension == 'cmfv' ||
937
- contentMimeType == 'video/mp4' || contentMimeType == 'audio/mp4' ||
937
+ extension == 'm4s' || contentMimeType == 'video/mp4' ||
938
+ contentMimeType == 'audio/mp4' ||
938
939
  contentMimeType == 'video/iso.segment') {
939
940
  const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromMp4(
940
941
  initData, response.data);
@@ -3752,14 +3753,26 @@ shaka.hls.HlsParser = class {
3752
3753
 
3753
3754
  // If unable to guess mime type, request a segment and try getting it
3754
3755
  // from the response.
3756
+ let contentMimeType;
3757
+ const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
3755
3758
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
3756
3759
  middleSegmentUris, this.config_.retryParameters);
3757
- headRequest.method = 'HEAD';
3758
- const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
3759
- const response = await this.makeNetworkRequest_(
3760
- headRequest, requestType, {type});
3760
+ try {
3761
+ headRequest.method = 'HEAD';
3762
+ const response = await this.makeNetworkRequest_(
3763
+ headRequest, requestType, {type});
3764
+
3765
+ contentMimeType = response.headers['content-type'];
3766
+ } catch (error) {
3767
+ if (error && error.code == shaka.util.Error.Code.HTTP_ERROR) {
3768
+ headRequest.method = 'GET';
3769
+ const response = await this.makeNetworkRequest_(
3770
+ headRequest, requestType, {type});
3771
+
3772
+ contentMimeType = response.headers['content-type'];
3773
+ }
3774
+ }
3761
3775
 
3762
- const contentMimeType = response.headers['content-type'];
3763
3776
  if (contentMimeType) {
3764
3777
  // Split the MIME type in case the server sent additional parameters.
3765
3778
  return contentMimeType.split(';')[0];
@@ -2038,9 +2038,6 @@ shaka.media.DrmEngine = class {
2038
2038
 
2039
2039
  for (const drm1 of drms1) {
2040
2040
  for (const drm2 of drms2) {
2041
- // This method is only called to compare drmInfos of a video and an
2042
- // audio adaptations, so we shouldn't have to worry about checking
2043
- // robustness.
2044
2041
  if (drm1.keySystem == drm2.keySystem) {
2045
2042
  const initDataMap = new Map();
2046
2043
  const bothInitDatas = (drm1.initData || [])
@@ -1661,6 +1661,7 @@ shaka.media.MediaSourceEngine = class {
1661
1661
  if (shaka.media.Capabilities.isChangeTypeSupported()) {
1662
1662
  if (this.transmuxers_[contentType]) {
1663
1663
  this.transmuxers_[contentType].destroy();
1664
+ delete this.transmuxers_[contentType];
1664
1665
  }
1665
1666
  if (transmuxer) {
1666
1667
  this.transmuxers_[contentType] = transmuxer;
@@ -1859,8 +1860,8 @@ shaka.media.MediaSourceEngine = class {
1859
1860
  }
1860
1861
  }
1861
1862
 
1862
- const newCodec = MimeUtils.getCodecBase(
1863
- MimeUtils.getCodecs(newMimeType));
1863
+ const newAllCodecs = MimeUtils.getCodecs(newMimeType);
1864
+ const newCodec = MimeUtils.getCodecBase(newAllCodecs);
1864
1865
  const newBasicType = MimeUtils.getBasicType(newMimeType);
1865
1866
 
1866
1867
  // Current/new codecs base and basic type match then no need to switch
@@ -1868,10 +1869,15 @@ shaka.media.MediaSourceEngine = class {
1868
1869
  return false;
1869
1870
  }
1870
1871
 
1871
- if (this.config_.codecSwitchingStrategy ===
1872
+ let allowChangeType = true;
1873
+ if (this.needSplitMuxedContent_ || (newAllCodecs.split(',').length &&
1874
+ transmuxer && !this.transmuxers_[contentType])) {
1875
+ allowChangeType = false;
1876
+ }
1877
+
1878
+ if (allowChangeType && this.config_.codecSwitchingStrategy ===
1872
1879
  shaka.config.CodecSwitchingStrategy.SMOOTH &&
1873
- shaka.media.Capabilities.isChangeTypeSupported() &&
1874
- !this.needSplitMuxedContent_) {
1880
+ shaka.media.Capabilities.isChangeTypeSupported()) {
1875
1881
  await this.changeType(contentType, newMimeType, transmuxer);
1876
1882
  } else {
1877
1883
  if (transmuxer) {
@@ -6,7 +6,6 @@
6
6
 
7
7
  /**
8
8
  * @fileoverview
9
- * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
10
9
  */
11
10
 
12
11
  goog.provide('shaka.media.StreamingEngine');
@@ -14,12 +13,14 @@ goog.provide('shaka.media.StreamingEngine');
14
13
  goog.require('goog.asserts');
15
14
  goog.require('shaka.log');
16
15
  goog.require('shaka.media.InitSegmentReference');
16
+ goog.require('shaka.media.ManifestParser');
17
17
  goog.require('shaka.media.MediaSourceEngine');
18
18
  goog.require('shaka.media.SegmentIterator');
19
19
  goog.require('shaka.media.SegmentReference');
20
20
  goog.require('shaka.media.SegmentPrefetch');
21
21
  goog.require('shaka.net.Backoff');
22
22
  goog.require('shaka.net.NetworkingEngine');
23
+ goog.require('shaka.util.BufferUtils');
23
24
  goog.require('shaka.util.DelayedTick');
24
25
  goog.require('shaka.util.Destroyer');
25
26
  goog.require('shaka.util.Error');
@@ -28,6 +29,7 @@ goog.require('shaka.util.IDestroyable');
28
29
  goog.require('shaka.util.Id3Utils');
29
30
  goog.require('shaka.util.ManifestParserUtils');
30
31
  goog.require('shaka.util.MimeUtils');
32
+ goog.require('shaka.util.Mp4BoxParsers');
31
33
  goog.require('shaka.util.Mp4Parser');
32
34
  goog.require('shaka.util.Networking');
33
35
 
@@ -2203,7 +2205,6 @@ shaka.media.StreamingEngine = class {
2203
2205
  *
2204
2206
  * @return {!Promise.<BufferSource>}
2205
2207
  * @private
2206
- * @suppress {strictMissingProperties}
2207
2208
  */
2208
2209
  async fetch_(mediaState, reference, streamDataCallback) {
2209
2210
  if (reference instanceof shaka.media.InitSegmentReference) {
@@ -183,8 +183,8 @@ shaka.media.VideoWrapper = class {
183
183
 
184
184
  /**
185
185
  * A class used to move the playhead away from its current time. Sometimes,
186
- * Edge ignores re-seeks. After changing the current time, check every 100ms,
187
- * retrying if the change was not accepted.
186
+ * legacy Edge ignores re-seeks. After changing the current time, check every
187
+ * 100ms, retrying if the change was not accepted.
188
188
  *
189
189
  * Delay stats over 100 runs of a re-seeking integration test:
190
190
  * Edge - 0ms - 2%
@@ -194,7 +194,8 @@ shaka.media.VideoWrapper = class {
194
194
  * Edge - 400ms - 2%
195
195
  * Chrome - 0ms - 100%
196
196
  *
197
- * TODO: File a bug on Edge about this.
197
+ * Unfortunately, legacy Edge is not receiving updates anymore, but it still
198
+ * must be supported as it is used for web browsers on XBox.
198
199
  *
199
200
  * @implements {shaka.util.IReleasable}
200
201
  * @final
package/lib/player.js CHANGED
@@ -5144,7 +5144,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5144
5144
  } else {
5145
5145
  // eslint-disable-next-line no-restricted-syntax
5146
5146
  if (HTMLMediaElement.prototype.addTextTrack) {
5147
- return new shaka.text.SimpleTextDisplayer(this.video_);
5147
+ return new shaka.text.SimpleTextDisplayer(
5148
+ this.video_, shaka.Player.TextTrackLabel);
5148
5149
  } else {
5149
5150
  shaka.log.warning('Text tracks are not supported by the ' +
5150
5151
  'browser, disabling.');
@@ -6386,6 +6387,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6386
6387
  } // if (keyIds.size)
6387
6388
 
6388
6389
  if (tracksChanged) {
6390
+ this.onTracksChanged_();
6389
6391
  const variantsUpdated = this.updateAbrManagerVariants_();
6390
6392
  if (!variantsUpdated) {
6391
6393
  return;
@@ -6397,10 +6399,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6397
6399
  shaka.log.debug('Choosing new streams after key status changed');
6398
6400
  this.chooseVariantAndSwitch_();
6399
6401
  }
6400
-
6401
- if (tracksChanged) {
6402
- this.onTracksChanged_();
6403
- }
6404
6402
  }
6405
6403
 
6406
6404
  /**
@@ -6781,7 +6779,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6781
6779
  * @export
6782
6780
  */
6783
6781
  // eslint-disable-next-line no-useless-concat
6784
- shaka.Player.version = 'v4.6.2' + '-uncompiled'; // x-release-please-version
6782
+ shaka.Player.version = 'v4.6.4' + '-uncompiled'; // x-release-please-version
6785
6783
 
6786
6784
  // Initialize the deprecation system using the version string we just set
6787
6785
  // on the player.
@@ -6,7 +6,6 @@
6
6
 
7
7
  /**
8
8
  * @fileoverview
9
- * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
10
9
  */
11
10
 
12
11
  goog.provide('shaka.text.SimpleTextDisplayer');
@@ -24,8 +23,11 @@ goog.require('shaka.text.Utils');
24
23
  * @export
25
24
  */
26
25
  shaka.text.SimpleTextDisplayer = class {
27
- /** @param {HTMLMediaElement} video */
28
- constructor(video) {
26
+ /**
27
+ * @param {HTMLMediaElement} video
28
+ * @param {string} label
29
+ */
30
+ constructor(video, label) {
29
31
  /** @private {TextTrack} */
30
32
  this.textTrack_ = null;
31
33
 
@@ -39,7 +41,7 @@ shaka.text.SimpleTextDisplayer = class {
39
41
  // element.
40
42
  track.mode = 'disabled';
41
43
 
42
- if (track.label == shaka.Player.TextTrackLabel) {
44
+ if (track.label == label) {
43
45
  this.textTrack_ = track;
44
46
  }
45
47
  }
@@ -49,8 +51,7 @@ shaka.text.SimpleTextDisplayer = class {
49
51
  // kind to 'subtitles' or 'captions' when creating the TextTrack object.
50
52
  // The individual text tracks from the manifest will still have their own
51
53
  // kinds which can be displayed in the app's UI.
52
- this.textTrack_ = video.addTextTrack(
53
- 'subtitles', shaka.Player.TextTrackLabel);
54
+ this.textTrack_ = video.addTextTrack('subtitles', label);
54
55
  }
55
56
  this.textTrack_.mode = 'hidden';
56
57
  }
@@ -80,7 +81,7 @@ shaka.text.SimpleTextDisplayer = class {
80
81
  * @export
81
82
  */
82
83
  append(cues) {
83
- const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues, []);
84
+ const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues);
84
85
 
85
86
  // Convert cues.
86
87
  const textTrackCues = [];
@@ -15,16 +15,19 @@ shaka.text.Utils = class {
15
15
  * their contents should be combined and replace the payload of the parent.
16
16
  *
17
17
  * @param {!shaka.text.Cue} cue
18
+ * @param {?shaka.text.Cue=} parentCue
18
19
  * @return {string}
19
20
  * @private
20
21
  */
21
- static flattenPayload_(cue) {
22
+ static flattenPayload_(cue, parentCue) {
22
23
  if (cue.lineBreak) {
23
24
  // This is a vertical lineBreak, so insert a newline.
24
25
  return '\n';
25
26
  }
26
27
  if (cue.nestedCues.length) {
27
- return cue.nestedCues.map(shaka.text.Utils.flattenPayload_).join('');
28
+ return cue.nestedCues.map((nested) => {
29
+ return shaka.text.Utils.flattenPayload_(nested, cue);
30
+ }).join('');
28
31
  }
29
32
 
30
33
  // Handle bold, italics and underline
@@ -43,14 +46,22 @@ shaka.text.Utils = class {
43
46
  openStyleTags.push(['u']);
44
47
  }
45
48
  // Handle color classes, if the value consists of letters
49
+ let color = cue.color;
50
+ if (color == '' && parentCue) {
51
+ color = parentCue.color;
52
+ }
46
53
  let classes = '';
47
- const color = shaka.text.Utils.getColorName_(cue.color);
48
- if (color) {
49
- classes += `.${color}`;
54
+ const colorName = shaka.text.Utils.getColorName_(color);
55
+ if (colorName) {
56
+ classes += `.${colorName}`;
57
+ }
58
+ let bgColor = cue.backgroundColor;
59
+ if (bgColor == '' && parentCue) {
60
+ bgColor = parentCue.backgroundColor;
50
61
  }
51
- const bgColor = shaka.text.Utils.getColorName_(cue.backgroundColor);
52
- if (bgColor) {
53
- classes += `.bg_${bgColor}`;
62
+ const bgColorName = shaka.text.Utils.getColorName_(bgColor);
63
+ if (bgColorName) {
64
+ classes += `.bg_${bgColorName}`;
54
65
  }
55
66
  if (classes) {
56
67
  openStyleTags.push(['c', classes]);
@@ -69,7 +80,20 @@ shaka.text.Utils = class {
69
80
  * @private
70
81
  */
71
82
  static getColorName_(string) {
72
- switch (string.toLowerCase()) {
83
+ let colorString = string.toLowerCase();
84
+ const rgb = colorString.replace(/\s/g, '')
85
+ .match(/^rgba?\((\d+),(\d+),(\d+),?([^,\s)]+)?/i);
86
+ if (rgb) {
87
+ colorString = '#' +
88
+ (parseInt(rgb[1], 10) | (1 << 8)).toString(16).slice(1) +
89
+ (parseInt(rgb[2], 10) | (1 << 8)).toString(16).slice(1) +
90
+ (parseInt(rgb[3], 10) | (1 << 8)).toString(16).slice(1);
91
+ } else if (colorString.startsWith('#') && colorString.length > 7) {
92
+ // With this we lose the alpha of the color, but it is better than having
93
+ // no color.
94
+ colorString = colorString.slice(0, 7);
95
+ }
96
+ switch (colorString) {
73
97
  case 'white':
74
98
  case '#fff':
75
99
  case '#ffffff':
@@ -92,6 +116,7 @@ shaka.text.Utils = class {
92
116
  return 'yellow';
93
117
  case 'magenta':
94
118
  case '#f0f':
119
+ case '#ff00ff':
95
120
  return 'magenta';
96
121
  case 'blue':
97
122
  case '#00f':
@@ -118,19 +143,20 @@ shaka.text.Utils = class {
118
143
  * isContainer = false.
119
144
  *
120
145
  * @param {!Array.<!shaka.text.Cue>} cues
121
- * @param {!Array.<!shaka.text.Cue>} result
146
+ * @param {?shaka.text.Cue=} parentCue
122
147
  * @return {!Array.<!shaka.text.Cue>}
123
148
  */
124
- static getCuesToFlatten(cues, result) {
149
+ static getCuesToFlatten(cues, parentCue) {
150
+ const result = [];
125
151
  for (const cue of cues) {
126
152
  if (cue.isContainer) {
127
153
  // Recurse to find the actual text payload cues.
128
- shaka.text.Utils.getCuesToFlatten(cue.nestedCues, result);
154
+ result.push(...shaka.text.Utils.getCuesToFlatten(cue.nestedCues, cue));
129
155
  } else {
130
156
  // Flatten the payload.
131
157
  const flatCue = cue.clone();
132
158
  flatCue.nestedCues = [];
133
- flatCue.payload = shaka.text.Utils.flattenPayload_(cue);
159
+ flatCue.payload = shaka.text.Utils.flattenPayload_(cue, parentCue);
134
160
  result.push(flatCue);
135
161
  }
136
162
  }
@@ -44,6 +44,9 @@ shaka.text.UITextDisplayer = class {
44
44
  /** @private {HTMLElement} */
45
45
  this.videoContainer_ = videoContainer;
46
46
 
47
+ /** @private {?number} */
48
+ this.aspectRatio_ = null;
49
+
47
50
  /** @type {HTMLElement} */
48
51
  this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
49
52
  this.textContainer_.classList.add('shaka-text-container');
@@ -90,6 +93,23 @@ shaka.text.UITextDisplayer = class {
90
93
  this.updateCaptions_(/* forceUpdate= */ true);
91
94
  });
92
95
 
96
+ // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
97
+ // Whenever the natural width or natural height of the video changes
98
+ // (including, for example, because the selected video track was changed),
99
+ // if the element's readyState attribute is not HAVE_NOTHING, the user
100
+ // agent must queue a media element task given the media element to fire an
101
+ // event named resize at the media element.
102
+ this.eventManager_.listen(this.video_, 'resize', () => {
103
+ const element = /** @type {!HTMLVideoElement} */ (this.video_);
104
+ const width = element.videoWidth;
105
+ const height = element.videoHeight;
106
+ if (width && height) {
107
+ this.aspectRatio_ = width / height;
108
+ } else {
109
+ this.aspectRatio_ = null;
110
+ }
111
+ });
112
+
93
113
  /** @private {ResizeObserver} */
94
114
  this.resizeObserver_ = null;
95
115
  if ('ResizeObserver' in window) {
@@ -400,6 +420,10 @@ shaka.text.UITextDisplayer = class {
400
420
  */
401
421
  getRegionElement_(cue) {
402
422
  const region = cue.region;
423
+ // from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#caption-window-size
424
+ // if aspect ratio is 4/3, use that value, otherwise, use the 16:9 value
425
+ const lineWidthMultiple = this.aspectRatio_ === 4/3 ? 2.5 : 1.9;
426
+ const lineHeightMultiple = 5.33;
403
427
 
404
428
  const regionId = this.generateRegionId_(region);
405
429
  if (this.regionElements_.has(regionId)) {
@@ -417,11 +441,33 @@ shaka.text.UITextDisplayer = class {
417
441
  regionElement.id = 'shaka-text-region---' + regionId;
418
442
  regionElement.classList.add('shaka-text-region');
419
443
 
420
- regionElement.style.height = region.height + heightUnit;
421
- regionElement.style.width = region.width + widthUnit;
422
444
  regionElement.style.position = 'absolute';
423
- regionElement.style.top = region.viewportAnchorY + viewportAnchorUnit;
424
- regionElement.style.left = region.viewportAnchorX + viewportAnchorUnit;
445
+
446
+ const linesUnit = shaka.text.CueRegion.units.LINES;
447
+ if (region.heightUnits === linesUnit && region.widthUnits === linesUnit) {
448
+ regionElement.style.height = region.height * lineHeightMultiple + '%';
449
+ regionElement.style.width = region.width * lineWidthMultiple + '%';
450
+ } else {
451
+ regionElement.style.height = region.height + heightUnit;
452
+ regionElement.style.width = region.width + widthUnit;
453
+ }
454
+
455
+ if (region.viewportAnchorUnits === linesUnit) {
456
+ // from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708
457
+ let top = region.viewportAnchorY / 75 * 100;
458
+ const windowWidth = this.aspectRatio_ === 4/3 ? 160 : 210;
459
+ let left = region.viewportAnchorX / windowWidth * 100;
460
+ // adjust top and left values based on the region anchor and window size
461
+ top -= region.regionAnchorY * region.height * lineHeightMultiple / 100;
462
+ left -= region.regionAnchorX * region.width * lineWidthMultiple / 100;
463
+ regionElement.style.top = top + '%';
464
+ regionElement.style.left = left + '%';
465
+ } else {
466
+ regionElement.style.top = region.viewportAnchorY -
467
+ region.regionAnchorY * region.height / 100 + viewportAnchorUnit;
468
+ regionElement.style.left = region.viewportAnchorX -
469
+ region.regionAnchorX * region.width / 100 + viewportAnchorUnit;
470
+ }
425
471
 
426
472
  regionElement.style.display = 'flex';
427
473
  regionElement.style.flexDirection = 'column';
@@ -661,37 +707,46 @@ shaka.text.UITextDisplayer = class {
661
707
  // (for vertical growing left) is aligned at the line.
662
708
  // TODO: Implement line alignment with line number.
663
709
  // TODO: Implement lineAlignment of 'CENTER'.
664
- if (cue.line != null) {
710
+ let line = cue.line;
711
+ if (line != null) {
665
712
  let lineInterpretation = cue.lineInterpretation;
666
713
  // HACK: the current implementation of UITextDisplayer only handled
667
- // PERCENTAGE, as cue.line=0 is zero, we can interpret it as percentage
668
- // safely.
669
- if (cue.line == 0 &&
670
- lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
714
+ // PERCENTAGE, so we need convert LINE_NUMBER to PERCENTAGE
715
+ if (lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
671
716
  lineInterpretation = Cue.lineInterpretation.PERCENTAGE;
717
+ let maxLines = 16;
718
+ // The maximum number of lines is different if it is a vertical video.
719
+ if (this.aspectRatio_ && this.aspectRatio_ < 1) {
720
+ maxLines = 32;
721
+ }
722
+ if (line < 0) {
723
+ line = 100 + line / maxLines * 100;
724
+ } else {
725
+ line = line / maxLines * 100;
726
+ }
672
727
  }
673
728
  if (lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
674
729
  style.position = 'absolute';
675
730
  if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
676
731
  style.width = '100%';
677
732
  if (cue.lineAlign == Cue.lineAlign.START) {
678
- style.top = cue.line + '%';
733
+ style.top = line + '%';
679
734
  } else if (cue.lineAlign == Cue.lineAlign.END) {
680
- style.bottom = (100 - cue.line) + '%';
735
+ style.bottom = (100 - line) + '%';
681
736
  }
682
737
  } else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
683
738
  style.height = '100%';
684
739
  if (cue.lineAlign == Cue.lineAlign.START) {
685
- style.left = cue.line + '%';
740
+ style.left = line + '%';
686
741
  } else if (cue.lineAlign == Cue.lineAlign.END) {
687
- style.right = (100 - cue.line) + '%';
742
+ style.right = (100 - line) + '%';
688
743
  }
689
744
  } else {
690
745
  style.height = '100%';
691
746
  if (cue.lineAlign == Cue.lineAlign.START) {
692
- style.right = cue.line + '%';
747
+ style.right = line + '%';
693
748
  } else if (cue.lineAlign == Cue.lineAlign.END) {
694
- style.left = (100 - cue.line) + '%';
749
+ style.left = (100 - line) + '%';
695
750
  }
696
751
  }
697
752
  }
@@ -40,7 +40,7 @@ shaka.text.WebVttGenerator = class {
40
40
  milliseconds;
41
41
  };
42
42
 
43
- const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues, []);
43
+ const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues);
44
44
 
45
45
  let webvttString = 'WEBVTT\n\n';
46
46
  for (const cue of flattenedCues) {
@@ -301,11 +301,45 @@ shaka.util.Mp4BoxParsers = class {
301
301
  */
302
302
  static parseESDS(reader) {
303
303
  let codec = 'mp4a';
304
- reader.skip(11);
305
- codec += '.' + this.toHex_(reader.readUint8());
306
- // this value is only a single digit
307
- codec += '.' +
308
- this.toHex_((reader.readUint8() >>> 2) & 0x3f).replace(/^0/, '');
304
+ let tag;
305
+ let oti;
306
+ while (reader.hasMoreData()) {
307
+ tag = reader.readUint8();
308
+ let byteRead = reader.readUint8();
309
+ while (byteRead & 0x80) {
310
+ byteRead = reader.readUint8();
311
+ }
312
+ if (tag == 0x03) {
313
+ reader.readUint16();
314
+ const flags = reader.readUint8();
315
+ if (flags & 0x80) {
316
+ reader.readUint16();
317
+ }
318
+ if (flags & 0x40) {
319
+ reader.skip(reader.readUint8());
320
+ }
321
+ if (flags & 0x20) {
322
+ reader.readUint16();
323
+ }
324
+ } else if (tag == 0x04) {
325
+ oti = reader.readUint8();
326
+ reader.skip(12);
327
+ } else if (tag == 0x05) {
328
+ break;
329
+ }
330
+ }
331
+ if (oti) {
332
+ codec += '.' + this.toHex_(oti);
333
+ if (tag == 0x05 && reader.hasMoreData()) {
334
+ const firstData = reader.readUint8();
335
+ let audioObjectType = (firstData & 0xF8) >> 3;
336
+ if (audioObjectType === 31 && reader.hasMoreData()) {
337
+ audioObjectType = 32 + ((firstData & 0x7) << 3) +
338
+ ((reader.readUint8() & 0xE0) >> 5);
339
+ }
340
+ codec += '.' + audioObjectType;
341
+ }
342
+ }
309
343
  return {codec};
310
344
  }
311
345
 
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.6.2",
4
+ "version": "4.6.4",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [