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.
- package/.github/workflows/build-and-test.yaml +7 -0
- package/.github/workflows/sync-labels.yaml +1 -1
- package/.github/workflows/update-issues.yaml +1 -1
- package/AUTHORS +4 -0
- package/CHANGELOG.md +114 -74
- package/CONTRIBUTORS +4 -0
- package/README.md +14 -0
- package/conditional/dummy_cast_proxy.js +120 -0
- package/demo/common/assets.js +58 -0
- package/demo/common/message_ids.js +22 -1
- package/demo/config.js +89 -5
- package/demo/demo.less +7 -1
- package/demo/index.html +8 -1
- package/demo/locales/en.json +21 -1
- package/demo/locales/source.json +82 -2
- package/demo/main.js +57 -1
- package/demo/search.js +2 -0
- package/demo/visualizer.js +337 -0
- package/demo/visualizer_button.js +103 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +107 -88
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +107 -88
- package/dist/demo.compiled.map +4 -4
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +21 -13
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +75 -77
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +75 -77
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +181 -27
- package/dist/shaka-player.compiled.debug.d.ts +181 -27
- package/dist/shaka-player.compiled.debug.externs.js +169 -31
- package/dist/shaka-player.compiled.debug.js +510 -450
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +169 -31
- package/dist/shaka-player.compiled.js +912 -874
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +181 -27
- package/dist/shaka-player.ui.debug.d.ts +181 -27
- package/dist/shaka-player.ui.debug.externs.js +169 -31
- package/dist/shaka-player.ui.debug.js +533 -471
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +169 -31
- package/dist/shaka-player.ui.js +1073 -1036
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/lcevc-architecture.png +0 -0
- package/docs/design/lcevc-demo.png +0 -0
- package/docs/design/lcevc-integration.md +72 -0
- package/docs/tutorials/fairplay.md +7 -0
- package/docs/tutorials/faq.md +1 -1
- package/docs/tutorials/ui.md +7 -7
- package/docs/tutorials/upgrade.md +6 -0
- package/externs/ima.js +13 -1
- package/externs/lcevc.js +81 -0
- package/externs/mux.js +1 -42
- package/externs/shaka/ads.js +11 -1
- package/externs/shaka/manifest_parser.js +5 -1
- package/externs/shaka/mp4_parser.js +3 -0
- package/externs/shaka/player.js +178 -15
- package/lib/abr/simple_abr_manager.js +7 -0
- package/lib/ads/ad_manager.js +0 -8
- package/lib/ads/client_side_ad.js +15 -0
- package/lib/ads/client_side_ad_manager.js +21 -8
- package/lib/ads/server_side_ad.js +15 -0
- package/lib/ads/server_side_ad_manager.js +3 -3
- package/lib/cast/cast_utils.js +2 -0
- package/lib/cea/dummy_cea_parser.js +28 -0
- package/lib/cea/i_cea_parser.js +14 -2
- package/lib/cea/mp4_cea_parser.js +108 -5
- package/lib/config/auto_show_text.js +33 -0
- package/lib/dash/dash_parser.js +1 -1
- package/lib/hls/hls_parser.js +808 -384
- package/lib/lcevc/lcevc_dil.js +152 -0
- package/lib/media/closed_caption_parser.js +9 -6
- package/lib/media/drm_engine.js +14 -6
- package/lib/media/media_source_capabilities.js +36 -0
- package/lib/media/media_source_engine.js +113 -28
- package/lib/media/presentation_timeline.js +26 -11
- package/lib/media/segment_reference.js +49 -1
- package/lib/media/streaming_engine.js +142 -12
- package/lib/media/transmuxer.js +78 -21
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +456 -77
- package/lib/polyfill/media_capabilities.js +9 -5
- package/lib/polyfill/patchedmediakeys_apple.js +63 -4
- package/lib/text/vtt_text_parser.js +35 -1
- package/lib/util/dom_utils.js +0 -1
- package/lib/util/error.js +17 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/id3_utils.js +300 -0
- package/lib/util/mime_utils.js +3 -6
- package/lib/util/mp4_box_parsers.js +39 -5
- package/lib/util/mp4_parser.js +35 -0
- package/lib/util/player_configuration.js +23 -1
- package/lib/util/stream_utils.js +5 -3
- package/lib/util/ts_parser.js +499 -0
- package/maintained-branches.md +2 -2
- package/package.json +7 -4
- package/roadmap.md +5 -3
- package/test/ads/ad_manager_unit.js +182 -29
- package/test/cast/cast_utils_unit.js +3 -1
- package/test/dash/dash_parser_content_protection_unit.js +1 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +93 -1
- package/test/dash/dash_parser_segment_base_unit.js +1 -0
- package/test/dash/dash_parser_segment_list_unit.js +1 -0
- package/test/dash/dash_parser_segment_template_unit.js +1 -0
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +253 -19
- package/test/hls/hls_parser_unit.js +631 -12
- package/test/media/closed_caption_parser_unit.js +2 -1
- package/test/media/drm_engine_integration.js +3 -1
- package/test/media/media_source_engine_integration.js +71 -6
- package/test/media/media_source_engine_unit.js +10 -5
- package/test/media/streaming_engine_integration.js +4 -1
- package/test/media/streaming_engine_unit.js +144 -1
- package/test/media/transmuxer_integration.js +21 -10
- package/test/player_integration.js +224 -82
- package/test/player_unit.js +5 -2
- package/test/polyfill/media_capabilities_unit.js +21 -23
- package/test/polyfill/patchedmediakeys_apple_unit.js +130 -0
- package/test/test/assets/id3-metadata.aac +0 -0
- package/test/test/assets/id3-metadata.ts +0 -0
- package/test/test/assets/thumbnails-sprites.vtt +10 -0
- package/test/test/assets/thumbnails.vtt +10 -0
- package/test/test/util/dash_parser_util.js +2 -0
- package/test/test/util/fake_ad.js +21 -0
- package/test/test/util/fake_media_source_engine.js +7 -0
- package/test/test/util/id3_generator.js +100 -0
- package/test/test/util/manifest_generator.js +3 -6
- package/test/test/util/simple_fakes.js +4 -0
- package/test/test/util/stream_generator.js +38 -0
- package/test/test/util/test_scheme.js +43 -14
- package/test/text/vtt_text_parser_unit.js +11 -0
- package/test/util/id3_utils_unit.js +116 -0
- package/test/util/ts_parser_unit.js +77 -0
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/less/containers.less +7 -0
- 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 '
|
|
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 =
|
|
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 =
|
|
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
|
-
'
|
|
235
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
+
'<': '<',
|
|
861
|
+
'>': '>',
|
|
862
|
+
'"': '"',
|
|
863
|
+
''': '\'',
|
|
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
|
/**
|
package/lib/util/dom_utils.js
CHANGED
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,
|
package/lib/util/fake_event.js
CHANGED
|
@@ -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;
|
package/lib/util/mime_utils.js
CHANGED
|
@@ -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 (
|
|
51
|
-
|
|
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.
|
|
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
|
-
//
|
|
120
|
+
// "data_offset"
|
|
117
121
|
if (flags & 0x000001) {
|
|
118
|
-
reader.
|
|
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;
|