shaka-player 4.2.4 → 4.3.0

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 (137) hide show
  1. package/.github/workflows/sync-labels.yaml +1 -1
  2. package/.github/workflows/update-issues.yaml +1 -1
  3. package/AUTHORS +4 -0
  4. package/CHANGELOG.md +85 -54
  5. package/CONTRIBUTORS +4 -0
  6. package/README.md +14 -0
  7. package/demo/common/assets.js +58 -0
  8. package/demo/common/message_ids.js +22 -1
  9. package/demo/config.js +89 -5
  10. package/demo/demo.less +7 -1
  11. package/demo/index.html +8 -1
  12. package/demo/locales/en.json +21 -1
  13. package/demo/locales/source.json +82 -2
  14. package/demo/main.js +57 -1
  15. package/demo/search.js +2 -0
  16. package/demo/visualizer.js +337 -0
  17. package/demo/visualizer_button.js +103 -0
  18. package/dist/controls.css +1 -1
  19. package/dist/controls.css.map +1 -1
  20. package/dist/demo.compiled.debug.js +83 -58
  21. package/dist/demo.compiled.debug.map +4 -4
  22. package/dist/demo.compiled.js +83 -58
  23. package/dist/demo.compiled.map +4 -4
  24. package/dist/demo.css +1 -1
  25. package/dist/demo.css.map +1 -1
  26. package/dist/deps.js +16 -9
  27. package/dist/locales.js +32 -32
  28. package/dist/receiver.compiled.debug.js +23 -19
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +23 -19
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +173 -26
  33. package/dist/shaka-player.compiled.debug.d.ts +173 -26
  34. package/dist/shaka-player.compiled.debug.externs.js +161 -31
  35. package/dist/shaka-player.compiled.debug.js +464 -401
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +161 -31
  38. package/dist/shaka-player.compiled.js +897 -862
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +173 -26
  41. package/dist/shaka-player.ui.debug.d.ts +173 -26
  42. package/dist/shaka-player.ui.debug.externs.js +161 -31
  43. package/dist/shaka-player.ui.debug.js +488 -422
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +161 -31
  46. package/dist/shaka-player.ui.js +1053 -1018
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/lcevc-architecture.png +0 -0
  49. package/docs/design/lcevc-demo.png +0 -0
  50. package/docs/design/lcevc-integration.md +72 -0
  51. package/docs/tutorials/fairplay.md +7 -0
  52. package/docs/tutorials/faq.md +1 -1
  53. package/docs/tutorials/ui.md +7 -7
  54. package/docs/tutorials/upgrade.md +6 -0
  55. package/externs/ima.js +13 -1
  56. package/externs/lcevc.js +81 -0
  57. package/externs/mux.js +1 -42
  58. package/externs/shaka/ads.js +11 -1
  59. package/externs/shaka/manifest_parser.js +5 -1
  60. package/externs/shaka/player.js +178 -15
  61. package/lib/abr/simple_abr_manager.js +7 -0
  62. package/lib/ads/ad_manager.js +0 -8
  63. package/lib/ads/client_side_ad.js +15 -0
  64. package/lib/ads/client_side_ad_manager.js +21 -8
  65. package/lib/ads/server_side_ad.js +15 -0
  66. package/lib/ads/server_side_ad_manager.js +3 -3
  67. package/lib/cast/cast_utils.js +2 -0
  68. package/lib/cea/dummy_cea_parser.js +28 -0
  69. package/lib/cea/i_cea_parser.js +14 -2
  70. package/lib/cea/mp4_cea_parser.js +87 -5
  71. package/lib/config/auto_show_text.js +33 -0
  72. package/lib/dash/dash_parser.js +1 -1
  73. package/lib/hls/hls_parser.js +790 -374
  74. package/lib/lcevc/lcevc_dil.js +152 -0
  75. package/lib/media/closed_caption_parser.js +9 -6
  76. package/lib/media/drm_engine.js +14 -6
  77. package/lib/media/media_source_engine.js +106 -26
  78. package/lib/media/presentation_timeline.js +26 -11
  79. package/lib/media/segment_reference.js +49 -1
  80. package/lib/media/streaming_engine.js +142 -12
  81. package/lib/media/transmuxer.js +73 -18
  82. package/lib/offline/storage.js +1 -0
  83. package/lib/player.js +454 -75
  84. package/lib/polyfill/patchedmediakeys_apple.js +55 -1
  85. package/lib/text/ui_text_displayer.js +5 -5
  86. package/lib/text/vtt_text_parser.js +35 -1
  87. package/lib/util/dom_utils.js +0 -1
  88. package/lib/util/error.js +17 -0
  89. package/lib/util/fake_event.js +2 -0
  90. package/lib/util/id3_utils.js +300 -0
  91. package/lib/util/mime_utils.js +3 -6
  92. package/lib/util/mp4_box_parsers.js +15 -5
  93. package/lib/util/platform.js +9 -1
  94. package/lib/util/player_configuration.js +23 -1
  95. package/lib/util/ts_parser.js +499 -0
  96. package/maintained-branches.md +2 -2
  97. package/package.json +4 -1
  98. package/roadmap.md +5 -3
  99. package/test/ads/ad_manager_unit.js +180 -29
  100. package/test/cast/cast_utils_unit.js +3 -1
  101. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  102. package/test/dash/dash_parser_live_unit.js +1 -0
  103. package/test/dash/dash_parser_manifest_unit.js +93 -1
  104. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  105. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  106. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  107. package/test/demo/demo_unit.js +2 -1
  108. package/test/hls/hls_live_unit.js +253 -19
  109. package/test/hls/hls_parser_unit.js +500 -12
  110. package/test/media/closed_caption_parser_unit.js +2 -1
  111. package/test/media/drm_engine_integration.js +3 -1
  112. package/test/media/media_source_engine_integration.js +71 -6
  113. package/test/media/media_source_engine_unit.js +10 -5
  114. package/test/media/streaming_engine_integration.js +4 -1
  115. package/test/media/streaming_engine_unit.js +144 -1
  116. package/test/media/transmuxer_integration.js +21 -10
  117. package/test/player_integration.js +224 -82
  118. package/test/player_unit.js +5 -2
  119. package/test/polyfill/patchedmediakeys_apple_unit.js +124 -0
  120. package/test/test/assets/id3-metadata.aac +0 -0
  121. package/test/test/assets/id3-metadata.ts +0 -0
  122. package/test/test/assets/thumbnails-sprites.vtt +10 -0
  123. package/test/test/assets/thumbnails.vtt +10 -0
  124. package/test/test/util/dash_parser_util.js +2 -0
  125. package/test/test/util/fake_ad.js +21 -0
  126. package/test/test/util/fake_media_source_engine.js +7 -0
  127. package/test/test/util/id3_generator.js +100 -0
  128. package/test/test/util/manifest_generator.js +3 -6
  129. package/test/test/util/simple_fakes.js +4 -0
  130. package/test/test/util/stream_generator.js +38 -0
  131. package/test/test/util/test_scheme.js +43 -14
  132. package/test/text/vtt_text_parser_unit.js +11 -0
  133. package/test/ui/text_displayer_layout_unit.js +14 -0
  134. package/test/util/id3_utils_unit.js +116 -0
  135. package/test/util/ts_parser_unit.js +77 -0
  136. package/ui/less/containers.less +7 -0
  137. package/ui/ui.js +43 -4
