Skip to content

Commit b6a49d8

Browse files
committed
add children cues
1 parent a0bcd11 commit b6a49d8

File tree

6 files changed

+161
-76
lines changed

6 files changed

+161
-76
lines changed

lib/text/cue.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ shaka.text.Cue = function(startTime, endTime, payload) {
184184
* @exportInterface
185185
*/
186186
this.id = '';
187+
188+
/**
189+
* @override
190+
* @exportInterface
191+
*/
192+
this.children = [];
187193
};
188194

189195

lib/text/simple_text_displayer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ shaka.text.SimpleTextDisplayer.convertToTextTrackCue_ = function(shakaCue) {
222222
}
223223

224224
if (shakaCue.color !== null) {
225-
vttCue.text = `<c.${shakaCue.color}>${shakaCue.payload}</c>`
225+
vttCue.text = `<c.${shakaCue.color}>${shakaCue.payload}</c>`;
226226
}
227227

228228
return vttCue;

lib/text/ttml_text_parser.js

Lines changed: 121 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ shaka.text.TtmlTextParser.prototype.parseMedia = function(data, time) {
127127
}
128128
}
129129

130-
let textNodes = TtmlTextParser.getLeafNodes_(
130+
let textNodes = TtmlTextParser.getLeafCues_(
131131
tt.getElementsByTagName('body')[0]);
132132

133133
for (let i = 0; i < textNodes.length; i++) {
@@ -268,7 +268,10 @@ shaka.text.TtmlTextParser.getLeafNodes_ = function(element) {
268268
for (let i = 0; i < childNodes.length; i++) {
269269
const childNodeItem = childNodes[i];
270270

271-
if (childNodeItem.nodeType == Node.ELEMENT_NODE && childNodeItem.nodeName !== 'br') {
271+
if (
272+
childNodeItem.nodeType == Node.ELEMENT_NODE &&
273+
childNodeItem.nodeName !== 'br'
274+
) {
272275
// Get the leaves the child might contain.
273276
goog.asserts.assert(childNodeItem instanceof Element,
274277
'Node should be Element!');
@@ -277,23 +280,7 @@ shaka.text.TtmlTextParser.getLeafNodes_ = function(element) {
277280
goog.asserts.assert(leafChildren.length > 0,
278281
'Only a null Element should return no leaves!');
279282

280-
const nearestAncestor = element.hasAttribute('begin') ? element : element.closest('[begin]');
281-
282-
if (nearestAncestor) {
283-
const begin = nearestAncestor.getAttribute('begin');
284-
const end = nearestAncestor.getAttribute('end');
285-
const region = nearestAncestor.getAttribute('region');
286-
287-
result = result.concat(leafChildren.map(leafChild => {
288-
leafChild.setAttribute('begin', begin);
289-
leafChild.setAttribute('end', end);
290-
leafChild.setAttribute('region', leafChild.getAttribute('region') || region);
291-
292-
return leafChild;
293-
}));
294-
} else {
295-
result = result.concat(leafChildren);
296-
}
283+
result = result.concat(leafChildren);
297284
}
298285
}
299286

@@ -305,31 +292,20 @@ shaka.text.TtmlTextParser.getLeafNodes_ = function(element) {
305292
return result;
306293
};
307294

295+
shaka.text.TtmlTextParser.getLeafCues_ = function(element) {
296+
if (!element) {
297+
return [];
298+
}
308299

309-
/**
310-
* Inserts \n where <br> tags are found.
311-
*
312-
* @param {!Node} element
313-
* @param {boolean} whitespaceTrim
314-
* @private
315-
*/
316-
shaka.text.TtmlTextParser.addNewLines_ = function(element, whitespaceTrim) {
317-
let childNodes = element.childNodes;
300+
return Array.from(element.querySelectorAll('[begin][end]'));
301+
};
318302

319-
for (let i = 0; i < childNodes.length; i++) {
320-
if (childNodes[i].nodeName == 'br' && i > 0) {
321-
childNodes[i - 1].textContent += '\n';
322-
} else if (childNodes[i].childNodes.length > 0) {
323-
shaka.text.TtmlTextParser.addNewLines_(childNodes[i], whitespaceTrim);
324-
} else if (whitespaceTrim) {
325-
// Trim leading and trailing whitespace.
326-
let trimmed = childNodes[i].textContent.trim();
327-
// Collapse multiple spaces into one.
328-
trimmed = trimmed.replace(/\s+/g, ' ');
329-
330-
childNodes[i].textContent = trimmed;
331-
}
332-
}
303+
shaka.text.TtmlTextParser.sanitizeTextContent = function(textContent) {
304+
return textContent
305+
// Trim leading and trailing whitespace.
306+
.trim()
307+
// Collapse multiple spaces into one.
308+
.replace(/\s+/g, ' ');
333309
};
334310

335311

@@ -349,33 +325,76 @@ shaka.text.TtmlTextParser.addNewLines_ = function(element, whitespaceTrim) {
349325
*/
350326
shaka.text.TtmlTextParser.parseCue_ = function(
351327
cueElement, offset, rateInfo, metadataElements, styles,
352-
regionElements, cueRegions, whitespaceTrim) {
328+
regionElements, cueRegions, whitespaceTrim, isChild) {
329+
if (isChild && cueElement.nodeName === 'br') {
330+
const cue = new shaka.text.Cue();
331+
cue.spacer = true;
332+
333+
return cue;
334+
}
335+
353336
// Disregard empty elements:
354337
// TTML allows for empty elements like <div></div>.
355338
// If cueElement has neither time attributes, nor
356339
// non-whitespace text, don't try to make a cue out of it.
357-
if (!cueElement.hasAttribute('begin') &&
358-
!cueElement.hasAttribute('end') &&
359-
/^\s*$/.test(cueElement.textContent)) {
340+
if (
341+
/^\s*$/.test(cueElement.textContent) ||
342+
(
343+
!isChild &&
344+
!cueElement.hasAttribute('begin') &&
345+
!cueElement.hasAttribute('end')
346+
)
347+
) {
360348
return null;
361349
}
362350

363-
shaka.text.TtmlTextParser.addNewLines_(cueElement, whitespaceTrim);
364-
365351
// Get time.
366352
let start = shaka.text.TtmlTextParser.parseTime_(
367353
cueElement.getAttribute('begin'), rateInfo);
368354
let end = shaka.text.TtmlTextParser.parseTime_(
369355
cueElement.getAttribute('end'), rateInfo);
370356
let duration = shaka.text.TtmlTextParser.parseTime_(
371357
cueElement.getAttribute('dur'), rateInfo);
372-
let payload = cueElement.textContent;
358+
let payload = '';
359+
let children = [];
360+
361+
if (
362+
cueElement.childNodes.length === 1 &&
363+
cueElement.childNodes[0].nodeType === Node.TEXT_NODE
364+
) {
365+
if (whitespaceTrim) {
366+
payload = shaka.text.TtmlTextParser.sanitizeTextContent(
367+
cueElement.textContent
368+
);
369+
} else {
370+
payload = cueElement.textContent;
371+
}
372+
} else {
373+
for (let i = 0; i < cueElement.childNodes.length; i++) {
374+
const childNode = cueElement.childNodes[i];
375+
const childCue = shaka.text.TtmlTextParser.parseCue_(
376+
childNode,
377+
offset,
378+
rateInfo,
379+
metadataElements,
380+
styles,
381+
regionElements,
382+
cueRegions,
383+
whitespaceTrim,
384+
true
385+
);
386+
387+
if (childCue) {
388+
children.push(childCue);
389+
}
390+
}
391+
}
373392

374393
if (end == null && duration != null) {
375394
end = start + duration;
376395
}
377396

378-
if (start == null || end == null) {
397+
if (!isChild && (start == null || end == null)) {
379398
throw new shaka.util.Error(
380399
shaka.util.Error.Severity.CRITICAL,
381400
shaka.util.Error.Category.TEXT,
@@ -386,9 +405,10 @@ shaka.text.TtmlTextParser.parseCue_ = function(
386405
end += offset;
387406

388407
let cue = new shaka.text.Cue(start, end, payload);
408+
cue.children = children;
389409

390410
// Get other properties if available.
391-
let regionElement = shaka.text.TtmlTextParser.getElementFromCollection_(
411+
const [regionElement] = shaka.text.TtmlTextParser.getElementFromCollection_(
392412
cueElement, 'region', regionElements, /* prefix= */ '');
393413
if (regionElement && regionElement.getAttribute('xml:id')) {
394414
let regionId = regionElement.getAttribute('xml:id');
@@ -397,7 +417,7 @@ shaka.text.TtmlTextParser.parseCue_ = function(
397417
});
398418
cue.region = regionsWithId[0];
399419
}
400-
const imageElement = shaka.text.TtmlTextParser.getElementFromCollection_(
420+
const [imageElement] = shaka.text.TtmlTextParser.getElementFromCollection_(
401421
cueElement, 'smpte:backgroundImage', metadataElements, '#');
402422
shaka.text.TtmlTextParser.addStyle_(
403423
cue,
@@ -701,6 +721,7 @@ shaka.text.TtmlTextParser.getStyleAttribute_ = function(
701721
// An attribute can be specified on region level or in a styling block
702722
// associated with the region or original element.
703723
const TtmlTextParser = shaka.text.TtmlTextParser;
724+
704725
let attr = TtmlTextParser.getStyleAttributeFromElement_(
705726
cueElement, styles, attribute);
706727
if (attr) {
@@ -735,7 +756,7 @@ shaka.text.TtmlTextParser.getStyleAttributeFromRegion_ = function(
735756
}
736757
}
737758

738-
let style = shaka.text.TtmlTextParser.getElementFromCollection_(
759+
let [style] = shaka.text.TtmlTextParser.getElementFromCollection_(
739760
region, 'style', styles, /* prefix= */ '');
740761
if (style) {
741762
return XmlUtils.getAttributeNS(style, ttsNs, attribute);
@@ -759,14 +780,36 @@ shaka.text.TtmlTextParser.getStyleAttributeFromElement_ = function(
759780
const XmlUtils = shaka.util.XmlUtils;
760781
const ttsNs = shaka.text.TtmlTextParser.styleNs_;
761782

762-
let getElementFromCollection_ =
763-
shaka.text.TtmlTextParser.getElementFromCollection_;
764-
let style = getElementFromCollection_(
783+
// Styling on elements should take precedence over the main styling attributes
784+
const elementAttribute = XmlUtils.getAttributeNS(
785+
cueElement,
786+
ttsNs,
787+
attribute
788+
);
789+
790+
if (elementAttribute) {
791+
return elementAttribute;
792+
}
793+
794+
const inheritedStyles = shaka.text.TtmlTextParser.getElementFromCollection_(
765795
cueElement, 'style', styles, /* prefix= */ '');
766-
if (style) {
767-
return XmlUtils.getAttributeNS(style, ttsNs, attribute);
796+
797+
let styleValue;
798+
799+
// The last value in our styles stack takes the precedence over the others
800+
for (let i = 0; i < inheritedStyles.length; i++) {
801+
const styleAttributeValue = XmlUtils.getAttributeNS(
802+
inheritedStyles[i],
803+
ttsNs,
804+
attribute
805+
);
806+
807+
if (styleAttributeValue) {
808+
styleValue = styleAttributeValue;
809+
}
768810
}
769-
return null;
811+
812+
return styleValue;
770813
};
771814

772815

@@ -783,22 +826,31 @@ shaka.text.TtmlTextParser.getStyleAttributeFromElement_ = function(
783826
*/
784827
shaka.text.TtmlTextParser.getElementFromCollection_ = function(
785828
element, attributeName, collection, prefixName) {
829+
const items = [];
830+
786831
if (!element || collection.length < 1) {
787-
return null;
832+
return items;
788833
}
789-
let item = null;
790-
let itemName = shaka.text.TtmlTextParser.getInheritedAttribute_(
834+
835+
const attributeValue = shaka.text.TtmlTextParser.getInheritedAttribute_(
791836
element, attributeName);
792-
if (itemName) {
793-
for (let i = 0; i < collection.length; i++) {
794-
if ((prefixName + collection[i].getAttribute('xml:id')) == itemName) {
795-
item = collection[i];
796-
break;
837+
838+
if (attributeValue) {
839+
// There could be multiple items in one attribute
840+
// <span style="style1 style2">A cue</span>
841+
const itemNames = attributeValue.split(' ');
842+
843+
itemNames.forEach((itemName) => {
844+
for (let i = 0; i < collection.length; i++) {
845+
if ((prefixName + collection[i].getAttribute('xml:id')) == itemName) {
846+
items.push(collection[i]);
847+
break;
848+
}
797849
}
798-
}
850+
});
799851
}
800852

801-
return item;
853+
return items;
802854
};
803855

804856

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
},
6464
"license": "Apache-2.0",
6565
"scripts": {
66-
"prepublish": "in-publish && python build/checkversion.py && python build/all.py --force || not-in-publish"
66+
"prepublish": "in-publish && python build/checkversion.py && python build/all.py --force || not-in-publish",
67+
"lint": "eslint './{demo,lib,ui}/**/*.js'"
6768
}
6869
}

ui/less/containers.less

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,5 @@
212212
span {
213213
font-size: 18px;
214214
color: rgb(255, 255, 255);
215-
display: block;
216-
max-width:95%;
217215
}
218216
}

ui/text_displayer.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,38 @@ shaka.ui.TextDisplayer = class {
181181
});
182182

183183
for (const cue of currentCues) {
184-
const captions = shaka.util.Dom.createHTMLElement('span');
184+
this.displayCue_(this.textContainer_, cue);
185+
}
186+
}
187+
188+
displayChildrenCue_(container, cue) {
189+
const captions = shaka.util.Dom.createHTMLElement('span');
190+
191+
if (cue.spacer) {
192+
captions.style.display = 'block';
193+
} else {
185194
this.setCaptionStyles_(captions, cue);
186-
this.currentCuesMap_.set(cue, captions);
187-
this.textContainer_.appendChild(captions);
195+
}
196+
197+
container.appendChild(captions);
198+
199+
return captions;
200+
}
201+
202+
displayCue_(container, cue) {
203+
if (cue.children.length) {
204+
const childrenContainer = shaka.util.Dom.createHTMLElement('p');
205+
childrenContainer.style.width = '100%';
206+
this.setCaptionStyles_(childrenContainer, cue);
207+
208+
cue.children.forEach(
209+
(childCue) => this.displayChildrenCue_(childrenContainer, childCue)
210+
);
211+
212+
container.appendChild(childrenContainer);
213+
this.currentCuesMap_.set(cue, childrenContainer);
214+
} else {
215+
this.currentCuesMap_.set(cue, this.displayChildrenCue_(container, cue));
188216
}
189217
}
190218

0 commit comments

Comments
 (0)