shaka-player 4.2.5 → 4.3.1

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 (143) hide show
  1. package/.github/workflows/build-and-test.yaml +7 -0
  2. package/.github/workflows/sync-labels.yaml +1 -1
  3. package/.github/workflows/update-issues.yaml +1 -1
  4. package/AUTHORS +4 -0
  5. package/CHANGELOG.md +114 -74
  6. package/CONTRIBUTORS +4 -0
  7. package/README.md +14 -0
  8. package/conditional/dummy_cast_proxy.js +120 -0
  9. package/demo/common/assets.js +58 -0
  10. package/demo/common/message_ids.js +22 -1
  11. package/demo/config.js +89 -5
  12. package/demo/demo.less +7 -1
  13. package/demo/index.html +8 -1
  14. package/demo/locales/en.json +21 -1
  15. package/demo/locales/source.json +82 -2
  16. package/demo/main.js +57 -1
  17. package/demo/search.js +2 -0
  18. package/demo/visualizer.js +337 -0
  19. package/demo/visualizer_button.js +103 -0
  20. package/dist/controls.css +1 -1
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +107 -88
  23. package/dist/demo.compiled.debug.map +4 -4
  24. package/dist/demo.compiled.js +107 -88
  25. package/dist/demo.compiled.map +4 -4
  26. package/dist/demo.css +1 -1
  27. package/dist/demo.css.map +1 -1
  28. package/dist/deps.js +21 -13
  29. package/dist/locales.js +31 -31
  30. package/dist/receiver.compiled.debug.js +75 -77
  31. package/dist/receiver.compiled.debug.map +3 -3
  32. package/dist/receiver.compiled.js +75 -77
  33. package/dist/receiver.compiled.map +3 -3
  34. package/dist/shaka-player.compiled.d.ts +181 -27
  35. package/dist/shaka-player.compiled.debug.d.ts +181 -27
  36. package/dist/shaka-player.compiled.debug.externs.js +169 -31
  37. package/dist/shaka-player.compiled.debug.js +510 -450
  38. package/dist/shaka-player.compiled.debug.map +4 -4
  39. package/dist/shaka-player.compiled.externs.js +169 -31
  40. package/dist/shaka-player.compiled.js +912 -874
  41. package/dist/shaka-player.compiled.map +4 -4
  42. package/dist/shaka-player.ui.d.ts +181 -27
  43. package/dist/shaka-player.ui.debug.d.ts +181 -27
  44. package/dist/shaka-player.ui.debug.externs.js +169 -31
  45. package/dist/shaka-player.ui.debug.js +533 -471
  46. package/dist/shaka-player.ui.debug.map +4 -4
  47. package/dist/shaka-player.ui.externs.js +169 -31
  48. package/dist/shaka-player.ui.js +1073 -1036
  49. package/dist/shaka-player.ui.map +4 -4
  50. package/docs/design/lcevc-architecture.png +0 -0
  51. package/docs/design/lcevc-demo.png +0 -0
  52. package/docs/design/lcevc-integration.md +72 -0
  53. package/docs/tutorials/fairplay.md +7 -0
  54. package/docs/tutorials/faq.md +1 -1
  55. package/docs/tutorials/ui.md +7 -7
  56. package/docs/tutorials/upgrade.md +6 -0
  57. package/externs/ima.js +13 -1
  58. package/externs/lcevc.js +81 -0
  59. package/externs/mux.js +1 -42
  60. package/externs/shaka/ads.js +11 -1
  61. package/externs/shaka/manifest_parser.js +5 -1
  62. package/externs/shaka/mp4_parser.js +3 -0
  63. package/externs/shaka/player.js +178 -15
  64. package/lib/abr/simple_abr_manager.js +7 -0
  65. package/lib/ads/ad_manager.js +0 -8
  66. package/lib/ads/client_side_ad.js +15 -0
  67. package/lib/ads/client_side_ad_manager.js +21 -8
  68. package/lib/ads/server_side_ad.js +15 -0
  69. package/lib/ads/server_side_ad_manager.js +3 -3
  70. package/lib/cast/cast_utils.js +2 -0
  71. package/lib/cea/dummy_cea_parser.js +28 -0
  72. package/lib/cea/i_cea_parser.js +14 -2
  73. package/lib/cea/mp4_cea_parser.js +108 -5
  74. package/lib/config/auto_show_text.js +33 -0
  75. package/lib/dash/dash_parser.js +1 -1
  76. package/lib/hls/hls_parser.js +808 -384
  77. package/lib/lcevc/lcevc_dil.js +152 -0
  78. package/lib/media/closed_caption_parser.js +9 -6
  79. package/lib/media/drm_engine.js +14 -6
  80. package/lib/media/media_source_capabilities.js +36 -0
  81. package/lib/media/media_source_engine.js +113 -28
  82. package/lib/media/presentation_timeline.js +26 -11
  83. package/lib/media/segment_reference.js +49 -1
  84. package/lib/media/streaming_engine.js +142 -12
  85. package/lib/media/transmuxer.js +78 -21
  86. package/lib/offline/storage.js +1 -0
  87. package/lib/player.js +456 -77
  88. package/lib/polyfill/media_capabilities.js +9 -5
  89. package/lib/polyfill/patchedmediakeys_apple.js +63 -4
  90. package/lib/text/vtt_text_parser.js +35 -1
  91. package/lib/util/dom_utils.js +0 -1
  92. package/lib/util/error.js +17 -0
  93. package/lib/util/fake_event.js +2 -0
  94. package/lib/util/id3_utils.js +300 -0
  95. package/lib/util/mime_utils.js +3 -6
  96. package/lib/util/mp4_box_parsers.js +39 -5
  97. package/lib/util/mp4_parser.js +35 -0
  98. package/lib/util/player_configuration.js +23 -1
  99. package/lib/util/stream_utils.js +5 -3
  100. package/lib/util/ts_parser.js +499 -0
  101. package/maintained-branches.md +2 -2
  102. package/package.json +7 -4
  103. package/roadmap.md +5 -3
  104. package/test/ads/ad_manager_unit.js +182 -29
  105. package/test/cast/cast_utils_unit.js +3 -1
  106. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  107. package/test/dash/dash_parser_live_unit.js +1 -0
  108. package/test/dash/dash_parser_manifest_unit.js +93 -1
  109. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  110. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  111. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  112. package/test/demo/demo_unit.js +2 -1
  113. package/test/hls/hls_live_unit.js +253 -19
  114. package/test/hls/hls_parser_unit.js +631 -12
  115. package/test/media/closed_caption_parser_unit.js +2 -1
  116. package/test/media/drm_engine_integration.js +3 -1
  117. package/test/media/media_source_engine_integration.js +71 -6
  118. package/test/media/media_source_engine_unit.js +10 -5
  119. package/test/media/streaming_engine_integration.js +4 -1
  120. package/test/media/streaming_engine_unit.js +144 -1
  121. package/test/media/transmuxer_integration.js +21 -10
  122. package/test/player_integration.js +224 -82
  123. package/test/player_unit.js +5 -2
  124. package/test/polyfill/media_capabilities_unit.js +21 -23
  125. package/test/polyfill/patchedmediakeys_apple_unit.js +130 -0
  126. package/test/test/assets/id3-metadata.aac +0 -0
  127. package/test/test/assets/id3-metadata.ts +0 -0
  128. package/test/test/assets/thumbnails-sprites.vtt +10 -0
  129. package/test/test/assets/thumbnails.vtt +10 -0
  130. package/test/test/util/dash_parser_util.js +2 -0
  131. package/test/test/util/fake_ad.js +21 -0
  132. package/test/test/util/fake_media_source_engine.js +7 -0
  133. package/test/test/util/id3_generator.js +100 -0
  134. package/test/test/util/manifest_generator.js +3 -6
  135. package/test/test/util/simple_fakes.js +4 -0
  136. package/test/test/util/stream_generator.js +38 -0
  137. package/test/test/util/test_scheme.js +43 -14
  138. package/test/text/vtt_text_parser_unit.js +11 -0
  139. package/test/util/id3_utils_unit.js +116 -0
  140. package/test/util/ts_parser_unit.js +77 -0
  141. package/third_party/closure-uri/uri.js +1 -1
  142. package/ui/less/containers.less +7 -0
  143. package/ui/ui.js +43 -4
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.polyfill.MediaCapabilities');
8
8
 