@@ -27,14 +27,33 @@ 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) {
33
34
  if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
34
35
  // No HTML5 video or no prefixed EME.
35
36
  return;
36
37
  }
37
38
 
39
+ if (enableUninstall) {
40
+ this.enableUninstall = true;
41
+ this.originalHTMLMediaElementPrototypeMediaKeys =
42
+ /** @type {!Object} */ (
43
+ Object.getOwnPropertyDescriptor(
44
+ // eslint-disable-next-line no-restricted-syntax
45
+ HTMLMediaElement.prototype, 'mediaKeys',
46
+ )
47
+ );
48
+ // eslint-disable-next-line no-restricted-syntax
49
+ this.originalHTMLMediaElementPrototypeSetMediaKeys = HTMLMediaElement
50
+ .prototype.setMediaKeys;
51
+ this.originalWindowMediaKeys = window.MediaKeys;
52
+ this.originalWindowMediaKeySystemAccess = window.MediaKeySystemAccess;
53
+ this.originalNavigatorRequestMediaKeySystemAccess = navigator
54
+ .requestMediaKeySystemAccess;
55
+ }
56
+
38
57
  shaka.log.info('Using Apple-prefixed EME');
39
58
 
40
59
  // Alias
@@ -59,6 +78,41 @@ shaka.polyfill.PatchedMediaKeysApple = class {
59
78
  window.shakaMediaKeysPolyfill = true;
60
79
  }
