/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
describe('TtmlTextParser', () => {
const Cue = shaka.text.Cue;
const CueRegion = shaka.text.CueRegion;
const Util = shaka.test.Util;
const anyString = jasmine.any(String);
it('supports no cues', () => {
verifyHelper([],
'',
{periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0},
{});
});
it('supports empty text string', () => {
verifyHelper([],
'',
{periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0},
{});
});
it('supports div with no cues but whitespace', () => {
verifyHelper(
[],
'
\r\n
',
{periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0},
{});
});
it('supports xml:space', () => {
const ttBody =
'\n' +
'
\n' +
' A B C \n' +
'
\n' +
'
\n';
// When xml:space="default", ignore whitespace outside tags.
verifyHelper(
[
{
startTime: 62.03,
endTime: 62.05,
nestedCues: [{
payload: ' A B C ',
startTime: 62.03,
endTime: 62.05,
}],
payload: '',
},
],
'' + ttBody + '',
{periodStart: 0, segmentStart: 60, segmentEnd: 70, vttOffset: 0},
{startTime: 62.03, endTime: 62.05});
// When xml:space="preserve", take them into account.
verifyHelper(
[
{
startTime: 62.03,
endTime: 62.05,
nestedCues: [{
// anonymous span
payload: '\n ',
startTime: 62.03,
endTime: 62.05,
}, {
payload: ' A B C ',
startTime: 62.03,
endTime: 62.05,
}, {
// anonymous span
payload: '\n ',
startTime: 62.03,
endTime: 62.05,
}],
},
],
'' + ttBody + '',
{periodStart: 0, segmentStart: 60, segmentEnd: 70, vttOffset: 0},
{startTime: 62.03, endTime: 62.05});
// The default value for xml:space is "default".
verifyHelper(
[
{
startTime: 62.03,
endTime: 62.05,
nestedCues: [{
payload: ' A B C ',
startTime: 62.03,
endTime: 62.05,
}],
},
],
'' + ttBody + '',
{periodStart: 0, segmentStart: 60, segmentEnd: 70, vttOffset: 0},
{startTime: 62.03, endTime: 62.05});
// Any other value is rejected as an error.
errorHelper(shaka.util.Error.Code.INVALID_XML,
'' + ttBody + '',
jasmine.any(String));
});
it('supports xml:space overriding default at span level', () => {
const ttBody = '\n' +
' \n' +
'
\n' +
' A B C \n' +
'
\n' +
'
\n';
// When xml:space="preserve", take them into account.
verifyHelper(
[
{
startTime: 62.03,
endTime: 62.05,
nestedCues: [{
payload: ' A B C ',
startTime: 62.03,
endTime: 62.05,
}],
},
],
'' + ttBody + '',
{periodStart: 0, segmentStart: 60, segmentEnd: 70, vttOffset: 0},
{startTime: 62.03, endTime: 62.05});
});
it('rejects invalid ttml', () => {
errorHelper(shaka.util.Error.Code.INVALID_XML, '', anyString);
});
it('rejects ttml with body>p instead of body>div', () => {
errorHelper(
shaka.util.Error.Code.INVALID_TEXT_CUE,
'',
anyString);
});
it('rejects ttml with div>span instead of div>p', () => {
errorHelper(
shaka.util.Error.Code.INVALID_TEXT_CUE,
'
',
anyString);
});
it('rejects invalid time format', () => {
const wrap = (ttmlCue) => {
return `${ttmlCue}
`;
};
errorHelper(
shaka.util.Error.Code.INVALID_TEXT_CUE,
wrap('My very own cue
'),
anyString);
errorHelper(
shaka.util.Error.Code.INVALID_TEXT_CUE,
wrap('An invalid cue
'),
anyString);
});
it('supports spans as nestedCues of paragraphs', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
payload: '',
nestedCues: [
{
payload: 'First cue',
startTime: 62.05,
endTime: 3723.2,
},
{
payload: '',
lineBreak: true,
startTime: 62.05,
endTime: 3723.2,
},
{
payload: 'Second cue',
startTime: 62.05,
endTime: 3723.2,
},
],
},
],
'' +
'
' +
'First cue
Second cue' +
'
' +
'
',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2});
});
it('supports anonymous spans as nestedCues of paragraphs', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
payload: '',
nestedCues: [
{
payload: 'First cue',
startTime: 62.05,
endTime: 3723.2,
},
{
payload: '',
lineBreak: true,
startTime: 62.05,
endTime: 3723.2,
},
{
payload: 'Second cue',
startTime: 62.05,
endTime: 3723.2,
},
],
},
],
'' +
'
' +
'First cue
Second cue' +
'
' +
'
',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2});
});
it('supports multiple levels of nestedCues', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
payload: '',
color: 'black',
// cues in p container.
nestedCues: [
// anonymous span in p container.
{
payload: 'First cue',
startTime: 62.05,
endTime: 3723.2,
},
// container for Second cue and Third cue.
{
payload: '',
startTime: 62.05,
endTime: 3723.2,
color: 'blue',
nestedCues: [
{
payload: 'Second cue',
startTime: 62.05,
endTime: 3723.2,
color: '',
},
{
payload: 'Third cue',
startTime: 62.05,
endTime: 3723.2,
color: 'green',
},
],
},
],
},
],
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'First cue' +
'Second cue' +
'Third cue' +
'' +
'
' +
'
',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2});
});
it('inherits timing information of nested cues if unprovided', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
nestedCues: [
{startTime: 62.05, endTime: 3723.2, payload: 'Test'},
],
},
],
'' +
'' +
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2});
});
it('does not discard cues with image subcues', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
nestedCues: [
{
startTime: 62.05,
endTime: 3723.2,
payload: '',
backgroundImage: 'data:image/png;base64,base64EncodedImage',
},
],
},
],
'' +
'' +
'' +
'base64EncodedImage' +
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2});
});
it('supports colon formatted time', () => {
verifyHelper(
[
{startTime: 62.05, endTime: 3723.2, payload: 'Test'},
],
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2});
});
it('supports colon formatted time with 0 or 1 dp', () => {
verifyHelper(
[
{startTime: 62, endTime: 3723.2, payload: 'Test'},
],
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62, endTime: 3723.2});
});
it('accounts for offset', () => {
verifyHelper(
[
{startTime: 69.05, endTime: 3730.2, payload: 'Test'},
],
'',
{periodStart: 7, segmentStart: 60, segmentEnd: 3740, vttOffset: 0},
{startTime: 69.05, endTime: 3730.2});
});
it('supports nested cues with an offset', () => {
verifyHelper(
[
{
startTime: 69.05,
endTime: 3730.2,
payload: '',
nestedCues: [
{
payload: 'Nested cue',
startTime: 69.05,
endTime: 3730.2,
},
],
},
],
'',
{periodStart: 7, segmentStart: 60, segmentEnd: 3740, vttOffset: 0},
{startTime: 69.05, endTime: 3730.2});
});
it('supports time in 0.00h 0.00m 0.00s format', () => {
verifyHelper(
[
{startTime: 3567.03, endTime: 5402.3, payload: 'Test'},
],
'',
{periodStart: 0, segmentStart: 3560, segmentEnd: 5410, vttOffset: 0},
{startTime: 3567.03, endTime: 5402.3});
});
it('supports time with frame rate', () => {
verifyHelper(
[
{startTime: 615.5, endTime: 663, payload: 'Test'},
],
' ' +
'',
{periodStart: 0, segmentStart: 610, segmentEnd: 670, vttOffset: 0},
{startTime: 615.5, endTime: 663});
});
it('supports time with frame rate multiplier', () => {
verifyHelper(
[
{startTime: 615.5, endTime: 663, payload: 'Test'},
],
' ' +
'',
{periodStart: 0, segmentStart: 610, segmentEnd: 670, vttOffset: 0},
{startTime: 615.5, endTime: 663});
});
it('supports time with subframes', () => {
verifyHelper(
[
{
startTime: Util.closeTo(615.5 + 1 / 60),
endTime: 663,
payload: 'Test',
},
],
' ' +
'',
{periodStart: 0, segmentStart: 610, segmentEnd: 670, vttOffset: 0},
{startTime: Util.closeTo(615.5 + 1 / 60), endTime: 663});
});
it('supports time in frame format', () => {
verifyHelper(
[
{startTime: 2.5, endTime: Util.closeTo(10.01), payload: 'Test'},
],
'' +
'',
{periodStart: 0, segmentStart: 0, segmentEnd: 20, vttOffset: 0},
{startTime: 2.5, endTime: Util.closeTo(10.01)});
});
it('supports time in tick format', () => {
verifyHelper(
[
{startTime: 5, endTime: Util.closeTo(6.02), payload: 'Test'},
],
'' +
'',
{periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0},
{startTime: 5, endTime: Util.closeTo(6.02)});
});
it('supports time with duration', () => {
verifyHelper(
[
{startTime: 62.05, endTime: 67.05, payload: 'Test'},
],
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 70, vttOffset: 0},
{startTime: 62.05, endTime: 67.05});
});
it('supports comments in the body', () => {
verifyHelper(
[],
'' +
'' +
'
',
{periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0},
{});
});
it('does not inherit regions', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
payload: 'Test',
},
],
'' +
'' +
'' +
'' +
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2, region: {id: 'subtitleArea'}},
{startTime: 62.05, endTime: 3723.2});
});
it('parses alignment from textAlign attribute of a region', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
payload: 'Test',
lineAlign: Cue.textAlign.START,
},
],
'' +
'' +
'' +
'' +
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2, region: {id: 'subtitleArea'}},
{startTime: 62.05, endTime: 3723.2});
});
it('allows non-standard namespace names', () => {
verifyHelper(
[
{
startTime: 62.05,
endTime: 3723.2,
payload: 'Test',
lineAlign: Cue.textAlign.START,
},
],
'' +
'' +
'' +
'' +
'',
{periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
{startTime: 62.05, endTime: 3723.2, region: {id: 'subtitleArea'}},
{startTime: 62.05, endTime: 3723.2});
});
it('parses alignment from