shaka-player 3.2.0 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build_and_test.yaml +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/AUTHORS +3 -0
- package/CHANGELOG.md +182 -1
- package/CONTRIBUTORS +3 -0
- package/README.md +4 -4
- package/build/checkversion.py +1 -1
- package/build/generateExterns.js +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/build/test.py +6 -0
- package/demo/common/assets.js +1 -1
- package/demo/index.html +1 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/deps.js +8 -8
- package/dist/locales.js +37 -37
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/shaka-player.compiled.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.externs.js +226 -14
- package/dist/shaka-player.compiled.debug.js +424 -399
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +226 -14
- package/dist/shaka-player.compiled.js +826 -812
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +197 -16
- package/dist/shaka-player.ui.debug.d.ts +197 -16
- package/dist/shaka-player.ui.debug.externs.js +230 -27
- package/dist/shaka-player.ui.debug.js +466 -442
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +230 -27
- package/dist/shaka-player.ui.js +946 -932
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +14 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +25 -0
- package/externs/shaka/player.js +8 -0
- package/externs/shaka/text.js +10 -0
- package/karma.conf.js +20 -15
- package/lib/ads/ad_manager.js +46 -26
- package/lib/ads/client_side_ad_manager.js +82 -88
- package/lib/ads/server_side_ad_manager.js +45 -13
- package/lib/cast/cast_proxy.js +6 -2
- package/lib/cast/cast_receiver.js +5 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cea/sei_processor.js +24 -17
- package/lib/dash/dash_parser.js +131 -24
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +74 -17
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/closed_caption_parser.js +3 -1
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/drm_engine.js +20 -1
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/playhead.js +1 -1
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/segment_index.js +2 -1
- package/lib/media/segment_reference.js +19 -1
- package/lib/media/streaming_engine.js +8 -4
- package/lib/net/networking_engine.js +6 -1
- package/lib/offline/download_manager.js +1 -2
- package/lib/offline/storage.js +8 -9
- package/lib/player.js +129 -59
- package/lib/polyfill/aria.js +12 -2
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/media_capabilities.js +73 -21
- package/lib/polyfill/mediasource.js +8 -0
- package/lib/polyfill/orientation.js +3 -1
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/storage_estimate.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/cue.js +6 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +135 -87
- package/lib/text/ui_text_displayer.js +213 -80
- package/lib/text/vtt_text_parser.js +4 -2
- package/lib/text/web_vtt_generator.js +21 -12
- package/lib/util/fake_event.js +53 -7
- package/lib/util/fake_event_target.js +21 -1
- package/lib/util/mp4_box_parsers.js +1 -1
- package/lib/util/periods.js +4 -1
- package/lib/util/platform.js +13 -1
- package/lib/util/stream_utils.js +105 -13
- package/lib/util/xml_utils.js +50 -12
- package/package.json +3 -6
- package/roadmap.md +24 -22
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -0
- package/test/dash/dash_parser_live_unit.js +43 -0
- package/test/dash/dash_parser_manifest_unit.js +222 -3
- package/test/hls/hls_parser_unit.js +79 -1
- package/test/media/closed_caption_parser_unit.js +20 -0
- package/test/media/drm_engine_integration.js +86 -2
- package/test/media/drm_engine_unit.js +57 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +145 -0
- package/test/player_integration.js +79 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +14 -4
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/assets/empty_caption_video_init.mp4 +0 -0
- package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
- package/test/test/util/fake_ad_manager.js +11 -1
- package/test/test/util/ttml_utils.js +81 -0
- package/test/test/util/util.js +10 -7
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +542 -220
- package/test/text/ui_text_displayer_unit.js +93 -20
- package/test/text/vtt_text_parser_unit.js +17 -1
- package/test/text/web_vtt_generator_unit.js +58 -3
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +21 -8
- package/test/util/mp4_box_parsers_unit.js +28 -0
- package/test/util/stream_utils_unit.js +60 -3
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +11 -6
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +6 -7
- package/ui/locales/ru.json +6 -6
- package/ui/localization.js +40 -87
- package/ui/pip_button.js +2 -3
- package/ui/seek_bar.js +1 -2
- package/ui/text_selection.js +1 -0
|
@@ -15,10 +15,12 @@ goog.require('shaka.polyfill');
|
|
|
15
15
|
* @summary A polyfill for systems that do not implement screen.orientation.
|
|
16
16
|
* For now, this only handles systems that implement the deprecated
|
|
17
17
|
* window.orientation feature... e.g. iPad.
|
|
18
|
+
* @export
|
|
18
19
|
*/
|
|
19
20
|
shaka.polyfill.Orientation = class {
|
|
20
21
|
/**
|
|
21
22
|
* Install the polyfill if needed.
|
|
23
|
+
* @export
|
|
22
24
|
*/
|
|
23
25
|
static install() {
|
|
24
26
|
if (screen.orientation) {
|
|
@@ -90,7 +92,7 @@ class extends shaka.util.FakeEventTarget {
|
|
|
90
92
|
|
|
91
93
|
/** Dispatch a 'change' event. */
|
|
92
94
|
dispatchChangeEvent() {
|
|
93
|
-
const event = new shaka.util.FakeEvent('change'
|
|
95
|
+
const event = new shaka.util.FakeEvent('change');
|
|
94
96
|
this.dispatchEvent(event);
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -22,10 +22,12 @@ goog.require('shaka.util.StringUtils');
|
|
|
22
22
|
/**
|
|
23
23
|
* @summary A polyfill to implement modern, standardized EME on top of Apple's
|
|
24
24
|
* prefixed EME in Safari.
|
|
25
|
+
* @export
|
|
25
26
|
*/
|
|
26
27
|
shaka.polyfill.PatchedMediaKeysApple = class {
|
|
27
28
|
/**
|
|
28
29
|
* Installs the polyfill if needed.
|
|
30
|
+
* @export
|
|
29
31
|
*/
|
|
30
32
|
static install() {
|
|
31
33
|
if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
|
|
@@ -565,10 +567,10 @@ class extends shaka.util.FakeEventTarget {
|
|
|
565
567
|
|
|
566
568
|
const isNew = this.keyStatuses.getStatus() == undefined;
|
|
567
569
|
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
570
|
+
const data = new Map()
|
|
571
|
+
.set('messageType', isNew ? 'license-request' : 'license-renewal')
|
|
572
|
+
.set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
|
|
573
|
+
const event2 = new shaka.util.FakeEvent('message', data);
|
|
572
574
|
|
|
573
575
|
this.dispatchEvent(event2);
|
|
574
576
|
}
|
|
@@ -24,10 +24,12 @@ goog.require('shaka.util.PublicPromise');
|
|
|
24
24
|
* {@link https://bit.ly/EmeMar15 EME draft 12 March 2015}
|
|
25
25
|
* on top of ms-prefixed
|
|
26
26
|
* {@link https://www.w3.org/TR/2014/WD-encrypted-media-20140218/ EME v20140218}
|
|
27
|
+
* @export
|
|
27
28
|
*/
|
|
28
29
|
shaka.polyfill.PatchedMediaKeysMs = class {
|
|
29
30
|
/**
|
|
30
31
|
* Installs the polyfill if needed.
|
|
32
|
+
* @export
|
|
31
33
|
*/
|
|
32
34
|
static install() {
|
|
33
35
|
if (!window.HTMLVideoElement || !window.MSMediaKeys ||
|
|
@@ -510,10 +512,10 @@ class extends shaka.util.FakeEventTarget {
|
|
|
510
512
|
|
|
511
513
|
const isNew = this.keyStatuses.getStatus() == undefined;
|
|
512
514
|
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
515
|
+
const data = new Map()
|
|
516
|
+
.set('messageType', isNew ? 'license-request' : 'license-renewal')
|
|
517
|
+
.set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
|
|
518
|
+
const event2 = new shaka.util.FakeEvent('message', data);
|
|
517
519
|
|
|
518
520
|
this.dispatchEvent(event2);
|
|
519
521
|
}
|
|
@@ -16,10 +16,12 @@ goog.require('shaka.polyfill');
|
|
|
16
16
|
* {@link https://bit.ly/EmeMar15 EME draft 12 March 2015} on browsers without
|
|
17
17
|
* EME.
|
|
18
18
|
* All methods will fail.
|
|
19
|
+
* @export
|
|
19
20
|
*/
|
|
20
21
|
shaka.polyfill.PatchedMediaKeysNop = class {
|
|
21
22
|
/**
|
|
22
23
|
* Installs the polyfill if needed.
|
|
24
|
+
* @export
|
|
23
25
|
*/
|
|
24
26
|
static install() {
|
|
25
27
|
if (!window.HTMLVideoElement ||
|
|
@@ -24,10 +24,12 @@ goog.require('shaka.util.Uint8ArrayUtils');
|
|
|
24
24
|
* @summary A polyfill to implement
|
|
25
25
|
* {@link https://bit.ly/EmeMar15 EME draft 12 March 2015} on top of
|
|
26
26
|
* webkit-prefixed {@link https://bit.ly/Eme01b EME v0.1b}.
|
|
27
|
+
* @export
|
|
27
28
|
*/
|
|
28
29
|
shaka.polyfill.PatchedMediaKeysWebkit = class {
|
|
29
30
|
/**
|
|
30
31
|
* Installs the polyfill if needed.
|
|
32
|
+
* @export
|
|
31
33
|
*/
|
|
32
34
|
static install() {
|
|
33
35
|
// Alias.
|
|
@@ -449,10 +451,10 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeys = class {
|
|
|
449
451
|
|
|
450
452
|
const isNew = session.keyStatuses.getStatus() == undefined;
|
|
451
453
|
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
454
|
+
const data = new Map()
|
|
455
|
+
.set('messageType', isNew ? 'licenserequest' : 'licenserenewal')
|
|
456
|
+
.set('message', event.message);
|
|
457
|
+
const event2 = new shaka.util.FakeEvent('message', data);
|
|
456
458
|
|
|
457
459
|
session.generated();
|
|
458
460
|
session.dispatchEvent(event2);
|
|
@@ -12,10 +12,12 @@ goog.require('shaka.polyfill');
|
|
|
12
12
|
/**
|
|
13
13
|
* @summary A polyfill to provide PiP support in Safari.
|
|
14
14
|
* Note that Safari only supports PiP on video elements, not audio.
|
|
15
|
+
* @export
|
|
15
16
|
*/
|
|
16
17
|
shaka.polyfill.PiPWebkit = class {
|
|
17
18
|
/**
|
|
18
19
|
* Install the polyfill if needed.
|
|
20
|
+
* @export
|
|
19
21
|
*/
|
|
20
22
|
static install() {
|
|
21
23
|
if (!window.HTMLVideoElement) {
|
|
@@ -13,10 +13,12 @@ goog.require('shaka.polyfill');
|
|
|
13
13
|
* @summary A polyfill to provide navigator.storage.estimate in old
|
|
14
14
|
* webkit browsers.
|
|
15
15
|
* See: https://developers.google.com/web/updates/2017/08/estimating-available-storage-space#the-present
|
|
16
|
+
* @export
|
|
16
17
|
*/
|
|
17
18
|
shaka.polyfill.StorageEstimate = class {
|
|
18
19
|
/**
|
|
19
20
|
* Install the polyfill if needed.
|
|
21
|
+
* @export
|
|
20
22
|
*/
|
|
21
23
|
static install() {
|
|
22
24
|
if (navigator.storage && navigator.storage.estimate) {
|
|
@@ -12,10 +12,12 @@ goog.require('shaka.polyfill');
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @summary A polyfill to silence the play() Promise in HTML5 video.
|
|
15
|
+
* @export
|
|
15
16
|
*/
|
|
16
17
|
shaka.polyfill.VideoPlayPromise = class {
|
|
17
18
|
/**
|
|
18
19
|
* Install the polyfill if needed.
|
|
20
|
+
* @export
|
|
19
21
|
*/
|
|
20
22
|
static install() {
|
|
21
23
|
shaka.log.debug('VideoPlayPromise.install');
|
|
@@ -13,10 +13,12 @@ goog.require('shaka.polyfill');
|
|
|
13
13
|
* @summary A polyfill to provide MSE VideoPlaybackQuality metrics.
|
|
14
14
|
* Many browsers do not yet provide this API, and Chrome currently provides
|
|
15
15
|
* similar data through individual prefixed attributes on HTMLVideoElement.
|
|
16
|
+
* @export
|
|
16
17
|
*/
|
|
17
18
|
shaka.polyfill.VideoPlaybackQuality = class {
|
|
18
19
|
/**
|
|
19
20
|
* Install the polyfill if needed.
|
|
21
|
+
* @export
|
|
20
22
|
*/
|
|
21
23
|
static install() {
|
|
22
24
|
if (!window.HTMLVideoElement) {
|
package/lib/polyfill/vttcue.js
CHANGED
|
@@ -12,10 +12,12 @@ goog.require('shaka.polyfill');
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @summary A polyfill to provide VTTCue.
|
|
15
|
+
* @export
|
|
15
16
|
*/
|
|
16
17
|
shaka.polyfill.VTTCue = class {
|
|
17
18
|
/**
|
|
18
19
|
* Install the polyfill if needed.
|
|
20
|
+
* @export
|
|
19
21
|
*/
|
|
20
22
|
static install() {
|
|
21
23
|
if (window.VTTCue) {
|
package/lib/text/cue.js
CHANGED
|
@@ -88,6 +88,9 @@ shaka.text.Mp4TtmlParser = class {
|
|
|
88
88
|
|
|
89
89
|
shaka.text.TextEngine.registerParser(
|
|
90
90
|
'application/mp4; codecs="stpp"', () => new shaka.text.Mp4TtmlParser());
|
|
91
|
+
shaka.text.TextEngine.registerParser(
|
|
92
|
+
'application/mp4; codecs="stpp.ttml"',
|
|
93
|
+
() => new shaka.text.Mp4TtmlParser());
|
|
91
94
|
shaka.text.TextEngine.registerParser(
|
|
92
95
|
'application/mp4; codecs="stpp.ttml.im1t"',
|
|
93
96
|
() => new shaka.text.Mp4TtmlParser());
|
package/lib/text/text_engine.js
CHANGED
|
@@ -345,6 +345,19 @@ shaka.text.TextEngine = class {
|
|
|
345
345
|
return cues;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* @param {!shaka.text.Cue} cue the cue to apply the timestamp to recursively
|
|
350
|
+
* @param {number} videoTimestampOffset the timestamp offset of the video
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
applyVideoTimestampOffsetRecursive_(cue, videoTimestampOffset) {
|
|
354
|
+
cue.startTime += videoTimestampOffset;
|
|
355
|
+
cue.endTime += videoTimestampOffset;
|
|
356
|
+
for (const nested of cue.nestedCues) {
|
|
357
|
+
this.applyVideoTimestampOffsetRecursive_(nested, videoTimestampOffset);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
348
361
|
/**
|
|
349
362
|
* Store the closed captions in the text engine, and append the cues to the
|
|
350
363
|
* text displayer. This is a side-channel used for embedded text only.
|
|
@@ -373,8 +386,7 @@ shaka.text.TextEngine = class {
|
|
|
373
386
|
|
|
374
387
|
// Adjust CEA captions with respect to the timestamp offset of the video
|
|
375
388
|
// stream in which they were embedded.
|
|
376
|
-
cue
|
|
377
|
-
cue.endTime += videoTimestampOffset;
|
|
389
|
+
this.applyVideoTimestampOffsetRecursive_(cue, videoTimestampOffset);
|
|
378
390
|
|
|
379
391
|
const keepThisCue =
|
|
380
392
|
cue.startTime >= this.appendWindowStart_ &&
|
|
@@ -86,16 +86,17 @@ shaka.text.TtmlTextParser = class {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// Get the framerate, subFrameRate and frameRateMultiplier if applicable.
|
|
89
|
-
const frameRate = XmlUtils.
|
|
90
|
-
const subFrameRate = XmlUtils.
|
|
89
|
+
const frameRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRate');
|
|
90
|
+
const subFrameRate = XmlUtils.getAttributeNSList(
|
|
91
|
+
tt, ttpNs, 'subFrameRate');
|
|
91
92
|
const frameRateMultiplier =
|
|
92
|
-
XmlUtils.
|
|
93
|
-
const tickRate = XmlUtils.
|
|
93
|
+
XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRateMultiplier');
|
|
94
|
+
const tickRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'tickRate');
|
|
94
95
|
|
|
95
|
-
const cellResolution = XmlUtils.
|
|
96
|
+
const cellResolution = XmlUtils.getAttributeNSList(
|
|
96
97
|
tt, ttpNs, 'cellResolution');
|
|
97
98
|
const spaceStyle = tt.getAttribute('xml:space') || 'default';
|
|
98
|
-
const extent = XmlUtils.
|
|
99
|
+
const extent = XmlUtils.getAttributeNSList(tt, ttsNs, 'extent');
|
|
99
100
|
|
|
100
101
|
if (spaceStyle != 'default' && spaceStyle != 'preserve') {
|
|
101
102
|
throw new shaka.util.Error(
|
|
@@ -147,28 +148,15 @@ shaka.text.TtmlTextParser = class {
|
|
|
147
148
|
shaka.util.Error.Code.INVALID_TEXT_CUE,
|
|
148
149
|
'<span> can only be inside <p> in TTML');
|
|
149
150
|
}
|
|
151
|
+
}
|
|
150
152
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (cue) {
|
|
159
|
-
cues.push(cue);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
// Only used for parsing the background image
|
|
164
|
-
const cue = TtmlTextParser.parseCue_(
|
|
165
|
-
div, time.periodStart, rateInfo, metadataElements, styles,
|
|
166
|
-
regionElements, cueRegions, whitespaceTrim,
|
|
167
|
-
/* isNested= */ false, cellResolutionInfo);
|
|
168
|
-
if (cue) {
|
|
169
|
-
cues.push(cue);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
153
|
+
const cue = TtmlTextParser.parseCue_(
|
|
154
|
+
body, time.periodStart, rateInfo, metadataElements, styles,
|
|
155
|
+
regionElements, cueRegions, whitespaceTrim,
|
|
156
|
+
cellResolutionInfo, /* parentCueElement= */ null,
|
|
157
|
+
/* isContent= */ false);
|
|
158
|
+
if (cue) {
|
|
159
|
+
cues.push(cue);
|
|
172
160
|
}
|
|
173
161
|
}
|
|
174
162
|
|
|
@@ -186,20 +174,32 @@ shaka.text.TtmlTextParser = class {
|
|
|
186
174
|
* @param {!Array.<!Element>} regionElements
|
|
187
175
|
* @param {!Array.<!shaka.text.CueRegion>} cueRegions
|
|
188
176
|
* @param {boolean} whitespaceTrim
|
|
189
|
-
* @param {boolean} isNested
|
|
190
177
|
* @param {?{columns: number, rows: number}} cellResolution
|
|
178
|
+
* @param {?Element} parentCueElement
|
|
179
|
+
* @param {boolean} isContent
|
|
191
180
|
* @return {shaka.text.Cue}
|
|
192
181
|
* @private
|
|
193
182
|
*/
|
|
194
183
|
static parseCue_(
|
|
195
184
|
cueNode, offset, rateInfo, metadataElements, styles, regionElements,
|
|
196
|
-
cueRegions, whitespaceTrim,
|
|
185
|
+
cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent) {
|
|
197
186
|
/** @type {Element} */
|
|
198
187
|
let cueElement;
|
|
199
188
|
/** @type {Element} */
|
|
200
|
-
let parentElement = /** @type {Element} */(cueNode.parentNode);
|
|
189
|
+
let parentElement = /** @type {Element} */ (cueNode.parentNode);
|
|
190
|
+
|
|
191
|
+
if (cueNode.nodeType == Node.COMMENT_NODE) {
|
|
192
|
+
// The comments do not contain information that interests us here.
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
201
195
|
|
|
202
196
|
if (cueNode.nodeType == Node.TEXT_NODE) {
|
|
197
|
+
if (!isContent) {
|
|
198
|
+
// Ignore text elements outside the content. For example, whitespace
|
|
199
|
+
// on the same lexical level as the <p> elements, in a document with
|
|
200
|
+
// xml:space="preserve", should not be renderer.
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
203
|
// This should generate an "anonymous span" according to the TTML spec.
|
|
204
204
|
// So pretend the element was a <span>. parentElement was set above, so
|
|
205
205
|
// we should still be able to correctly traverse up for timing
|
|
@@ -214,11 +214,59 @@ shaka.text.TtmlTextParser = class {
|
|
|
214
214
|
}
|
|
215
215
|
goog.asserts.assert(cueElement, 'cueElement should be non-null!');
|
|
216
216
|
|
|
217
|
+
let imageElement = null;
|
|
218
|
+
for (const nameSpace of shaka.text.TtmlTextParser.smpteNsList_) {
|
|
219
|
+
imageElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
|
|
220
|
+
cueElement, 'backgroundImage', metadataElements, '#',
|
|
221
|
+
nameSpace)[0];
|
|
222
|
+
if (imageElement) {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const parentIsContent = isContent;
|
|
228
|
+
if (cueNode.nodeName == 'p' || imageElement) {
|
|
229
|
+
isContent = true;
|
|
230
|
+
}
|
|
231
|
+
|
|
217
232
|
const spaceStyle = cueElement.getAttribute('xml:space') ||
|
|
218
233
|
(whitespaceTrim ? 'default' : 'preserve');
|
|
219
234
|
|
|
220
235
|
const localWhitespaceTrim = spaceStyle == 'default';
|
|
221
236
|
|
|
237
|
+
// Parse any nested cues first.
|
|
238
|
+
const isTextNode = (node) => {
|
|
239
|
+
return node.nodeType == Node.TEXT_NODE;
|
|
240
|
+
};
|
|
241
|
+
const isLeafNode = Array.from(cueElement.childNodes).every(isTextNode);
|
|
242
|
+
const nestedCues = [];
|
|
243
|
+
if (!isLeafNode) {
|
|
244
|
+
// Otherwise, recurse into the children. Text nodes will convert into
|
|
245
|
+
// anonymous spans, which will then be leaf nodes.
|
|
246
|
+
for (const childNode of cueElement.childNodes) {
|
|
247
|
+
const nestedCue = shaka.text.TtmlTextParser.parseCue_(
|
|
248
|
+
childNode,
|
|
249
|
+
offset,
|
|
250
|
+
rateInfo,
|
|
251
|
+
metadataElements,
|
|
252
|
+
styles,
|
|
253
|
+
regionElements,
|
|
254
|
+
cueRegions,
|
|
255
|
+
localWhitespaceTrim,
|
|
256
|
+
cellResolution,
|
|
257
|
+
cueElement,
|
|
258
|
+
isContent,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// This node may or may not generate a nested cue.
|
|
262
|
+
if (nestedCue) {
|
|
263
|
+
nestedCues.push(nestedCue);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const isNested = /** @type {boolean} */ (parentCueElement != null);
|
|
269
|
+
|
|
222
270
|
// In this regex, "\S" means "non-whitespace character".
|
|
223
271
|
const hasTextContent = /\S/.test(cueElement.textContent);
|
|
224
272
|
const hasTimeAttributes =
|
|
@@ -226,7 +274,8 @@ shaka.text.TtmlTextParser = class {
|
|
|
226
274
|
cueElement.hasAttribute('end') ||
|
|
227
275
|
cueElement.hasAttribute('dur');
|
|
228
276
|
|
|
229
|
-
if (!hasTimeAttributes && !hasTextContent && cueElement.tagName != 'br'
|
|
277
|
+
if (!hasTimeAttributes && !hasTextContent && cueElement.tagName != 'br' &&
|
|
278
|
+
nestedCues.length == 0) {
|
|
230
279
|
if (!isNested) {
|
|
231
280
|
// Disregards empty <p> elements without time attributes nor content.
|
|
232
281
|
// <p begin="..." smpte:backgroundImage="..." /> will go through,
|
|
@@ -263,6 +312,20 @@ shaka.text.TtmlTextParser = class {
|
|
|
263
312
|
end += offset;
|
|
264
313
|
}
|
|
265
314
|
|
|
315
|
+
if (!hasTimeAttributes && nestedCues.length > 0) {
|
|
316
|
+
// If no time is defined for this cue, base the timing information on
|
|
317
|
+
// the time of the nested cues. In the case of multiple nested cues with
|
|
318
|
+
// different start times, it is the text displayer's responsibility to
|
|
319
|
+
// make sure that only the appropriate nested cue is drawn at any given
|
|
320
|
+
// time.
|
|
321
|
+
start = Infinity;
|
|
322
|
+
end = 0;
|
|
323
|
+
for (const cue of nestedCues) {
|
|
324
|
+
start = Math.min(start, cue.startTime);
|
|
325
|
+
end = Math.max(end, cue.endTime);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
266
329
|
if (cueElement.tagName == 'br') {
|
|
267
330
|
const cue = new shaka.text.Cue(start, end, '');
|
|
268
331
|
cue.lineBreak = true;
|
|
@@ -270,13 +333,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
270
333
|
}
|
|
271
334
|
|
|
272
335
|
let payload = '';
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const isTextNode = (node) => {
|
|
276
|
-
return node.nodeType == Node.TEXT_NODE;
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
if (Array.from(cueElement.childNodes).every(isTextNode)) {
|
|
336
|
+
if (isLeafNode) {
|
|
280
337
|
// If the childNodes are all text, this is a leaf node. Get the payload.
|
|
281
338
|
payload = cueElement.textContent;
|
|
282
339
|
if (localWhitespaceTrim) {
|
|
@@ -285,33 +342,18 @@ shaka.text.TtmlTextParser = class {
|
|
|
285
342
|
// Collapse multiple spaces into one.
|
|
286
343
|
payload = payload.replace(/\s+/g, ' ');
|
|
287
344
|
}
|
|
288
|
-
} else {
|
|
289
|
-
// Otherwise, recurse into the children. Text nodes will convert into
|
|
290
|
-
// anonymous spans, which will then be leaf nodes.
|
|
291
|
-
for (const childNode of cueElement.childNodes) {
|
|
292
|
-
const nestedCue = shaka.text.TtmlTextParser.parseCue_(
|
|
293
|
-
childNode,
|
|
294
|
-
offset,
|
|
295
|
-
rateInfo,
|
|
296
|
-
metadataElements,
|
|
297
|
-
styles,
|
|
298
|
-
regionElements,
|
|
299
|
-
cueRegions,
|
|
300
|
-
localWhitespaceTrim,
|
|
301
|
-
/* isNested= */ true,
|
|
302
|
-
cellResolution,
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
// This node may or may not generate a nested cue.
|
|
306
|
-
if (nestedCue) {
|
|
307
|
-
nestedCues.push(nestedCue);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
345
|
}
|
|
311
346
|
|
|
312
347
|
const cue = new shaka.text.Cue(start, end, payload);
|
|
313
348
|
cue.nestedCues = nestedCues;
|
|
314
349
|
|
|
350
|
+
if (!isContent) {
|
|
351
|
+
// If this is not a <p> element or a <div> with images, and it has no
|
|
352
|
+
// parent that was a <p> element, then it's part of the outer containers
|
|
353
|
+
// (e.g. the <body> or a normal <div> element within it).
|
|
354
|
+
cue.isContainer = true;
|
|
355
|
+
}
|
|
356
|
+
|
|
315
357
|
if (cellResolution) {
|
|
316
358
|
cue.cellResolution = cellResolution;
|
|
317
359
|
}
|
|
@@ -319,31 +361,32 @@ shaka.text.TtmlTextParser = class {
|
|
|
319
361
|
// Get other properties if available.
|
|
320
362
|
const regionElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
|
|
321
363
|
cueElement, 'region', regionElements, /* prefix= */ '')[0];
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
imageElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
|
|
330
|
-
cueElement, 'backgroundImage', metadataElements, '#',
|
|
331
|
-
nameSpace)[0];
|
|
332
|
-
if (imageElement) {
|
|
333
|
-
break;
|
|
364
|
+
// Do not actually apply that region unless it is non-inherited, though.
|
|
365
|
+
// This makes it so that, if a parent element has a region, the children
|
|
366
|
+
// don't also all independently apply the positioning of that region.
|
|
367
|
+
if (cueElement.hasAttribute('region')) {
|
|
368
|
+
if (regionElement && regionElement.getAttribute('xml:id')) {
|
|
369
|
+
const regionId = regionElement.getAttribute('xml:id');
|
|
370
|
+
cue.region = cueRegions.filter((region) => region.id == regionId)[0];
|
|
334
371
|
}
|
|
335
372
|
}
|
|
336
373
|
|
|
337
|
-
|
|
374
|
+
let regionElementForStyle = regionElement;
|
|
375
|
+
if (parentCueElement && isNested && !cueElement.getAttribute('region') &&
|
|
376
|
+
!cueElement.getAttribute('style')) {
|
|
377
|
+
regionElementForStyle =
|
|
378
|
+
shaka.text.TtmlTextParser.getElementsFromCollection_(
|
|
379
|
+
parentCueElement, 'region', regionElements, /* prefix= */ '')[0];
|
|
380
|
+
}
|
|
338
381
|
|
|
339
382
|
shaka.text.TtmlTextParser.addStyle_(
|
|
340
383
|
cue,
|
|
341
384
|
cueElement,
|
|
342
|
-
|
|
385
|
+
regionElementForStyle,
|
|
343
386
|
imageElement,
|
|
344
387
|
styles,
|
|
345
|
-
isNested,
|
|
346
|
-
isLeaf);
|
|
388
|
+
/** isNested= */ parentIsContent, // "nested in a <div>" doesn't count.
|
|
389
|
+
/** isLeaf= */ (nestedCues.length == 0));
|
|
347
390
|
|
|
348
391
|
return cue;
|
|
349
392
|
}
|
|
@@ -483,14 +526,13 @@ shaka.text.TtmlTextParser = class {
|
|
|
483
526
|
}
|
|
484
527
|
|
|
485
528
|
const align = TtmlTextParser.getStyleAttribute_(
|
|
486
|
-
cueElement, region, styles, 'textAlign',
|
|
529
|
+
cueElement, region, styles, 'textAlign', true);
|
|
487
530
|
if (align) {
|
|
488
531
|
cue.positionAlign = TtmlTextParser.textAlignToPositionAlign_[align];
|
|
489
532
|
cue.lineAlign = TtmlTextParser.textAlignToLineAlign_[align];
|
|
490
533
|
|
|
491
534
|
goog.asserts.assert(align.toUpperCase() in Cue.textAlign,
|
|
492
|
-
align.toUpperCase() +
|
|
493
|
-
' Should be in Cue.textAlign values!');
|
|
535
|
+
align.toUpperCase() + ' Should be in Cue.textAlign values!');
|
|
494
536
|
|
|
495
537
|
cue.textAlign = Cue.textAlign[align.toUpperCase()];
|
|
496
538
|
} else {
|
|
@@ -499,7 +541,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
499
541
|
}
|
|
500
542
|
|
|
501
543
|
const displayAlign = TtmlTextParser.getStyleAttribute_(
|
|
502
|
-
cueElement, region, styles, 'displayAlign',
|
|
544
|
+
cueElement, region, styles, 'displayAlign', true);
|
|
503
545
|
if (displayAlign) {
|
|
504
546
|
goog.asserts.assert(displayAlign.toUpperCase() in Cue.displayAlign,
|
|
505
547
|
displayAlign.toUpperCase() +
|
|
@@ -731,7 +773,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
731
773
|
return null;
|
|
732
774
|
}
|
|
733
775
|
|
|
734
|
-
const attr = XmlUtils.
|
|
776
|
+
const attr = XmlUtils.getAttributeNSList(region, ttsNs, attribute);
|
|
735
777
|
if (attr) {
|
|
736
778
|
return attr;
|
|
737
779
|
}
|
|
@@ -756,7 +798,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
756
798
|
|
|
757
799
|
// Styling on elements should take precedence
|
|
758
800
|
// over the main styling attributes
|
|
759
|
-
const elementAttribute = XmlUtils.
|
|
801
|
+
const elementAttribute = XmlUtils.getAttributeNSList(
|
|
760
802
|
cueElement,
|
|
761
803
|
ttsNs,
|
|
762
804
|
attribute);
|
|
@@ -799,7 +841,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
799
841
|
|
|
800
842
|
if (!styleAttributeValue) {
|
|
801
843
|
// Fall back to tts namespace.
|
|
802
|
-
styleAttributeValue = XmlUtils.
|
|
844
|
+
styleAttributeValue = XmlUtils.getAttributeNSList(
|
|
803
845
|
inheritedStyles[i],
|
|
804
846
|
ttsNs,
|
|
805
847
|
attribute);
|
|
@@ -1254,20 +1296,26 @@ shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
|
|
|
1254
1296
|
* document, not just "ttp:", so we use this with getAttributeNS() to ensure
|
|
1255
1297
|
* that we support arbitrary namespace names.
|
|
1256
1298
|
*
|
|
1257
|
-
* @const {string}
|
|
1299
|
+
* @const {!Array.<string>}
|
|
1258
1300
|
* @private
|
|
1259
1301
|
*/
|
|
1260
|
-
shaka.text.TtmlTextParser.parameterNs_ =
|
|
1302
|
+
shaka.text.TtmlTextParser.parameterNs_ = [
|
|
1303
|
+
'http://www.w3.org/ns/ttml#parameter',
|
|
1304
|
+
'http://www.w3.org/2006/10/ttaf1#parameter',
|
|
1305
|
+
];
|
|
1261
1306
|
|
|
1262
1307
|
/**
|
|
1263
1308
|
* The namespace URL for TTML styles. Can be assigned any name in the TTML
|
|
1264
1309
|
* document, not just "tts:", so we use this with getAttributeNS() to ensure
|
|
1265
1310
|
* that we support arbitrary namespace names.
|
|
1266
1311
|
*
|
|
1267
|
-
* @const {string}
|
|
1312
|
+
* @const {!Array.<string>}
|
|
1268
1313
|
* @private
|
|
1269
1314
|
*/
|
|
1270
|
-
shaka.text.TtmlTextParser.styleNs_ =
|
|
1315
|
+
shaka.text.TtmlTextParser.styleNs_ = [
|
|
1316
|
+
'http://www.w3.org/ns/ttml#styling',
|
|
1317
|
+
'http://www.w3.org/2006/10/ttaf1#styling',
|
|
1318
|
+
];
|
|
1271
1319
|
|
|
1272
1320
|
/**
|
|
1273
1321
|
* The namespace URL for EBU TTML styles. Can be assigned any name in the TTML
|