61
80
 
81
+ /**
82
+ * Uninstalls the polyfill if needed and enabled.
83
+ * @export
84
+ */
85
+ static uninstall() {
86
+ if (!this.enableUninstall) {
87
+ return;
88
+ }
89
+
90
+ shaka.log.info('Un-installing Apple-prefixed EME');
91
+
92
+ this.enableUninstall = false;
93
+ Object.defineProperty(
94
+ // eslint-disable-next-line no-restricted-syntax
95
+ HTMLMediaElement.prototype,
96
+ 'mediaKeys',
97
+ this.originalHTMLMediaElementPrototypeMediaKeys,
98
+ );
99
+ // eslint-disable-next-line no-restricted-syntax
100
+ HTMLMediaElement.prototype.setMediaKeys = this
101
+ .originalHTMLMediaElementPrototypeSetMediaKeys;
102
+ window.MediaKeys = this.originalWindowMediaKeys;
103
+ window.MediaKeySystemAccess = this.originalWindowMediaKeySystemAccess;
104
+ navigator.requestMediaKeySystemAccess = this
105
+ .originalNavigatorRequestMediaKeySystemAccess;
106
+
107
+ this.originalWindowMediaKeys = null;
108
+ this.originalWindowMediaKeySystemAccess = null;
109
+ this.originalHTMLMediaElementPrototypeSetMediaKeys = null;
110
+ this.originalNavigatorRequestMediaKeySystemAccess = null;
111
+ this.originalHTMLMediaElementPrototypeMediaKeys = null;
112
+
113
+ window.shakaMediaKeysPolyfill = false;
114
+ }
115
+
62
116
  /**
63
117
  * An implementation of navigator.requestMediaKeySystemAccess.
64
118
  * Retrieves a MediaKeySystemAccess object.
@@ -578,7 +578,7 @@ shaka.text.UITextDisplayer = class {
578
578
  // (for vertical growing left) is aligned at the line.
579
579
  // TODO: Implement line alignment with line number.
580
580
  // TODO: Implement lineAlignment of 'CENTER'.
581
- if (cue.line) {
581
+ if (cue.line != null) {
582
582
  if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
583
583
  // When setting absolute positioning, you need to set x/y/width/height
584
584
  // so the element is positioned correctly. Set these as default and
@@ -591,21 +591,21 @@ shaka.text.UITextDisplayer = class {
591
591
  if (cue.lineAlign == Cue.lineAlign.START) {
592
592
  style.top = cue.line + '%';
593
593
  } else if (cue.lineAlign == Cue.lineAlign.END) {
594
- style.bottom = cue.line + '%';
594
+ style.bottom = (100 - cue.line) + '%';
595
595
  }
596
596
  } else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
597
597
  style.height = '100%';
598
598
  if (cue.lineAlign == Cue.lineAlign.START) {
599
599
  style.left = cue.line + '%';
600
600
  } else if (cue.lineAlign == Cue.lineAlign.END) {
601
- style.right = cue.line + '%';
601
+ style.right = (100 - cue.line) + '%';
602
602
  }
603
603
  } else {
604
604
  style.height = '100%';
605
605
  if (cue.lineAlign == Cue.lineAlign.START) {
606
606
  style.right = cue.line + '%';
607
607
  } else if (cue.lineAlign == Cue.lineAlign.END) {
608
- style.left = cue.line + '%';
608
+ style.left = (100 - cue.line) + '%';
609
609
  }
610
610
  }
611
611
  }
@@ -615,7 +615,7 @@ shaka.text.UITextDisplayer = class {
615
615
 
616
616
  // The position defines the indent of the text container in the
617
617
  // direction defined by the writing direction.
618
- if (cue.position) {
618
+ if (cue.position != null) {
619
619
  if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
620
620
  style.paddingLeft = cue.position;
621
621
  } else {
@@ -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.
@@ -18,12 +18,13 @@ shaka.util.Mp4BoxParsers = class {
18
18
  static parseTFHD(reader, flags) {
19
19
  let defaultSampleDuration = null;
20
20
  let defaultSampleSize = null;
21
+ let baseDataOffset = null;
21
22
 
22
23
  const trackId = reader.readUint32(); // Read "track_ID"
23
24
 
24
25
  // Skip "base_data_offset" if present.
25
26
  if (flags & 0x000001) {
26
- reader.skip(8);
27
+ baseDataOffset = reader.readUint64();
27
28
  }
28
29
 
29
30
  // Skip "sample_description_index" if present.
@@ -45,6 +46,7 @@ shaka.util.Mp4BoxParsers = class {
45
46
  trackId,
46
47
  defaultSampleDuration,
47
48
  defaultSampleSize,
49
+ baseDataOffset,
48
50
  };
49
51
  }
50
52
 
@@ -112,10 +114,11 @@ shaka.util.Mp4BoxParsers = class {
112
114
  static parseTRUN(reader, version, flags) {
113
115
  const sampleCount = reader.readUint32();
114
116
  const sampleData = [];
117
+ let dataOffset = null;
115
118
 
116
- // Skip "data_offset" if present.
119
+ // "data_offset"
117
120
  if (flags & 0x000001) {
118
- reader.skip(4);
121
+ dataOffset = reader.readUint32();
119
122
  }
120
123
 
121
124
  // Skip "first_sample_flags" if present.
@@ -159,6 +162,7 @@ shaka.util.Mp4BoxParsers = class {
159
162
  return {
160
163
  sampleCount,
161
164
  sampleData,
165
+ dataOffset,
162
166
  };
163
167
  }
164
168
 
@@ -191,7 +195,8 @@ shaka.util.Mp4BoxParsers = class {
191
195
  * @typedef {{
192
196
  * trackId: number,
193
197
  * defaultSampleDuration: ?number,
194
- * defaultSampleSize: ?number
198
+ * defaultSampleSize: ?number,
199
+ * baseDataOffset: ?number
195
200
  * }}
196
201
  *
197
202
  * @property {number} trackId
@@ -203,6 +208,8 @@ shaka.util.Mp4BoxParsers = class {
203
208
  * @property {?number} defaultSampleSize
204
209
  * If specified via flags, this overrides the default sample
205
210
  * size in the Track Extends Box for this fragment
211
+ * @property {?number} baseDataOffset
212
+ * If specified via flags, this indicate the base data offset
206
213
  *
207
214
  * @exportDoc
208
215
  */
