shaka-player 4.2.3 → 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.
- package/.github/workflows/sync-labels.yaml +1 -1
- package/.github/workflows/update-issues.yaml +1 -1
- package/AUTHORS +5 -0
- package/CHANGELOG.md +85 -39
- package/CONTRIBUTORS +5 -0
- package/README.md +14 -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 +83 -58
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +83 -58
- package/dist/demo.compiled.map +4 -4
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +18 -11
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +23 -19
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +23 -19
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +178 -27
- package/dist/shaka-player.compiled.debug.d.ts +178 -27
- package/dist/shaka-player.compiled.debug.externs.js +163 -32
- package/dist/shaka-player.compiled.debug.js +488 -422
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +163 -32
- package/dist/shaka-player.compiled.js +895 -857
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +178 -27
- package/dist/shaka-player.ui.debug.d.ts +178 -27
- package/dist/shaka-player.ui.debug.externs.js +163 -32
- package/dist/shaka-player.ui.debug.js +519 -450
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +163 -32
- package/dist/shaka-player.ui.js +1053 -1015
- 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 +4 -42
- package/externs/shaka/ads.js +11 -1
- package/externs/shaka/manifest_parser.js +5 -1
- 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_proxy.js +2 -1
- package/lib/cast/cast_receiver.js +19 -5
- package/lib/cast/cast_sender.js +2 -1
- package/lib/cast/cast_utils.js +18 -6
- 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 +87 -5
- package/lib/config/auto_show_text.js +33 -0
- package/lib/dash/dash_parser.js +1 -1
- package/lib/hls/hls_parser.js +799 -376
- 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_engine.js +112 -26
- package/lib/media/playhead.js +23 -1
- package/lib/media/presentation_timeline.js +26 -11
- package/lib/media/segment_reference.js +49 -1
- package/lib/media/streaming_engine.js +145 -24
- package/lib/media/transmuxer.js +79 -18
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +477 -85
- package/lib/polyfill/patchedmediakeys_apple.js +55 -1
- package/lib/text/ui_text_displayer.js +5 -5
- 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/manifest_parser_utils.js +4 -0
- package/lib/util/mime_utils.js +3 -6
- package/lib/util/mp4_box_parsers.js +15 -5
- package/lib/util/platform.js +9 -1
- package/lib/util/player_configuration.js +23 -1
- package/lib/util/ts_parser.js +499 -0
- package/maintained-branches.md +2 -2
- package/package.json +4 -1
- package/roadmap.md +27 -7
- package/test/ads/ad_manager_unit.js +180 -29
- package/test/cast/cast_receiver_integration.js +97 -16
- package/test/cast/cast_receiver_unit.js +17 -12
- package/test/cast/cast_utils_unit.js +4 -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 +500 -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 +102 -6
- package/test/media/media_source_engine_unit.js +10 -5
- package/test/media/playhead_unit.js +41 -0
- package/test/media/streaming_engine_integration.js +17 -1
- package/test/media/streaming_engine_unit.js +180 -24
- package/test/media/transmuxer_integration.js +21 -10
- package/test/player_integration.js +224 -82
- package/test/player_unit.js +92 -2
- package/test/polyfill/patchedmediakeys_apple_unit.js +124 -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 +7 -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/ui/text_displayer_layout_unit.js +14 -0
- package/test/util/id3_utils_unit.js +116 -0
- package/test/util/ts_parser_unit.js +77 -0
- package/ui/controls.js +4 -1
- package/ui/less/containers.less +7 -0
- package/ui/settings_menu.js +11 -2
- 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 =
|
|
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;
|
|
@@ -168,6 +168,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
|
|
|
168
168
|
/^hvc/,
|
|
169
169
|
/^vp0?[89]/,
|
|
170
170
|
/^av01/,
|
|
171
|
+
/^dvhe/,
|
|
171
172
|
];
|
|
172
173
|
|
|
173
174
|
|
|
@@ -183,6 +184,9 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
|
|
|
183
184
|
/^flac$/,
|
|
184
185
|
/^mp4a/,
|
|
185
186
|
/^[ae]c-3$/,
|
|
187
|
+
/^ac-4$/,
|
|
188
|
+
/^dts[cx]$/, // DTS Surround Sound
|
|
189
|
+
/^[du]dts$/, // DTS Surround Sound
|
|
186
190
|
];
|
|
187
191
|
|
|
188
192
|
|
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.
|
|
@@ -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.
|
|
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
|
-
//
|
|
119
|
+
// "data_offset"
|
|
117
120
|
if (flags & 0x000001) {
|
|
118
|
-
reader.
|
|
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
|
*/
|
package/lib/util/platform.js
CHANGED
|
@@ -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.
|
|
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
|