diff --git a/lib/text/simple_text_displayer.js b/lib/text/simple_text_displayer.js index 63604c8fdc..e2343caa3f 100644 --- a/lib/text/simple_text_displayer.js +++ b/lib/text/simple_text_displayer.js @@ -121,16 +121,28 @@ shaka.text.SimpleTextDisplayer = class { // We don't want to modify the array or objects passed in, since we don't // technically own them. So we build a new array and replace certain items // in it if they need to be flattened. - const flattenedCues = cues.map((cue) => { - if (cue.nestedCues.length) { - const flatCue = cue.clone(); - flatCue.nestedCues = []; - flatCue.payload = flattenPayload(cue); - return flatCue; - } else { - return cue; + // We also don't want to flatten the text payloads starting at a container + // element; otherwise, for containers encapsulating multiple caption lines, + // the lines would merge into a single cue. This is undesirable when a + // subset of the captions are outside of the append time window. To fix + // this, we only call flattenPayload() starting at elements marked as + // isContainer = false. + const getCuesToFlatten = (cues, result) => { + for (const cue of cues) { + if (cue.isContainer) { + // Recurse to find the actual text payload cues. + getCuesToFlatten(cue.nestedCues, result); + } else { + // Flatten the payload. + const flatCue = cue.clone(); + flatCue.nestedCues = []; + flatCue.payload = flattenPayload(cue); + result.push(flatCue); + } } - }); + return result; + }; + const flattenedCues = getCuesToFlatten(cues, []); // Convert cues. const textTrackCues = []; diff --git a/test/text/simple_text_displayer_unit.js b/test/text/simple_text_displayer_unit.js index 88c4dc86b2..206387f6e4 100644 --- a/test/text/simple_text_displayer_unit.js +++ b/test/text/simple_text_displayer_unit.js @@ -127,6 +127,33 @@ describe('SimpleTextDisplayer', () => { [shakaCue]); }); + it('flattens nested cue payloads correctly', () => { + const level0ContainerCue = new shaka.text.Cue(10, 30, ''); + level0ContainerCue.isContainer = true; + + const level1NonContainerCueA = new shaka.text.Cue(10, 20, ''); + const level1NonContainerCueB = new shaka.text.Cue(20, 30, ''); + + // Add a trailing whitespace character to get a space-delimited expected + // result. + const cueANestedCue0 = new shaka.text.Cue(10, 20, 'Cue A Test0 '); + const cueANestedCue1 = new shaka.text.Cue(10, 20, 'Cue A Test1'); + const cueBNestedCue0 = new shaka.text.Cue(20, 30, 'Cue B Test0 '); + const cueBNestedCue1 = new shaka.text.Cue(20, 30, 'Cue B Test1'); + + level1NonContainerCueA.nestedCues = [cueANestedCue0, cueANestedCue1]; + level1NonContainerCueB.nestedCues = [cueBNestedCue0, cueBNestedCue1]; + level0ContainerCue.nestedCues = + [level1NonContainerCueA, level1NonContainerCueB]; + + verifyHelper( + [ + {startTime: 10, endTime: 20, text: 'Cue A Test0 Cue A Test1'}, + {startTime: 20, endTime: 30, text: 'Cue B Test0 Cue B Test1'}, + ], + [level0ContainerCue]); + }); + // Regression test for b/159050711 it('maintains the styles of the parent cue', () => { const shakaCue = new shaka.text.Cue(10, 20, '');