@@ -252,13 +259,16 @@ shaka.util.ParsedTREXBox;
252
259
  /**
253
260
  * @typedef {{
254
261
  * sampleCount: number,
255
- * sampleData: !Array.<shaka.util.ParsedTRUNSample>
262
+ * sampleData: !Array.<shaka.util.ParsedTRUNSample>,
263
+ * dataOffset: ?number
256
264
  * }}
257
265
  *
258
266
  * @property {number} sampleCount
259
267
  * As per the spec: the number of samples being added in this run;
260
268
  * @property {!Array.<shaka.util.ParsedTRUNSample>} sampleData
261
269
  * An array of size <sampleCount> containing data for each sample
270
+ * @property {?number} dataOffset
271
+ * If specified via flags, this indicate the offset of the sample in bytes.
262
272
  *
263
273
  * @exportDoc
264
274
  */
@@ -174,7 +174,7 @@ shaka.util.Platform = class {
174
174
  * @return {?number} A major version number or null if not Chromium-based.
175
175
  */
176
176
  static chromeVersion() {
177
- if (!shaka.util.Platform.userAgentContains_('Chrome')) {
177
+ if (!shaka.util.Platform.isChrome()) {
178
178
  return null;
179
179
  }
180
180
 
@@ -213,6 +213,7 @@ shaka.util.Platform = class {
213
213
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
214
214
  !shaka.util.Platform.isTizen() &&
215
215
  !shaka.util.Platform.isEOS() &&
216
+ !shaka.util.Platform.isVirginMedia() &&
216
217
  !shaka.util.Platform.isPS4() &&
217
218
  !shaka.util.Platform.isAmazonFireTV();
218
219
  }
@@ -237,6 +238,13 @@ shaka.util.Platform = class {
237
238
  return shaka.util.Platform.userAgentContains_('PlayStation 4');
238
239
  }
239
240
 
241
+ /**
242
+ * Check if the current platform is Virgin Media device.
243
+ */
244
+ static isVirginMedia() {
245
+ return shaka.util.Platform.userAgentContains_('VirginMedia');
246
+ }
247
+
240
248
  /**
241
249
  * Check if the current platform is Amazon Fire TV.
242
250
  * https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html