9
9
  goog.require('shaka.log');
10
+ goog.require('shaka.media.Capabilities');
10
11
  goog.require('shaka.polyfill');
11
12
  goog.require('shaka.util.Error');
12
13
  goog.require('shaka.util.Platform');
@@ -100,11 +101,14 @@ shaka.polyfill.MediaCapabilities = class {
100
101
  const videoConfig = mediaDecodingConfig['video'];
101
102
  const audioConfig = mediaDecodingConfig['audio'];
102
103
 
104
+ const Capabilities = shaka.media.Capabilities;
105
+
103
106
  if (mediaDecodingConfig.type == 'media-source') {
104
107
  if (!shaka.util.Platform.supportsMediaSource()) {
105
108
  return res;
106
109
  }
107
- // Use 'MediaSource.isTypeSupported' to check if the stream is supported.
110
+ // Use 'shaka.media.Capabilities.isTypeSupported'to check if
111
+ // the stream is supported.
108
112
  // Cast platforms will additionally check canDisplayType(), which
109
113
  // accepts extended MIME type parameters.
110
114
  // See: https://github.com/shaka-project/shaka-player/issues/4726
@@ -114,7 +118,7 @@ shaka.polyfill.MediaCapabilities = class {
114
118
  isSupported =
115
119
  shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
116
120
  } else {
117
- isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
121
+ isSupported = Capabilities.isTypeSupported(videoConfig.contentType);
118
122
  }
119
123
  if (!isSupported) {
120
124
  return res;
@@ -123,7 +127,7 @@ shaka.polyfill.MediaCapabilities = class {
123
127
 
124
128
  if (audioConfig) {
125
129
  const contentType = audioConfig.contentType;
126
- const isSupported = MediaSource.isTypeSupported(contentType);
130
+ const isSupported = Capabilities.isTypeSupported(contentType);
127
131
  if (!isSupported) {
128
132
  return res;
129
133
  }
@@ -231,8 +235,8 @@ shaka.polyfill.MediaCapabilities = class {
231
235
  shaka.util.Error.Code.CAST_API_UNAVAILABLE);
232
236
  } else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
233
237
  shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
234
- 'MediaSource.isTypeSupported() for type support.');
235
- return MediaSource.isTypeSupported(videoConfig.contentType);
238
+ 'shaka.media.Capabilities.isTypeSupported() for type support.');
239
+ return shaka.media.Capabilities.isTypeSupported(videoConfig.contentType);
236
240
  }
237
241
 
238
242
  let displayType = videoConfig.contentType;
@@ -27,18 +27,38 @@ goog.require('shaka.util.StringUtils');
27
27
  shaka.polyfill.PatchedMediaKeysApple = class {
28
28
  /**
29
29
  * Installs the polyfill if needed.
30
+ * @param {boolean=} enableUninstall enables uninstalling the polyfill
30
31
  * @export
31
32
  */
32
- static install() {
33
+ static install(enableUninstall = false) {
34
+ // Alias
35
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
36
+
33
37
  if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
34
38
  // No HTML5 video or no prefixed EME.
35
39
  return;
36
40
  }
37
41
 
38
- shaka.log.info('Using Apple-prefixed EME');
42
+ if (enableUninstall) {
43
+ PatchedMediaKeysApple.enableUninstall = true;
44
+ PatchedMediaKeysApple.originalHTMLMediaElementPrototypeMediaKeys =
45
+ /** @type {!Object} */ (
46
+ Object.getOwnPropertyDescriptor(
47
+ // eslint-disable-next-line no-restricted-syntax
48
+ HTMLMediaElement.prototype, 'mediaKeys',
49
+ )
50
+ );
51
+ PatchedMediaKeysApple.originalHTMLMediaElementPrototypeSetMediaKeys =
52
+ // eslint-disable-next-line no-restricted-syntax
53
+ HTMLMediaElement.prototype.setMediaKeys;
54
+ PatchedMediaKeysApple.originalWindowMediaKeys = window.MediaKeys;
55
+ PatchedMediaKeysApple.originalWindowMediaKeySystemAccess =
56
+ window.MediaKeySystemAccess;
57
+ PatchedMediaKeysApple.originalNavigatorRequestMediaKeySystemAccess =
58
+ navigator.requestMediaKeySystemAccess;
59
+ }
39
60
 
40
- // Alias
41
- const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
61
+ shaka.log.info('Using Apple-prefixed EME');
42
62
 
43
63
  // Delete mediaKeys to work around strict mode compatibility issues.
44
64
  // eslint-disable-next-line no-restricted-syntax
@@ -59,6 +79,45 @@ shaka.polyfill.PatchedMediaKeysApple = class {
59
79
  window.shakaMediaKeysPolyfill = true;
60
80
  }
61
81
 
82
+ /**
83
+ * Uninstalls the polyfill if needed and enabled.
84
+ * @export
85
+ */
86
+ static uninstall() {
87
+ // Alias
88
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
89
+
90
+ if (!PatchedMediaKeysApple.enableUninstall) {
91
+ return;
92
+ }
93
+
94
+ shaka.log.info('Un-installing Apple-prefixed EME');
95
+
96
+ PatchedMediaKeysApple.enableUninstall = false;
97
+ Object.defineProperty(
98
+ // eslint-disable-next-line no-restricted-syntax
99
+ HTMLMediaElement.prototype,
100
+ 'mediaKeys',
101
+ PatchedMediaKeysApple.originalHTMLMediaElementPrototypeMediaKeys,
102
+ );
103
+ // eslint-disable-next-line no-restricted-syntax
104
+ HTMLMediaElement.prototype.setMediaKeys =
105
+ PatchedMediaKeysApple.originalHTMLMediaElementPrototypeSetMediaKeys;
106
+ window.MediaKeys = PatchedMediaKeysApple.originalWindowMediaKeys;
107
+ window.MediaKeySystemAccess =
108
+ PatchedMediaKeysApple.originalWindowMediaKeySystemAccess;
109
+ navigator.requestMediaKeySystemAccess =
110
+ PatchedMediaKeysApple.originalNavigatorRequestMediaKeySystemAccess;
111
+
112
+ PatchedMediaKeysApple.originalWindowMediaKeys = null;
113
+ PatchedMediaKeysApple.originalWindowMediaKeySystemAccess = null;
114
+ PatchedMediaKeysApple.originalHTMLMediaElementPrototypeSetMediaKeys = null;
115
+ PatchedMediaKeysApple.originalNavigatorRequestMediaKeySystemAccess = null;
116
+ PatchedMediaKeysApple.originalHTMLMediaElementPrototypeMediaKeys = null;
117
+
118
+ window.shakaMediaKeysPolyfill = false;
119
+ }
120
+
62
121
  /**
63
122
  * An implementation of navigator.requestMediaKeySystemAccess.
64
123
  * Retrieves a MediaKeySystemAccess object.
@@ -366,7 +366,8 @@ shaka.text.VttTextParser = class {
366
366
  end += timeOffset;
367
367
 
368
368
  // Get the payload.
369
- const payload = text.slice(1).join('\n').trim();
369
+ const payload = VttTextParser.htmlUnescape_(
370
+ text.slice(1).join('\n').trim());
370
371
 
371
372
  let cue = null;
372
373
  if (styles.has('global')) {
@@ -843,6 +844,39 @@ shaka.text.VttTextParser = class {
843
844
 
844
845
  return (milliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
845
846
  }
847
+
848
+ /**
849
+ * This method converts the HTML entities &, <, >, ", and
850
+ * ' in string to their corresponding characters.
851
+ *
852
+ * @param {!string} input
853
+ * @return {string}
854
+ * @private
855
+ */
856
+ static htmlUnescape_(input) {
857
+ // Used to map HTML entities to characters.
858
+ const htmlUnescapes = {
859
+ '&': '&',
860
+ '&lt;': '<',
861
+ '&gt;': '>',
862
+ '&quot;': '"',
863
+ '&#39;': '\'',
864
+ };
865
+
866
+ // Used to match HTML entities and HTML characters.
867
+ const reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39);/g;
868
+ const reHasEscapedHtml = RegExp(reEscapedHtml.source);
869
+ // This check is an optimization, since replace always makes a copy
870
+ if (input && reHasEscapedHtml.test(input)) {
871
+ return input.replace(reEscapedHtml, (entity) => {
872
+ // The only thing that might not match the dictionary above is the
873
+ // single quote, which can be matched by many strings in the regex, but
874
+ // only has a single entry in the dictionary.
875
+ return htmlUnescapes[entity] || '\'';
876
+ });
877
+ }
878
+ return input || '';
879
+ }
846
880
  };
847
881
 
848
882
  /**
@@ -78,7 +78,6 @@ shaka.util.Dom = class {
78
78
  return shaka.util.Dom.asHTMLElement(elements[0]);
79
79
  }
80
80
 
81
-
82
81
  /**
83
82
  * Remove all of the child nodes of an element.
84
83
  * @param {!Element} element
package/lib/util/error.js CHANGED
@@ -358,6 +358,18 @@ shaka.util.Error.Code = {
358
358
  */
359
359
  'CHAPTERS_TRACK_FAILED': 2015,
360
360
 
361
+ /**
362
+ * External thumbnails tracks cannot be added in src= because native platform
363
+ * doesn't support it.
364
+ */
365
+ 'CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_SRC_EQUALS': 2016,
366
+
367
+ /**
368
+ * Only external urls of WebVTT type are supported.
369
+ * <br> error.data[0] is the uri.
370
+ */
371
+ 'UNSUPPORTED_EXTERNAL_THUMBNAILS_URI': 2017,
372
+
361
373
  /**
362
374
  * Some component tried to read past the end of a buffer. The segment index,
363
375
  * init segment, or PSSH may be malformed.
@@ -707,6 +719,11 @@ shaka.util.Error.Code = {
707
719
  */
708
720
  'HLS_AES_128_INVALID_KEY_LENGTH': 4044,
709
721
 
722
+ /**
723
+ * External thumbnails tracks cannot be added to live streams.
724
+ */
725
+ 'CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_LIVE_STREAM': 4045,
726
+
710
727
 
711
728
  // RETIRED: 'INCONSISTENT_BUFFER_STATE': 5000,
712
729
  // RETIRED: 'INVALID_SEGMENT_INDEX': 5001,
@@ -160,6 +160,7 @@ shaka.util.FakeEvent.EventName = {
160
160
  DownloadHeadersReceived: 'downloadheadersreceived',
161
161
  DrmSessionUpdate: 'drmsessionupdate',
162
162
  Emsg: 'emsg',
163
+ Prft: 'prft',
163
164
  Error: 'error',
164
165
  ExpirationUpdated: 'expirationupdated',
165
166
  GapJumped: 'gapjumped',
@@ -171,6 +172,7 @@ shaka.util.FakeEvent.EventName = {
171
172
  OnStateChange: 'onstatechange',
172
173
  OnStateIdle: 'onstateidle',
173
174
  RateChange: 'ratechange',
175
+ SegmentAppended: 'segmentappended',
174
176
  SessionDataEvent: 'sessiondata',
175
177
  StallDetected: 'stalldetected',
176
178
  Streaming: 'streaming',
@@ -0,0 +1,300 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2022 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.util.Id3Utils');
8
+
9
+ goog.require('shaka.log');
10
+ goog.require('shaka.util.BufferUtils');
11
+ goog.require('shaka.util.StringUtils');
12
+
13
+
14
+ /**
15
+ * @summary A set of Id3Utils utility functions.
16
+ * @export
17
+ */
18
+ shaka.util.Id3Utils = class {
19
+ /**
20
+ * @param {Uint8Array} data
21
+ * @param {number} offset
22
+ * @return {boolean}
23
+ * @private
24
+ */
25
+ static isHeader_(data, offset) {
26
+ /*
27
+ * http://id3.org/id3v2.3.0
28
+ * [0] = 'I'
29
+ * [1] = 'D'
30
+ * [2] = '3'
31
+ * [3,4] = {Version}
32
+ * [5] = {Flags}
33
+ * [6-9] = {ID3 Size}
34
+ *
35
+ * An ID3v2 tag can be detected with the following pattern:
36
+ * $49 44 33 yy yy xx zz zz zz zz
37
+ * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80
38
+ */
39
+ if (offset + 10 <= data.length) {
40
+ // look for 'ID3' identifier
41
+ if (data[offset] === 0x49 &&
42
+ data[offset + 1] === 0x44 &&
43
+ data[offset + 2] === 0x33) {
44
+ // check version is within range
45
+ if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
46
+ // check size is within range
47
+ if (data[offset + 6] < 0x80 &&
48
+ data[offset + 7] < 0x80 &&
49
+ data[offset + 8] < 0x80 &&
50
+ data[offset + 9] < 0x80) {
51
+ return true;
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * @param {Uint8Array} data
62
+ * @param {number} offset
63
+ * @return {boolean}
64
+ * @private
65
+ */
66
+ static isFooter_(data, offset) {
67
+ /*
68
+ * The footer is a copy of the header, but with a different identifier
69
+ */
70
+ if (offset + 10 <= data.length) {
71
+ // look for '3DI' identifier
72
+ if (data[offset] === 0x33 &&
73
+ data[offset + 1] === 0x44 &&
74
+ data[offset + 2] === 0x49) {
75
+ // check version is within range
76
+ if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
77
+ // check size is within range
78
+ if (data[offset + 6] < 0x80 &&
79
+ data[offset + 7] < 0x80 &&
80
+ data[offset + 8] < 0x80 &&
81
+ data[offset + 9] < 0x80) {
82
+ return true;
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ return false;
89
+ }
90
+
91
+ /**
92
+ * @param {Uint8Array} data
93
+ * @param {number} offset
94
+ * @return {number}
95
+ * @private
96
+ */
97
+ static readSize_(data, offset) {
98
+ let size = 0;
99
+ size = (data[offset] & 0x7f) << 21;
100
+ size |= (data[offset + 1] & 0x7f) << 14;
101
+ size |= (data[offset + 2] & 0x7f) << 7;
102
+ size |= data[offset + 3] & 0x7f;
103
+ return size;
104
+ }
105
+
106
+ /**
107
+ * @param {Uint8Array} data
108
+ * @return {shaka.extern.MetadataRawFrame}
109
+ * @private
110
+ */
111
+ static getFrameData_(data) {
112
+ /*
113
+ * Frame ID $xx xx xx xx (four characters)
114
+ * Size $xx xx xx xx
115
+ * Flags $xx xx
116
+ */
117
+ const type = String.fromCharCode(data[0], data[1], data[2], data[3]);
118
+ const size = shaka.util.Id3Utils.readSize_(data, 4);
119
+
120
+ // skip frame id, size, and flags
121
+ const offset = 10;
122
+
123
+ return {
124
+ type,
125
+ size,
126
+ data: data.subarray(offset, offset + size),
127
+ };
128
+ }
129
+
130
+ /**
131
+ * @param {shaka.extern.MetadataRawFrame} frame
132
+ * @return {?shaka.extern.MetadataFrame}
133
+ * @private
134
+ */
135
+ static decodeFrame_(frame) {
136
+ const BufferUtils = shaka.util.BufferUtils;
137
+ const StringUtils = shaka.util.StringUtils;
138
+
139
+ const metadataFrame = {
140
+ key: frame.type,
141
+ description: '',
142
+ data: '',
143
+ };
144
+
145
+ if (frame.type === 'TXXX') {
146
+ /*
147
+ * Format:
148
+ * [0] = {Text Encoding}
149
+ * [1-?] = {Description}\0{Value}
150
+ */
151
+ if (frame.size < 2) {
152
+ return null;
153
+ }
154
+ if (frame.data[0] !== shaka.util.Id3Utils.UTF8_encoding) {
155
+ shaka.log.warning('Ignore frame with unrecognized character ' +
156
+ 'encoding');
157
+ return null;
158
+ }
159
+ const descriptionEndIndex = frame.data.subarray(1).indexOf(0);
160
+
161
+ if (descriptionEndIndex === -1) {
162
+ return null;
163
+ }
164
+ const description = StringUtils.fromUTF8(
165
+ BufferUtils.toUint8(frame.data, 1, descriptionEndIndex));
166
+ const data = StringUtils.fromUTF8(
167
+ BufferUtils.toUint8(frame.data, 2 + descriptionEndIndex))
168
+ .replace(/\0*$/, '');
169
+
170
+ metadataFrame.description = description;
171
+ metadataFrame.data = data;
172
+ return metadataFrame;
173
+ } else if (frame.type === 'WXXX') {
174
+ /*
175
+ * Format:
176
+ * [0] = {Text Encoding}
177
+ * [1-?] = {Description}\0{URL}
178
+ */
179
+ if (frame.size < 2) {
180
+ return null;
181
+ }
182
+ if (frame.data[0] !== shaka.util.Id3Utils.UTF8_encoding) {
183
+ shaka.log.warning('Ignore frame with unrecognized character ' +
184
+ 'encoding');
185
+ return null;
186
+ }
187
+ const descriptionEndIndex = frame.data.subarray(1).indexOf(0);
188
+
189
+ if (descriptionEndIndex === -1) {
190
+ return null;
191
+ }
192
+ const description = StringUtils.fromUTF8(
193
+ BufferUtils.toUint8(frame.data, 1, descriptionEndIndex));
194
+ const data = StringUtils.fromUTF8(
195
+ BufferUtils.toUint8(frame.data, 2 + descriptionEndIndex))
196
+ .replace(/\0*$/, '');
197
+
198
+ metadataFrame.description = description;
199
+ metadataFrame.data = data;
200
+ return metadataFrame;
201
+ } else if (frame.type === 'PRIV') {
202
+ /*
203
+ * Format: <text string>\0<binary data>
204
+ */
205
+ if (frame.size < 2) {
206
+ return null;
207
+ }
208
+ const textEndIndex = frame.data.indexOf(0);
209
+ if (textEndIndex === -1) {
210
+ return null;
211
+ }
212
+ const text = StringUtils.fromUTF8(
213
+ BufferUtils.toUint8(frame.data, 0, textEndIndex));
214
+ const data = BufferUtils.toArrayBuffer(
215
+ frame.data.subarray(text.length + 1));
216
+ metadataFrame.description = text;
217
+ metadataFrame.data = data;
218
+ return metadataFrame;
219
+ } else if (frame.type[0] === 'T') {
220
+ /*
221
+ * Format:
222
+ * [0] = {Text Encoding}
223
+ * [1-?] = {Value}
224
+ */
225
+ if (frame.size < 2) {
226
+ return null;
227
+ }
228
+ if (frame.data[0] !== shaka.util.Id3Utils.UTF8_encoding) {
229
+ shaka.log.warning('Ignore frame with unrecognized character ' +
230
+ 'encoding');
231
+ return null;
232
+ }
233
+ const text = StringUtils.fromUTF8(frame.data.subarray(1))
234
+ .replace(/\0*$/, '');
235
+ metadataFrame.data = text;
236
+ return metadataFrame;
237
+ } else if (frame.type[0] === 'W') {
238
+ /*
239
+ * Format:
240
+ * [0-?] = {URL}
241
+ */
242
+ const url = StringUtils.fromUTF8(frame.data)
243
+ .replace(/\0*$/, '');
244
+ metadataFrame.data = url;
245
+ return metadataFrame;
246
+ } else if (frame.data) {
247
+ shaka.log.warning('Unrecognized ID3 frame type:', frame.type);
248
+ metadataFrame.data = BufferUtils.toArrayBuffer(frame.data);
249
+ return metadataFrame;
250
+ }
251
+
252
+ return null;
253
+ }
254
+
255
+ /**
256
+ * Returns an array of ID3 frames found in all the ID3 tags in the id3Data
257
+ * @param {Uint8Array} id3Data - The ID3 data containing one or more ID3 tags
258
+ * @return {!Array.<shaka.extern.MetadataFrame>}
259
+ * @export
260
+ */
261
+ static getID3Frames(id3Data) {
262
+ const Id3Utils = shaka.util.Id3Utils;
263
+ let offset = 0;
264
+ const frames = [];
265
+ while (Id3Utils.isHeader_(id3Data, offset)) {
266
+ const size = Id3Utils.readSize_(id3Data, offset + 6);
267
+
268
+ if ((id3Data[offset + 5] >> 6) & 1) {
269
+ // skip extended header
270
+ offset += 10;
271
+ }
272
+ // skip past ID3 header
273
+ offset += 10;
274
+
275
+ const end = offset + size;
276
+ // loop through frames in the ID3 tag
277
+ while (offset + 10 < end) {
278
+ const frameData = Id3Utils.getFrameData_(id3Data.subarray(offset));
279
+ const frame = Id3Utils.decodeFrame_(frameData);
280
+ if (frame) {
281
+ frames.push(frame);
282
+ }
283
+
284
+ // skip frame header and frame data
285
+ offset += frameData.size + 10;
286
+ }
287
+
288
+ if (Id3Utils.isFooter_(id3Data, offset)) {
289
+ offset += 10;
290
+ }
291
+ }
292
+ return frames;
293
+ }
294
+ };
295
+
296
+ /**
297
+ * UTF8 encoding byte
298
+ * @const {number}
299
+ */
300
+ shaka.util.Id3Utils.UTF8_encoding = 0x03;
@@ -6,7 +6,6 @@
6
6
 
7
7
  goog.provide('shaka.util.MimeUtils');
8
8
 
9
- goog.require('shaka.dependencies');
10
9
  goog.require('shaka.media.Transmuxer');
11
10
  goog.require('shaka.util.ManifestParserUtils');
12
11
 
@@ -46,12 +45,10 @@ shaka.util.MimeUtils = class {
46
45
  static getFullOrConvertedType(mimeType, codecs, contentType) {
47
46
  const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
48
47
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
48
+ const Transmuxer = shaka.media.Transmuxer;
49
49
 
50
- if (shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
51
- if (shaka.dependencies.muxjs()) {
52
- return shaka.media.Transmuxer.convertTsCodecs(
53
- contentType, fullMimeType);
54
- }
50
+ if (Transmuxer.isSupported(fullMimeType, contentType)) {
51
+ return shaka.media.Transmuxer.convertCodecs(contentType, fullMimeType);
55
52
  } else if (contentType == ContentType.AUDIO) {
56
53
  // video/mp2t is the correct mime type for TS audio, so only replace the
57
54
  // word "video" with "audio" for non-TS audio content.
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.util.Mp4BoxParsers');
8
8
 
9
9
  goog.require('shaka.util.DataViewReader');
10
+ goog.require('shaka.util.Mp4Parser');
10
11
 
11
12
  shaka.util.Mp4BoxParsers = class {
12
13
  /**
@@ -18,12 +19,13 @@ shaka.util.Mp4BoxParsers = class {
18
19
  static parseTFHD(reader, flags) {
19
20
  let defaultSampleDuration = null;
20
21
  let defaultSampleSize = null;
22
+ let baseDataOffset = null;
21
23
 
22
24
  const trackId = reader.readUint32(); // Read "track_ID"
23
25
 
24
26
  // Skip "base_data_offset" if present.
25
27
  if (flags & 0x000001) {
26
- reader.skip(8);
28
+ baseDataOffset = reader.readUint64();
27
29
  }
28
30
 
29
31
  // Skip "sample_description_index" if present.
@@ -45,6 +47,7 @@ shaka.util.Mp4BoxParsers = class {
45
47
  trackId,
46
48
  defaultSampleDuration,
47
49
  defaultSampleSize,
50
+ baseDataOffset,
48
51
  };
49
52
  }
50
53
 
@@ -112,10 +115,11 @@ shaka.util.Mp4BoxParsers = class {
112
115
  static parseTRUN(reader, version, flags) {
113
116
  const sampleCount = reader.readUint32();
114
117
  const sampleData = [];
118
+ let dataOffset = null;
115
119
 
116
- // Skip "data_offset" if present.
120
+ // "data_offset"
117
121
  if (flags & 0x000001) {
118
- reader.skip(4);
122
+ dataOffset = reader.readUint32();
119
123
  }
120
124
 
121
125
  // Skip "first_sample_flags" if present.
@@ -159,6 +163,7 @@ shaka.util.Mp4BoxParsers = class {
159
163
  return {
160
164
  sampleCount,
161
165
  sampleData,
166
+ dataOffset,
162
167
  };
163
168
  }
164
169
 
@@ -184,6 +189,17 @@ shaka.util.Mp4BoxParsers = class {
184
189
  trackId,
185
190
  };
186
191
  }
192
+
193
+ /**
194
+ * Parses a FRMA box.
195
+ * @param {!shaka.util.DataViewReader} reader
196
+ * @return {!shaka.util.ParsedFRMABox}
197
+ */
198
+ static parseFRMA(reader) {
199
+ const fourcc = reader.readUint32();
200
+ const codec = shaka.util.Mp4Parser.typeToString(fourcc);
201
+ return {codec};
202
+ }
187
203
  };
188
204
 
189
205
 
@@ -191,7 +207,8 @@ shaka.util.Mp4BoxParsers = class {
191
207
  * @typedef {{
192
208
  * trackId: number,
193
209
  * defaultSampleDuration: ?number,
194
- * defaultSampleSize: ?number
210
+ * defaultSampleSize: ?number,
211
+ * baseDataOffset: ?number
195
212
  * }}
196
213
  *
197
214
  * @property {number} trackId
@@ -203,6 +220,8 @@ shaka.util.Mp4BoxParsers = class {
203
220
  * @property {?number} defaultSampleSize
204
221
  * If specified via flags, this overrides the default sample
205
222
  * size in the Track Extends Box for this fragment
223
+ * @property {?number} baseDataOffset
224
+ * If specified via flags, this indicate the base data offset
206
225
  *
207
226
  * @exportDoc
208
227
  */
@@ -252,13 +271,16 @@ shaka.util.ParsedTREXBox;
252
271
  /**
253
272
  * @typedef {{
254
273
  * sampleCount: number,
255
- * sampleData: !Array.<shaka.util.ParsedTRUNSample>
274
+ * sampleData: !Array.<shaka.util.ParsedTRUNSample>,
275
+ * dataOffset: ?number
256
276
  * }}
257
277
  *
258
278
  * @property {number} sampleCount
259
279
  * As per the spec: the number of samples being added in this run;
260
280
  * @property {!Array.<shaka.util.ParsedTRUNSample>} sampleData
261
281
  * An array of size <sampleCount> containing data for each sample
282
+ * @property {?number} dataOffset
283
+ * If specified via flags, this indicate the offset of the sample in bytes.
262
284
  *
263
285
  * @exportDoc
264
286
  */
@@ -296,3 +318,15 @@ shaka.util.ParsedTRUNSample;
296
318
  * @exportDoc
297
319
  */
298
320
  shaka.util.ParsedTKHDBox;
321
+
322
+ /**
323
+ * @typedef {{
324
+ * codec: string
325
+ * }}
326
+ *
327
+ * @property {string} codec
328
+ * A fourcc for a codec
329
+ *
330
+ * @exportDoc
331
+ */
332
+ shaka.util.ParsedFRMABox;