Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 143 additions & 7 deletions lib/vtt.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,54 @@ var TAG_NAME = {
lang: "span"
};

// 5.1 default text color
// 5.2 default text background color is equivalent to text color with bg_ prefix
var DEFAULT_COLOR_CLASS = {
white: 'rgba(255,255,255,1)',
lime: 'rgba(0,255,0,1)',
cyan: 'rgba(0,255,255,1)',
red: 'rgba(255,0,0,1)',
yellow: 'rgba(255,255,0,1)',
magenta: 'rgba(255,0,255,1)',
blue: 'rgba(0,0,255,1)',
black: 'rgba(0,0,0,1)'
};

var ALLOWED_CSS_PROPS = {
'color': 1,
'opacity': 1,
'visibility': 1,
'text-decoration': 1,
'text-decoration-color': 1,
'text-decoration-style': 1,
'text-decoration-line': 1,
'text-shadow': 1,
'background': 1,
'background-image': 1,
'background-position': 1,
'background-size': 1,
'background-repeat': 1,
'background-origin': 1,
'background-clip': 1,
'background-attachment': 1,
'background-color': 1,
'outline': 1,
'outline-color': 1,
'outline-style': 1,
'outline-width': 1,
'font': 1,
'font-style': 1,
'font-variant': 1,
'font-weight': 1,
'font-stretch': 1,
'font-size': 1,
'line-height': 1,
'font-family': 1,
'white-space': 1,
'text-combine-upright': 1,
'ruby-position': 1
};

var TAG_ANNOTATION = {
v: "title",
lang: "lang"
Expand All @@ -286,7 +334,11 @@ var NEEDS_PARENT = {
};

// Parse content into a document fragment.
function parseContent(window, input) {
function parseContent(window, input, styles) {
if (!styles) {
styles = [];
}

function nextToken() {
// Check for end-of-string.
if (!input) {
Expand Down Expand Up @@ -341,6 +393,23 @@ function parseContent(window, input) {
t,
tagStack = [];

styles.forEach(function(style) {
var s = css.parse(style.css);
s.stylesheet.rules.forEach(function(rule) {
if (rule.type === 'comment') {
return;
}
if (rule.selectors.indexOf('::cue') !== -1) {
rule.declarations.forEach(function(decl) {
if (ALLOWED_CSS_PROPS.hasOwnProperty(decl.property)) {
rootDiv.style[decl.property] = decl.value;
}
});
}
});
});


while ((t = nextToken()) !== null) {
if (t[0] === '<') {
if (t[1] === "/") {
Expand Down Expand Up @@ -378,7 +447,22 @@ function parseContent(window, input) {
}
// Set the class list (as a list of classes, separated by space).
if (m[2]) {
node.className = m[2].substr(1).replace('.', ' ');
var classes = m[2].split('.');

classes.forEach(function(cl) {
var bgColor = /^bg_/.test(cl);
// slice out `bg_` if it's a background color
var colorName = bgColor ? cl.slice(3) : cl;

if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
var propName = bgColor ? 'background-color' : 'color';
var propValue = DEFAULT_COLOR_CLASS[colorName];

node.style[propName] = propValue;
}
});

node.className = classes.join(' ');
}
// Append the node to the current node, and enter the scope of the new
// node.
Expand Down Expand Up @@ -530,13 +614,13 @@ StyleBox.prototype.formatStyle = function(val, unit) {

// Constructs the computed display state of the cue (a div). Places the div
// into the overlay which should be a block level element (usually a div).
function CueStyleBox(window, cue, styleOptions) {
function CueStyleBox(window, cue, styleOptions, styles) {
StyleBox.call(this);
this.cue = cue;

// Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
// have inline positioning and will function as the cue background box.
this.cueDiv = parseContent(window, cue.text);
this.cueDiv = parseContent(window, cue.text, styles);
var styles = {
color: "rgba(255, 255, 255, 1)",
backgroundColor: "rgba(0, 0, 0, 0.8)",
Expand Down Expand Up @@ -930,7 +1014,7 @@ var CUE_BACKGROUND_PADDING = "1.5%";
// Runs the processing model over the cues and regions passed to it.
// @param overlay A block level element (usually a div) that the computed cues
// and regions will be placed into.
WebVTT.processCues = function(window, cues, overlay) {
WebVTT.processCues = function(window, cues, overlay, styles) {
if (!window || !cues || !overlay) {
return null;
}
Expand Down Expand Up @@ -983,7 +1067,7 @@ WebVTT.processCues = function(window, cues, overlay) {
cue = cues[i];

// Compute the intial position and styles of the cue div.
styleBox = new CueStyleBox(window, cue, styleOptions);
styleBox = new CueStyleBox(window, cue, styleOptions, styles);
paddedOverlay.appendChild(styleBox.div);

// Move the cue div to it's correct line position.
Expand Down Expand Up @@ -1028,6 +1112,8 @@ WebVTT.Parser.prototype = {
parse: function (data) {
var self = this;

self.styles = [];

// If there is no data then we won't decode it, but will just try to parse
// whatever is in buffer already. This may occur in circumstances, for
// example when flush() is called.
Expand All @@ -1036,6 +1122,10 @@ WebVTT.Parser.prototype = {
self.buffer += self.decoder.decode(data, {stream: true});
}

function skipWhitespace() {
self.buffer = self.buffer.replace(/^\s+/, '');
}

function collectNextLine() {
var buffer = self.buffer;
var pos = 0;
Expand Down Expand Up @@ -1181,6 +1271,7 @@ WebVTT.Parser.prototype = {
}

var alreadyCollectedLine = false;
var sawCue = false;
while (self.buffer) {
// We can't parse a line until we have the full line.
if (!/\r\n|\n/.test(self.buffer)) {
Expand All @@ -1200,15 +1291,58 @@ WebVTT.Parser.prototype = {
parseHeader(line);
} else if (!line) {
// An empty line terminates the header and starts the body (cues).
self.state = "ID";
self.state = "BLOCKS";
}
continue;
case "REGION":
if (!line) {
self.state = "BLOCKS";
break;
}
continue;
case "STYLE":
if (!line) {
self.state = "BLOCKS";
break;
}
self.style.css += line + "\n";
continue;
case "NOTE":
// Ignore NOTE blocks.
if (!line) {
self.state = "ID";
}
continue;
case "BLOCKS":
console.log('blocks', line);

if (!line) {
continue;
}

// Check for the start of NOTE blocks.
if (/^NOTE($|[ \t])/.test(line)) {
self.state = "NOTE";
break;
}

// Check for the start of the REGION blocks
if (/^REGION/.test(line) && !sawCue) {
self.state = "REGION";
break;
}

// Check for the start of the STYLE blocks
if (/^STYLE/.test(line) && !sawCue) {
self.state = "STYLE";
self.style = {css: ''};
self.styles.push(self.style);
break;
}

self.state = "ID";
// Process line as an ID.
/*falls through*/
case "ID":
// Check for the start of NOTE blocks.
if (/^NOTE($|[ \t])/.test(line)) {
Expand All @@ -1219,6 +1353,8 @@ WebVTT.Parser.prototype = {
if (!line) {
continue;
}
console.log(line);
sawCue = true;
self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
self.state = "CUE";
// 30-39 - Check if self line contains an optional identifier or timing data.
Expand Down