Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b1d27c3
add id
gkatsev Jan 10, 2020
c0b1cb2
more region parser into separate file
gkatsev Jan 10, 2020
e5d17fc
parse VTTRegion blocks
gkatsev Jan 10, 2020
65a4d6b
simple region positioning
gkatsev Jan 13, 2020
4602c20
naive scroll:up and scroll:none support
gkatsev Jan 14, 2020
abd28bb
null check
gkatsev Jan 14, 2020
ca2f1a9
keep cues and regions in the DOM and remove old cues if they aren't i…
gkatsev Mar 20, 2020
fa3797d
add version
gkatsev Jun 1, 2020
9e7b4a7
check region
gkatsev Jun 1, 2020
44c12d7
make sure to emit pending cues, for example if there's no EOF
gkatsev Jun 1, 2020
7d067b2
move region related code to regions.js
gkatsev Jun 2, 2020
62a8123
get scroll:up working properly
gkatsev Jun 3, 2020
37ef575
get initial positionining correct
gkatsev Jun 3, 2020
fae8f4f
reset region top to account for player resizing
gkatsev Jun 3, 2020
015eb48
properly decrease region display area when removing cues
gkatsev Jun 3, 2020
683a861
adjust for multiple items being added to a region
gkatsev Jun 4, 2020
db7db2d
make batch removals slightly more reliable
gkatsev Jun 5, 2020
d9c3f2f
region cues should be relative to the container
gkatsev Jun 6, 2020
34b1839
only transition when scroll:up
gkatsev Oct 7, 2020
dc2228a
properly clear out the overlay
gkatsev Oct 7, 2020
eb80874
re-use the cue for the final parsing
gkatsev Oct 7, 2020
5dd7293
unneeded for regular non-region cues
gkatsev Oct 7, 2020
912d291
bring back styles for non-region cues.
gkatsev Oct 7, 2020
27c5baa
update processCue docs with region
gkatsev Oct 7, 2020
75701cb
if regions weren't provided to processCues, do our best
gkatsev Oct 7, 2020
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,17 @@ nodes attached to a top level div.
var div = WebVTT.convertCueToDOMTree(window, cuetext);
```

## WebVTT.processCues(window, cues, overlay)
## WebVTT.processCues(window, cues, overlay, regions)

Converts the cuetext of the cues passed to it to DOM trees—by calling convertCueToDOMTree—and
then runs the processing model steps of the WebVTT specification on the divs. The processing model applies the necessary
CSS styles to the cue divs to prepare them for display on the web page. During this process the cue divs get added
to a block level element (overlay). The overlay should be a part of the live DOM as the algorithm will use the
computed styles (only of the divs to do overlap avoidance.
Regions list should be supplied based on what was emitted with `onregion`.

```javascript
var divs = WebVTT.processCues(window, cues, overlay);
var divs = WebVTT.processCues(window, cues, overlay, regions);
```

## ParsingError
Expand Down
4 changes: 3 additions & 1 deletion lib/browser-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
// off browser.

var window = require('global/window');
var {version} = require('../package.json');

var vttjs = module.exports = {
WebVTT: require("./vtt.js"),
VTTCue: require("./vttcue.js"),
VTTRegion: require("./vttregion.js")
VTTRegion: require("./vttregion.js"),
VERSION: version
};

window.vttjs = vttjs;
Expand Down
179 changes: 106 additions & 73 deletions lib/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ Parser.prototype.reportOrThrowError = function(e) {
}
};

Parser.prototype.parse = function (data) {
Parser.prototype.parse = function (data, reuseCue) {
var self = this;
var reuseCue = reuseCue || false;

// 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
Expand Down Expand Up @@ -60,65 +61,6 @@ Parser.prototype.parse = function (data) {
return line;
}

// 3.4 WebVTT region and WebVTT region settings syntax
function parseRegion(input) {
var settings = new Settings();

parseOptions(input, function (k, v) {
switch (k) {
case "id":
settings.set(k, v);
break;
case "width":
settings.percent(k, v);
break;
case "lines":
settings.integer(k, v);
break;
case "regionanchor":
case "viewportanchor":
var xy = v.split(',');
if (xy.length !== 2) {
break;
}
// We have to make sure both x and y parse, so use a temporary
// settings object here.
var anchor = new Settings();
anchor.percent("x", xy[0]);
anchor.percent("y", xy[1]);
if (!anchor.has("x") || !anchor.has("y")) {
break;
}
settings.set(k + "X", anchor.get("x"));
settings.set(k + "Y", anchor.get("y"));
break;
case "scroll":
settings.alt(k, v, ["up"]);
break;
}
}, /=/, /\s/);

// Create the region, using default values for any values that were not
// specified.
if (settings.has("id")) {
var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
region.width = settings.get("width", 100);
region.lines = settings.get("lines", 3);
region.regionAnchorX = settings.get("regionanchorX", 0);
region.regionAnchorY = settings.get("regionanchorY", 100);
region.viewportAnchorX = settings.get("viewportanchorX", 0);
region.viewportAnchorY = settings.get("viewportanchorY", 100);
region.scroll = settings.get("scroll", "");
// Register the region.
self.onregion && self.onregion(region);
// Remember the VTTRegion for later in case we parse any VTTCues that
// reference it.
self.regionList.push({
id: settings.get("id"),
region: region
});
}
}

// draft-pantos-http-live-streaming-20
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
Expand Down Expand Up @@ -154,17 +96,7 @@ Parser.prototype.parse = function (data) {
break;
}
}, /=/);
} else {
parseOptions(input, function (k, v) {
switch (k) {
case "Region":
// 3.3 WebVTT region metadata header syntax
parseRegion(v);
break;
}
}, /:/);
}

}

// 5.1 WebVTT file parsing.
Expand All @@ -187,6 +119,12 @@ Parser.prototype.parse = function (data) {
}

var alreadyCollectedLine = false;
var sawCue = reuseCue;
if (!reuseCue) {
self.cue = null;
self.regionSettings = null;
}

while (self.buffer) {
// We can't parse a line until we have the full line.
if (!/\r\n|\n/.test(self.buffer)) {
Expand All @@ -205,16 +143,102 @@ Parser.prototype.parse = function (data) {
if (/:/.test(line)) {
parseHeader(line);
} else if (!line) {
// An empty line terminates the header and starts the body (cues).
self.state = "ID";
// An empty line terminates the header and blocks section.
self.state = "BLOCKS";
}
continue;
case "REGION":
if (!line) {
// create the region
var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
region.id = self.regionSettings.get('id', "");
region.width = self.regionSettings.get("width", 100);
region.lines = self.regionSettings.get("lines", 3);
region.regionAnchorX = self.regionSettings.get("regionanchorX", 0);
region.regionAnchorY = self.regionSettings.get("regionanchorY", 100);
region.viewportAnchorX = self.regionSettings.get("viewportanchorX", 0);
region.viewportAnchorY = self.regionSettings.get("viewportanchorY", 100);
region.scroll = self.regionSettings.get("scroll", "");
// Register the region.
self.onregion && self.onregion(region);
// Remember the VTTRegion for later in case we parse any VTTCues that reference it.
self.regionList.push({
id: region.id,
region: region
});
// An empty line terminates the REGION block
self.regionSettings = null;
self.state = "BLOCKS";
break;
}

// if it's a new region block, create a new VTTRegion
if (self.regionSettings === null) {
self.regionSettings = new Settings();
}

// parse region options and set it as appropriate on the region
parseOptions(line, function (k, v) {
switch (k) {
case "id":
self.regionSettings.set(k, v);
break;
case "width":
self.regionSettings.percent(k, v);
break;
case "lines":
self.regionSettings.integer(k, v);
break;
case "regionanchor":
case "viewportanchor":
var xy = v.split(',');
if (xy.length !== 2) {
break;
}
// We have to make sure both x and y parse, so use a temporary
// settings object here.
var anchor = new Settings();
anchor.percent("x", xy[0]);
anchor.percent("y", xy[1]);
if (!anchor.has("x") || !anchor.has("y")) {
break;
}
self.regionSettings.set(k + "X", anchor.get("x"));
self.regionSettings.set(k + "Y", anchor.get("y"));
break;
case "scroll":
self.regionSettings.alt(k, v, ["up"]);
break;
}
}, /:/, /\s/);

continue;
case "NOTE":
// Ignore NOTE blocks.
if (!line) {
self.state = "ID";
}
continue;
case "BLOCKS":
if (!line) {
continue;
}

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

// Check for the start of a REGION blocks
if (/^REGION/.test(line) && !sawCue) {
self.state = "REGION";
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 @@ -225,6 +249,7 @@ Parser.prototype.parse = function (data) {
if (!line) {
continue;
}
sawCue = true;
self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
// Safari still uses the old middle value and won't accept center
try {
Expand Down Expand Up @@ -279,6 +304,13 @@ Parser.prototype.parse = function (data) {
continue;
}
}

// if we ran out of buffer but we still have a cue, finish parsing it
if (self.cue) {
self.oncue && self.oncue(self.cue);
self.cue = null;
self.state = "ID";
}
} catch (e) {
self.reportOrThrowError(e);

Expand All @@ -287,6 +319,7 @@ Parser.prototype.parse = function (data) {
self.oncue(self.cue);
}
self.cue = null;
self.regionSettings = null;
// Enter BADWEBVTT state if header was not parsed correctly otherwise
// another exception occurred so enter BADCUE state.
self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
Expand All @@ -302,7 +335,7 @@ Parser.prototype.flush = function () {
// Synthesize the end of the current cue or region.
if (self.cue || self.state === "HEADER") {
self.buffer += "\n\n";
self.parse();
self.parse(null, true);
}
// If we've flushed, parsed, and we're still on the INITIAL state then
// that means we don't have enough of the stream to parse the first
Expand Down
20 changes: 14 additions & 6 deletions lib/process/cue-style-box.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,27 @@ function CueStyleBox(window, cue, styleOptions) {
color: "rgba(255, 255, 255, 1)",
backgroundColor: "rgba(0, 0, 0, 0.8)",
position: "relative",
left: 0,
right: 0,
top: 0,
bottom: 0,
display: "inline",
writingMode: cue.vertical === "" ? "horizontal-tb"
: cue.vertical === "lr" ? "vertical-lr"
: "vertical-rl",
unicodeBidi: "plaintext"
};

if (!cue.region) {
styles.left = 0;
styles.right = 0;
styles.top = 0;
styles.bottom = 0;
}

this.applyStyles(styles, this.cueDiv);

// Create an absolutely positioned div that will be used to position the cue
// div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
// mirrors of them except middle instead of center on Safari.
this.div = window.document.createElement("div");
this.div.className = 'vttjs-cue';
styles = {
direction: determineBidi(this.cueDiv),
writingMode: cue.vertical === "" ? "horizontal-tb"
Expand All @@ -40,11 +44,15 @@ function CueStyleBox(window, cue, styleOptions) {
unicodeBidi: "plaintext",
textAlign: cue.align === "middle" ? "center" : cue.align,
font: styleOptions.font,
whiteSpace: "pre-line",
position: "absolute"
whiteSpace: "pre-line"
};

if (!cue.region) {
styles.position = "absolute";
}

this.applyStyles(styles);

this.div.appendChild(this.cueDiv);

// Calculate the distance from the reference edge of the viewport to the text
Expand Down
11 changes: 8 additions & 3 deletions lib/process/move-box-to-line-position.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
linePos = computeLinePos(cue),
axis = [];

// If we have a line number to align the cue to.
if (cue.snapToLines) {
// If we have a line number to align the cue to.

var size;
switch (cue.vertical) {
case "":
Expand Down Expand Up @@ -78,7 +79,6 @@ function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
// If computed line position returns negative then line numbers are
// relative to the bottom of the video instead of the top. Therefore, we
// need to increase our initial position by the length or width of the
// video, depending on the writing direction, and reverse our axis directions.
if (linePos < 0) {
position += cue.vertical === "" ? containerBox.height : containerBox.width;
axis = axis.reverse();
Expand Down Expand Up @@ -127,7 +127,12 @@ function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
boxPosition = new BoxPosition(styleBox);
}

var bestPosition = findBestPosition(boxPosition, axis);
var bestPosition;
if (cue.region && cue.region.scroll === 'up') {
bestPosition = boxPosition;
} else {
bestPosition = findBestPosition(boxPosition, axis);
}
styleBox.move(bestPosition.toCSSCompatValues(containerBox));
}

Expand Down
Loading