From b1d27c3297f97638c43e50e2e8298f83b94815b9 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Fri, 10 Jan 2020 16:44:57 -0500 Subject: [PATCH 01/25] add id --- lib/vttregion.js | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/vttregion.js b/lib/vttregion.js index 4c175703..3566895f 100644 --- a/lib/vttregion.js +++ b/lib/vttregion.js @@ -31,17 +31,29 @@ function isValidPercentValue(value) { return typeof value === "number" && (value >= 0 && value <= 100); } -// VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface +// VTTRegion shim https://w3c.github.io/webvtt/#the-vttregion-interface function VTTRegion() { - var _width = 100; - var _lines = 3; - var _regionAnchorX = 0; - var _regionAnchorY = 100; - var _viewportAnchorX = 0; - var _viewportAnchorY = 100; - var _scroll = ""; + var _id = ""; // DOMString + var _width = 100; // double + var _lines = 3; // unsigned long + var _regionAnchorX = 0; // double + var _regionAnchorY = 100; // double + var _viewportAnchorX = 0; // double + var _viewportAnchorY = 100; // double + var _scroll = ""; // ScrollSetting, line 26 Object.defineProperties(this, { + "id": { + enumerable: true, + get: function() { + return _id; + }, + set: function(value) { + if (typeof value === 'string') { + _id = value; + } + } + }, "width": { enumerable: true, get: function() { @@ -63,6 +75,9 @@ function VTTRegion() { if (typeof value !== "number") { throw new TypeError("Lines must be set to a number."); } + if (typeof value < 0) { + throw new Error('lines must be a positive number.'); + } _lines = value; } }, From c0b1cb2d3d9ba7ee4bbba4ab7061ecbf40cb6fd5 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Fri, 10 Jan 2020 16:58:26 -0500 Subject: [PATCH 02/25] more region parser into separate file --- lib/parser/parse-region.js | 65 ++++++++++++++++++++++++++++++++++++++ lib/parser/parser.js | 62 ++---------------------------------- 2 files changed, 67 insertions(+), 60 deletions(-) create mode 100644 lib/parser/parse-region.js diff --git a/lib/parser/parse-region.js b/lib/parser/parse-region.js new file mode 100644 index 00000000..f94bd009 --- /dev/null +++ b/lib/parser/parse-region.js @@ -0,0 +1,65 @@ +var ParsingError = require('./parsing-error.js'); +var Settings = require('./settings.js'); +var parseOptions = require('./parse-options.js'); +var parseTimeStamp = require('./parse-timestamp.js'); + +function parseRegion(self, 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 + }); + } +} + +module.exports = parseRegion; diff --git a/lib/parser/parser.js b/lib/parser/parser.js index cff941da..c2ee2d7d 100644 --- a/lib/parser/parser.js +++ b/lib/parser/parser.js @@ -2,6 +2,7 @@ var ParsingError = require('./parsing-error.js'); var Settings = require('./settings.js'); var parseOptions = require('./parse-options.js'); var parseCue = require('./parse-cue.js'); +var parseRegion = require('./parse-region.js'); var parseTimeStamp = require('./parse-timestamp.js'); var Parser = function(window, vttjs, decoder) { @@ -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 @@ -159,7 +101,7 @@ Parser.prototype.parse = function (data) { switch (k) { case "Region": // 3.3 WebVTT region metadata header syntax - parseRegion(v); + parseRegion(self, v); break; } }, /:/); From e5d17fc500516f83431a85c601bfa3a142a2205a Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Fri, 10 Jan 2020 18:05:30 -0500 Subject: [PATCH 03/25] parse VTTRegion blocks --- lib/parser/parse-region.js | 65 ---------------------- lib/parser/parser.js | 107 ++++++++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 78 deletions(-) delete mode 100644 lib/parser/parse-region.js diff --git a/lib/parser/parse-region.js b/lib/parser/parse-region.js deleted file mode 100644 index f94bd009..00000000 --- a/lib/parser/parse-region.js +++ /dev/null @@ -1,65 +0,0 @@ -var ParsingError = require('./parsing-error.js'); -var Settings = require('./settings.js'); -var parseOptions = require('./parse-options.js'); -var parseTimeStamp = require('./parse-timestamp.js'); - -function parseRegion(self, 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 - }); - } -} - -module.exports = parseRegion; diff --git a/lib/parser/parser.js b/lib/parser/parser.js index c2ee2d7d..0a061b8c 100644 --- a/lib/parser/parser.js +++ b/lib/parser/parser.js @@ -2,7 +2,6 @@ var ParsingError = require('./parsing-error.js'); var Settings = require('./settings.js'); var parseOptions = require('./parse-options.js'); var parseCue = require('./parse-cue.js'); -var parseRegion = require('./parse-region.js'); var parseTimeStamp = require('./parse-timestamp.js'); var Parser = function(window, vttjs, decoder) { @@ -96,17 +95,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(self, v); - break; - } - }, /:/); } - } // 5.1 WebVTT file parsing. @@ -129,6 +118,10 @@ Parser.prototype.parse = function (data) { } var alreadyCollectedLine = false; + var sawCue = false; + 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)) { @@ -147,9 +140,75 @@ 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. @@ -157,6 +216,26 @@ Parser.prototype.parse = function (data) { 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)) { @@ -167,6 +246,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 { @@ -229,6 +309,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"; From 65a4d6be3595822f7775affde03ed330384595ff Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Mon, 13 Jan 2020 18:01:32 -0500 Subject: [PATCH 04/25] simple region positioning --- lib/process/process-cues.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 3e83be73..9c1c17c0 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -9,7 +9,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. -var processCues = function(window, cues, overlay) { +var processCues = function(window, cues, overlay, regions) { if (!window || !cues || !overlay) { return null; } @@ -19,6 +19,25 @@ var processCues = function(window, cues, overlay) { overlay.removeChild(overlay.firstChild); } + regions = regions || []; + + for (var i = 0; i < regions.length; i++) { + var region = regions[i]; + var display = region.displayState = window.document.createElement('div') + var overlayHeight = overlay.offsetHeight; + var height = Math.round(overlayHeight / 100) * 6 * region.lines; + var width = region.width; + display.classList.add(region.id); + display.style.width = width + '%'; + display.style.height = height + 'px'; + display.style.position = "absolute"; + display.style.left = `calc(${region.viewportAnchorX}% - ${width * region.regionAnchorX / 100}%`; + display.style.top = overlayHeight * region.viewportAnchorY / 100 - height * region.regionAnchorY / 100 + 'px'; + display.style.margin = CUE_BACKGROUND_PADDING; + + overlay.appendChild(display); + } + var paddedOverlay = window.document.createElement("div"); paddedOverlay.style.position = "absolute"; paddedOverlay.style.left = "0"; @@ -43,7 +62,11 @@ var processCues = function(window, cues, overlay) { // We don't need to recompute the cues' display states. Just reuse them. if (!shouldCompute(cues)) { for (var i = 0; i < cues.length; i++) { - paddedOverlay.appendChild(cues[i].displayState); + if (cues[i].region) { + region.displayState.appendChild(cues[i].displayState); + } else { + paddedOverlay.appendChild(cues[i].displayState); + } } return; } @@ -63,7 +86,12 @@ var processCues = function(window, cues, overlay) { // Compute the intial position and styles of the cue div. styleBox = new CueStyleBox(window, cue, styleOptions); - paddedOverlay.appendChild(styleBox.div); + if (cue.region) { + cue.region.displayState.appendChild(styleBox.div); + containerBox = BoxPosition.getSimpleBoxPosition(cue.region.displayState); + } else { + paddedOverlay.appendChild(styleBox.div); + } // Move the cue div to it's correct line position. moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); From 4602c2001a0a05d85fbcd9598a8760bf894ec395 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Tue, 14 Jan 2020 14:10:00 -0500 Subject: [PATCH 05/25] naive scroll:up and scroll:none support --- lib/process/move-box-to-line-position.js | 8 ++++++-- lib/process/process-cues.js | 14 +++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/process/move-box-to-line-position.js b/lib/process/move-box-to-line-position.js index 4e548e62..e60847ae 100644 --- a/lib/process/move-box-to-line-position.js +++ b/lib/process/move-box-to-line-position.js @@ -78,7 +78,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(); @@ -127,7 +126,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)); } diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 9c1c17c0..dfecfbd5 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -34,6 +34,9 @@ var processCues = function(window, cues, overlay, regions) { display.style.left = `calc(${region.viewportAnchorX}% - ${width * region.regionAnchorX / 100}%`; display.style.top = overlayHeight * region.viewportAnchorY / 100 - height * region.regionAnchorY / 100 + 'px'; display.style.margin = CUE_BACKGROUND_PADDING; + display.style.overflow = "hidden"; + display.style.transitionProperty = 'top'; + display.style.transitionDuration = '0.433s'; overlay.appendChild(display); } @@ -89,12 +92,21 @@ var processCues = function(window, cues, overlay, regions) { if (cue.region) { cue.region.displayState.appendChild(styleBox.div); containerBox = BoxPosition.getSimpleBoxPosition(cue.region.displayState); + if (cue.region.scroll === 'up') { + for (var j = 0; j < cue.region.displayState.children.length; j++) { + var c = cue.region.displayState.children[j]; + var top = parseInt(c.style.top, 10); + console.log(c.style.top, '!!1'); + c.style.top = top - styleBox.div.offsetHeight + 'px'; + console.log(c.style.top, '!!'); + } + } } else { paddedOverlay.appendChild(styleBox.div); } // Move the cue div to it's correct line position. - moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions, cue.region); // Remember the computed div so that we don't have to recompute it later // if we don't have too. From abd28bb397c92e2861c2e2af4828a8722329214c Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Tue, 14 Jan 2020 16:27:13 -0500 Subject: [PATCH 06/25] null check --- lib/process/cue-style-box.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/process/cue-style-box.js b/lib/process/cue-style-box.js index bdcfd8ee..04425f78 100644 --- a/lib/process/cue-style-box.js +++ b/lib/process/cue-style-box.js @@ -44,6 +44,11 @@ function CueStyleBox(window, cue, styleOptions) { position: "absolute" }; + if (cue.region && cue.region.scroll === 'up') { + styles.transitionProperty = 'top'; + styles.transitionDuration = '0.433s'; + } + this.applyStyles(styles); this.div.appendChild(this.cueDiv); From ca2f1a9c8d97aee3a94abe6a893b0d0046ae37bf Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Fri, 20 Mar 2020 16:07:27 -0400 Subject: [PATCH 07/25] keep cues and regions in the DOM and remove old cues if they aren't in the new list --- lib/process/cue-style-box.js | 1 + lib/process/process-cues.js | 50 ++++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/process/cue-style-box.js b/lib/process/cue-style-box.js index 04425f78..b3fa32e3 100644 --- a/lib/process/cue-style-box.js +++ b/lib/process/cue-style-box.js @@ -32,6 +32,7 @@ function CueStyleBox(window, cue, styleOptions) { // 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" diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index dfecfbd5..a4c3bf15 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -14,15 +14,31 @@ var processCues = function(window, cues, overlay, regions) { return null; } - // Remove all previous children. - while (overlay.firstChild) { - overlay.removeChild(overlay.firstChild); + var cueEls = overlay.querySelectorAll('.vttjs-cue'); + console.log(cues, cueEls); + for (var i = 0; i < cueEls.length; i++) { + var el = cueEls[i]; + var keep = false; + for (var j = 0; j < cues.length; j++) { + var cue = cues[j]; + if (cue.displayState && el === cue.displayState) { + keep = true; + } + } + if (!keep) { + el.parentElement.removeChild(el); + } } regions = regions || []; for (var i = 0; i < regions.length; i++) { var region = regions[i]; + + if (region.displayState && region.displayState.parentElement === overlay) { + continue; + } + var display = region.displayState = window.document.createElement('div') var overlayHeight = overlay.offsetHeight; var height = Math.round(overlayHeight / 100) * 6 * region.lines; @@ -41,14 +57,19 @@ var processCues = function(window, cues, overlay, regions) { overlay.appendChild(display); } - var paddedOverlay = window.document.createElement("div"); - paddedOverlay.style.position = "absolute"; - paddedOverlay.style.left = "0"; - paddedOverlay.style.right = "0"; - paddedOverlay.style.top = "0"; - paddedOverlay.style.bottom = "0"; - paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; - overlay.appendChild(paddedOverlay); + var paddedOverlay = overlay.querySelector('.vttjs-padded-overlay'); + + if (!paddedOverlay) { + paddedOverlay = window.document.createElement("div"); + paddedOverlay.className = 'vttjs-padded-overlay' + paddedOverlay.style.position = "absolute"; + paddedOverlay.style.left = "0"; + paddedOverlay.style.right = "0"; + paddedOverlay.style.top = "0"; + paddedOverlay.style.bottom = "0"; + paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; + overlay.appendChild(paddedOverlay); + } // Determine if we need to compute the display states of the cues. This could // be the case if a cue's state has been changed since the last computation or @@ -87,6 +108,13 @@ var processCues = function(window, cues, overlay, regions) { for (var i = 0; i < cues.length; i++) { cue = cues[i]; + if (cue.displayState && + (cue.displayState.parentElement === paddedOverlay || + cue.displayState.parentElement === cue.region.displayState) + ) { + continue; + } + // Compute the intial position and styles of the cue div. styleBox = new CueStyleBox(window, cue, styleOptions); if (cue.region) { From fa3797d81c66a7425a7393a5dc5c2df33be635ad Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Mon, 1 Jun 2020 13:36:07 -0400 Subject: [PATCH 08/25] add version --- lib/browser-index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/browser-index.js b/lib/browser-index.js index 35a47347..54aed413 100644 --- a/lib/browser-index.js +++ b/lib/browser-index.js @@ -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; From 9e7b4a7717e0f5f88c1c43f3ff5b7329c11b6776 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Mon, 1 Jun 2020 13:36:24 -0400 Subject: [PATCH 09/25] check region --- lib/process/process-cues.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index a4c3bf15..5a0f749d 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -110,7 +110,7 @@ var processCues = function(window, cues, overlay, regions) { if (cue.displayState && (cue.displayState.parentElement === paddedOverlay || - cue.displayState.parentElement === cue.region.displayState) + cue.region && cue.displayState.parentElement === cue.region.displayState) ) { continue; } From 44c12d7972e5feddcdf6ce294f7ca56cee116d3d Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Mon, 1 Jun 2020 13:58:56 -0400 Subject: [PATCH 10/25] make sure to emit pending cues, for example if there's no EOF --- lib/parser/parser.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/parser/parser.js b/lib/parser/parser.js index 0a061b8c..0b476384 100644 --- a/lib/parser/parser.js +++ b/lib/parser/parser.js @@ -301,6 +301,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); From 7d067b2d281ad35610a4f56768a819df7b0987b8 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Tue, 2 Jun 2020 16:31:23 -0400 Subject: [PATCH 11/25] move region related code to regions.js --- lib/process/process-cues.js | 57 +++++++------------------- lib/process/regions.js | 79 +++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 42 deletions(-) create mode 100644 lib/process/regions.js diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 5a0f749d..25830dd6 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -1,6 +1,12 @@ var BoxPosition = require('./box-position.js'); var CueStyleBox = require('./cue-style-box.js'); var moveBoxToLinePosition = require('./move-box-to-line-position.js'); +var { + setupRegions, + readdRegionCue, + handleRegionCue, + cueInRegion +} = require('./regions.js'); var FONT_SIZE_PERCENT = 0.05; var FONT_STYLE = "sans-serif"; @@ -15,7 +21,6 @@ var processCues = function(window, cues, overlay, regions) { } var cueEls = overlay.querySelectorAll('.vttjs-cue'); - console.log(cues, cueEls); for (var i = 0; i < cueEls.length; i++) { var el = cueEls[i]; var keep = false; @@ -30,33 +35,10 @@ var processCues = function(window, cues, overlay, regions) { } } - regions = regions || []; - - for (var i = 0; i < regions.length; i++) { - var region = regions[i]; - - if (region.displayState && region.displayState.parentElement === overlay) { - continue; - } - - var display = region.displayState = window.document.createElement('div') - var overlayHeight = overlay.offsetHeight; - var height = Math.round(overlayHeight / 100) * 6 * region.lines; - var width = region.width; - display.classList.add(region.id); - display.style.width = width + '%'; - display.style.height = height + 'px'; - display.style.position = "absolute"; - display.style.left = `calc(${region.viewportAnchorX}% - ${width * region.regionAnchorX / 100}%`; - display.style.top = overlayHeight * region.viewportAnchorY / 100 - height * region.regionAnchorY / 100 + 'px'; - display.style.margin = CUE_BACKGROUND_PADDING; - display.style.overflow = "hidden"; - display.style.transitionProperty = 'top'; - display.style.transitionDuration = '0.433s'; - - overlay.appendChild(display); - } + // setup region overlays + setupRegions(regions, overlay); + // setup main overlay var paddedOverlay = overlay.querySelector('.vttjs-padded-overlay'); if (!paddedOverlay) { @@ -87,7 +69,7 @@ var processCues = function(window, cues, overlay, regions) { if (!shouldCompute(cues)) { for (var i = 0; i < cues.length; i++) { if (cues[i].region) { - region.displayState.appendChild(cues[i].displayState); + readdRegionCue(cues[i]); } else { paddedOverlay.appendChild(cues[i].displayState); } @@ -107,28 +89,19 @@ var processCues = function(window, cues, overlay, regions) { for (var i = 0; i < cues.length; i++) { cue = cues[i]; - + // if cue is already displaying, we don't need to position it if (cue.displayState && - (cue.displayState.parentElement === paddedOverlay || - cue.region && cue.displayState.parentElement === cue.region.displayState) + (cue.displayState.parentElement === paddedOverlay || + cueInRegion(cue)) ) { continue; } // Compute the intial position and styles of the cue div. styleBox = new CueStyleBox(window, cue, styleOptions); + if (cue.region) { - cue.region.displayState.appendChild(styleBox.div); - containerBox = BoxPosition.getSimpleBoxPosition(cue.region.displayState); - if (cue.region.scroll === 'up') { - for (var j = 0; j < cue.region.displayState.children.length; j++) { - var c = cue.region.displayState.children[j]; - var top = parseInt(c.style.top, 10); - console.log(c.style.top, '!!1'); - c.style.top = top - styleBox.div.offsetHeight + 'px'; - console.log(c.style.top, '!!'); - } - } + containerBox = handleRegionCue(cue, styleBox) } else { paddedOverlay.appendChild(styleBox.div); } diff --git a/lib/process/regions.js b/lib/process/regions.js new file mode 100644 index 00000000..5b04eb27 --- /dev/null +++ b/lib/process/regions.js @@ -0,0 +1,79 @@ +var BoxPosition = require('./box-position.js'); +var CUE_BACKGROUND_PADDING = "1.5%"; + +function setupRegions(regions, overlay) { + regions = regions || []; + + for (var i = 0; i < regions.length; i++) { + var region = regions[i]; + + if (region.displayState && region.displayState.parentElement === overlay) { + continue; + } + + var display = region.displayState = window.document.createElement('div') + var overlayHeight = overlay.offsetHeight; + var height = Math.round(overlayHeight / 100) * 6 * region.lines; + var width = region.width; + display.classList.add(region.id); + display.style.width = width + '%'; + display.style.height = height + 'px'; + display.style.position = "absolute"; + display.style.left = `calc(${region.viewportAnchorX}% - ${width * region.regionAnchorX / 100}%`; + display.style.top = overlayHeight * region.viewportAnchorY / 100 - height * region.regionAnchorY / 100 + 'px'; + display.style.margin = CUE_BACKGROUND_PADDING; + display.style.overflow = "hidden"; + + + var innerDisplay = window.document.createElement('div'); + innerDisplay.style.width = '100%'; + innerDisplay.style.height = 'auto'; + innerDisplay.style.position = "absolute"; + innerDisplay.style.bottom = 0; + innerDisplay.style.transitionProperty = 'height'; + innerDisplay.style.transitionDuration = '0.433s'; + + display.appendChild(innerDisplay); + overlay.appendChild(display); + } +} + +function readdRegionCue(cue) { + region.displayState.firstChild.appendChild(cue.displayState); +} + +function handleRegionCue(cue, styleBox) { + var regionDisplay = cue.region.displayState.firstChild; + var rH = regionDisplay.offsetHeight; + + cue.region.displayState.firstChild.appendChild(styleBox.div); + + var cH = styleBox.div.offsetHeight; + + regionDisplay.style.height = rH+cH + 'px'; + var containerBox = BoxPosition.getSimpleBoxPosition(cue.region.displayState.firstChild); + if (cue.region.scroll === 'up') { + styleBox.div.style.top = 'auto'; + // for (var j = 0; j < cue.region.displayState.firstChild.children.length; j++) { + // var c = cue.region.displayState.firstChild.children[j]; + // var top = parseInt(c.style.top, 10); + // console.log(c.style.top, '!!1'); + // c.style.top = 'auto';// top - styleBox.div.offsetHeight + 'px'; + // console.log(c.style.top, '!!'); + // } + } + + return containerBox; +} + +function cueInRegion(cue) { + return cue.region && + cue.displayState.parentElement === cue.region.displayState.firstChild; +} + +module.exports = { + setupRegions, + readdRegionCue, + handleRegionCue, + cueInRegion +}; From 62a812366edd2dfdf6717c973c53406a6c94ee84 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 3 Jun 2020 14:28:47 -0400 Subject: [PATCH 12/25] get scroll:up working properly --- lib/process/process-cues.js | 8 +++++++- lib/process/regions.js | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 25830dd6..bb21cbf5 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -4,6 +4,7 @@ var moveBoxToLinePosition = require('./move-box-to-line-position.js'); var { setupRegions, readdRegionCue, + removeRegionCue, handleRegionCue, cueInRegion } = require('./regions.js'); @@ -20,6 +21,7 @@ var processCues = function(window, cues, overlay, regions) { return null; } + // remove cues that aren't in the list of cues we were just given var cueEls = overlay.querySelectorAll('.vttjs-cue'); for (var i = 0; i < cueEls.length; i++) { var el = cueEls[i]; @@ -31,7 +33,11 @@ var processCues = function(window, cues, overlay, regions) { } } if (!keep) { - el.parentElement.removeChild(el); + if (el.parentElement.classList.contains('vttjs-region-display')) { + removeRegionCue(el); + } else { + el.parentElement.removeChild(el); + } } } diff --git a/lib/process/regions.js b/lib/process/regions.js index 5b04eb27..3f4e67df 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -15,7 +15,7 @@ function setupRegions(regions, overlay) { var overlayHeight = overlay.offsetHeight; var height = Math.round(overlayHeight / 100) * 6 * region.lines; var width = region.width; - display.classList.add(region.id); + display.classList.add(region.id, 'vttjs-region'); display.style.width = width + '%'; display.style.height = height + 'px'; display.style.position = "absolute"; @@ -26,6 +26,7 @@ function setupRegions(regions, overlay) { var innerDisplay = window.document.createElement('div'); + innerDisplay.classList.add('vttjs-region-display'); innerDisplay.style.width = '100%'; innerDisplay.style.height = 'auto'; innerDisplay.style.position = "absolute"; @@ -39,7 +40,18 @@ function setupRegions(regions, overlay) { } function readdRegionCue(cue) { - region.displayState.firstChild.appendChild(cue.displayState); + cue.region.displayState.firstChild.appendChild(cue.displayState); + cue.region.displayState.firstChild.style.height = 'auto'; +} + +function removeRegionCue(el) { + var regionDisplay = el.parentElement; + var rH = regionDisplay.offsetHeight; + + regionDisplay.removeChild(el); + + var rH = regionDisplay.offsetHeight; + regionDisplay.style.height = rH + 'px'; } function handleRegionCue(cue, styleBox) { @@ -53,7 +65,7 @@ function handleRegionCue(cue, styleBox) { regionDisplay.style.height = rH+cH + 'px'; var containerBox = BoxPosition.getSimpleBoxPosition(cue.region.displayState.firstChild); if (cue.region.scroll === 'up') { - styleBox.div.style.top = 'auto'; + // styleBox.div.style.top = 'auto'; // for (var j = 0; j < cue.region.displayState.firstChild.children.length; j++) { // var c = cue.region.displayState.firstChild.children[j]; // var top = parseInt(c.style.top, 10); @@ -74,6 +86,7 @@ function cueInRegion(cue) { module.exports = { setupRegions, readdRegionCue, + removeRegionCue, handleRegionCue, cueInRegion }; From 37ef57567bbc363b398821f0412eab940eb9f7e1 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 3 Jun 2020 16:28:46 -0400 Subject: [PATCH 13/25] get initial positionining correct --- lib/process/process-cues.js | 3 +++ lib/process/regions.js | 13 ++++++++++--- lib/process/style-box.js | 3 +++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index bb21cbf5..1fd7a590 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -112,6 +112,9 @@ var processCues = function(window, cues, overlay, regions) { paddedOverlay.appendChild(styleBox.div); } + if (cue.region.id === 'fred') { + console.log(cue.text, containerBox); + } // Move the cue div to it's correct line position. moveBoxToLinePosition(window, styleBox, containerBox, boxPositions, cue.region); diff --git a/lib/process/regions.js b/lib/process/regions.js index 3f4e67df..801db84e 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -28,7 +28,7 @@ function setupRegions(regions, overlay) { var innerDisplay = window.document.createElement('div'); innerDisplay.classList.add('vttjs-region-display'); innerDisplay.style.width = '100%'; - innerDisplay.style.height = 'auto'; + innerDisplay.style.height = '0px'; innerDisplay.style.position = "absolute"; innerDisplay.style.bottom = 0; innerDisplay.style.transitionProperty = 'height'; @@ -58,12 +58,19 @@ function handleRegionCue(cue, styleBox) { var regionDisplay = cue.region.displayState.firstChild; var rH = regionDisplay.offsetHeight; - cue.region.displayState.firstChild.appendChild(styleBox.div); + regionDisplay.appendChild(styleBox.div); var cH = styleBox.div.offsetHeight; + if (cue.region.id === 'fred') { + console.log(cue.text, regionDisplay.style.height, regionDisplay.offsetHeight); + } + regionDisplay.style.height = rH+cH + 'px'; - var containerBox = BoxPosition.getSimpleBoxPosition(cue.region.displayState.firstChild); + var containerBox = BoxPosition.getSimpleBoxPosition(regionDisplay); + if (cue.region.id === 'fred') { + console.log(cue.text, regionDisplay.style.height, regionDisplay.offsetHeight); + } if (cue.region.scroll === 'up') { // styleBox.div.style.top = 'auto'; // for (var j = 0; j < cue.region.displayState.firstChild.children.length; j++) { diff --git a/lib/process/style-box.js b/lib/process/style-box.js index 3da5be09..1bf0b27a 100644 --- a/lib/process/style-box.js +++ b/lib/process/style-box.js @@ -5,6 +5,9 @@ function StyleBox() { // div on 'this'. StyleBox.prototype.applyStyles = function(styles, div) { div = div || this.div; + if (this.cue.region.id === 'fred' && 'top' in styles) { + console.log(this.cue.text,'top', styles.top); + } for (var prop in styles) { if (Object.prototype.hasOwnProperty.call(styles, prop)) { div.style[prop] = styles[prop]; From fae8f4f2f6c80205293396ef372ab18f5dfda9de Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 3 Jun 2020 16:45:08 -0400 Subject: [PATCH 14/25] reset region top to account for player resizing --- lib/process/regions.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/process/regions.js b/lib/process/regions.js index 801db84e..fc268535 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -8,6 +8,12 @@ function setupRegions(regions, overlay) { var region = regions[i]; if (region.displayState && region.displayState.parentElement === overlay) { + var overlayHeight = overlay.offsetHeight; + var height = Math.round(overlayHeight / 100) * 6 * region.lines; + var newTop = overlayHeight * region.viewportAnchorY / 100 - height * region.regionAnchorY / 100 + 'px'; + if (region.displayState.style.top !== newTop) { + region.displayState.style.top = newTop; + } continue; } From 015eb48018b8d37f3ffd225357b5ee333260aa3c Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 3 Jun 2020 17:29:14 -0400 Subject: [PATCH 15/25] properly decrease region display area when removing cues --- lib/process/regions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/process/regions.js b/lib/process/regions.js index fc268535..ccbe2379 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -53,11 +53,12 @@ function readdRegionCue(cue) { function removeRegionCue(el) { var regionDisplay = el.parentElement; var rH = regionDisplay.offsetHeight; + var cH = el.offsetHeight; regionDisplay.removeChild(el); var rH = regionDisplay.offsetHeight; - regionDisplay.style.height = rH + 'px'; + regionDisplay.style.height = rH - cH + 'px'; } function handleRegionCue(cue, styleBox) { From 683a86113c0cb5c85370493ab5ec136e23336d39 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Thu, 4 Jun 2020 16:44:13 -0400 Subject: [PATCH 16/25] adjust for multiple items being added to a region --- lib/process/process-cues.js | 12 ++++++++++-- lib/process/regions.js | 15 ++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 1fd7a590..07bbc879 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -91,7 +91,8 @@ var processCues = function(window, cues, overlay, regions) { }; (function() { - var styleBox, cue; + var styleBox, cue, processedRegionCue = false; + var regionCueCounts = {}; for (var i = 0; i < cues.length; i++) { cue = cues[i]; @@ -107,7 +108,14 @@ var processCues = function(window, cues, overlay, regions) { styleBox = new CueStyleBox(window, cue, styleOptions); if (cue.region) { - containerBox = handleRegionCue(cue, styleBox) + var regionId = cue.region.id; + if (!(regionId in regionCueCounts)) { + regionCueCounts[regionId] = 0; + } else { + regionCueCounts[regionId]++; + } + containerBox = handleRegionCue(cue, styleBox, regionCueCounts[regionId]) + processedRegionCue = true; } else { paddedOverlay.appendChild(styleBox.div); } diff --git a/lib/process/regions.js b/lib/process/regions.js index ccbe2379..3cb1def4 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -52,7 +52,7 @@ function readdRegionCue(cue) { function removeRegionCue(el) { var regionDisplay = el.parentElement; - var rH = regionDisplay.offsetHeight; + var rH = parseInt(regionDisplay.style.height); var cH = el.offsetHeight; regionDisplay.removeChild(el); @@ -61,22 +61,27 @@ function removeRegionCue(el) { regionDisplay.style.height = rH - cH + 'px'; } -function handleRegionCue(cue, styleBox) { +function handleRegionCue(cue, styleBox, adjust) { var regionDisplay = cue.region.displayState.firstChild; - var rH = regionDisplay.offsetHeight; + var rH = parseInt(regionDisplay.style.height, 10); regionDisplay.appendChild(styleBox.div); var cH = styleBox.div.offsetHeight; if (cue.region.id === 'fred') { - console.log(cue.text, regionDisplay.style.height, regionDisplay.offsetHeight); + console.log(cue.text, 'before', rH, regionDisplay.style.height, regionDisplay.offsetHeight, regionDisplay.clientHeight); } regionDisplay.style.height = rH+cH + 'px'; var containerBox = BoxPosition.getSimpleBoxPosition(regionDisplay); + if (adjust) { + containerBox.top -= adjust*cH; + containerBox.height *= adjust; + } + if (cue.region.id === 'fred') { - console.log(cue.text, regionDisplay.style.height, regionDisplay.offsetHeight); + console.log(cue.text, 'after', regionDisplay.style.height, regionDisplay.offsetHeight, regionDisplay.clientHeight); } if (cue.region.scroll === 'up') { // styleBox.div.style.top = 'auto'; From db7db2dcdd2b6da4e1be7f6f7aac43f1aa1925a2 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Fri, 5 Jun 2020 12:44:18 -0400 Subject: [PATCH 17/25] make batch removals slightly more reliable --- lib/process/process-cues.js | 65 +++++++++++++++++++++++++------------ lib/process/regions.js | 6 ++-- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 07bbc879..5ed9b738 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -21,26 +21,6 @@ var processCues = function(window, cues, overlay, regions) { return null; } - // remove cues that aren't in the list of cues we were just given - var cueEls = overlay.querySelectorAll('.vttjs-cue'); - for (var i = 0; i < cueEls.length; i++) { - var el = cueEls[i]; - var keep = false; - for (var j = 0; j < cues.length; j++) { - var cue = cues[j]; - if (cue.displayState && el === cue.displayState) { - keep = true; - } - } - if (!keep) { - if (el.parentElement.classList.contains('vttjs-region-display')) { - removeRegionCue(el); - } else { - el.parentElement.removeChild(el); - } - } - } - // setup region overlays setupRegions(regions, overlay); @@ -59,6 +39,51 @@ var processCues = function(window, cues, overlay, regions) { overlay.appendChild(paddedOverlay); } + + // remove cues that aren't in the list of cues we were just given + var cueEls = paddedOverlay.querySelectorAll('.vttjs-cue'); + clearOldCues(cueEls); + + for (var i = 0; i < regions.length; i++) { + var regionDisplay = regions[i].displayState.firstChild; + var cueEls = regionDisplay.querySelectorAll('.vttjs-cue'); + var [height, removed] = clearOldCues(cueEls, cues, true); + + if (removed > 1) { + regionDisplay.style.height = height + 'px'; + } + }; + + function clearOldCues(cueEls, cues) { + var keptCuesHeight = 0; + var removed = 0; + + for (var i = 0; i < cueEls.length; i++) { + var el = cueEls[i]; + var keep = false; + for (var j = 0; j < cues.length; j++) { + var cue = cues[j]; + if (cue.displayState && el === cue.displayState) { + keep = true; + if (cue.region) { + keptCuesHeight += cue.displayState.offsetHeight; + } + break; + } + } + if (!keep) { + if (el.parentElement.classList.contains('vttjs-region-display')) { + removeRegionCue(el); + removed++; + } else { + el.parentElement.removeChild(el); + } + } + } + + return [keptCuesHeight, removed]; + } + // Determine if we need to compute the display states of the cues. This could // be the case if a cue's state has been changed since the last computation or // if it has not been computed yet. diff --git a/lib/process/regions.js b/lib/process/regions.js index 3cb1def4..7bc54904 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -50,15 +50,17 @@ function readdRegionCue(cue) { cue.region.displayState.firstChild.style.height = 'auto'; } -function removeRegionCue(el) { +function removeRegionCue(el, adjust) { var regionDisplay = el.parentElement; - var rH = parseInt(regionDisplay.style.height); + var rH = parseInt(regionDisplay.style.height, 10); var cH = el.offsetHeight; regionDisplay.removeChild(el); var rH = regionDisplay.offsetHeight; regionDisplay.style.height = rH - cH + 'px'; + + return cH; } function handleRegionCue(cue, styleBox, adjust) { From d9c3f2f6b9ec0ff6f8a712a4ca644e7ad99d33a2 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Sat, 6 Jun 2020 12:55:56 -0400 Subject: [PATCH 18/25] region cues should be relative to the container This simplifies a lot of things for region cues. It probably don't need to keep track of box positions for them but we also don't need to do the moveBoxToLinePosition as they will get positioned as part of the regular flow of things. --- lib/process/cue-style-box.js | 44 +++++++++++++----------- lib/process/move-box-to-line-position.js | 3 +- lib/process/process-cues.js | 33 +++++++++++------- lib/process/regions.js | 28 +-------------- lib/process/style-box.js | 3 -- 5 files changed, 47 insertions(+), 64 deletions(-) diff --git a/lib/process/cue-style-box.js b/lib/process/cue-style-box.js index b3fa32e3..9989af22 100644 --- a/lib/process/cue-style-box.js +++ b/lib/process/cue-style-box.js @@ -15,10 +15,6 @@ 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" @@ -41,16 +37,22 @@ 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 && cue.region.scroll === 'up') { - styles.transitionProperty = 'top'; - styles.transitionDuration = '0.433s'; + this.applyStyles(styles); + + if (!cue.region) { + styles = { + left: 0, + right: 0, + top: 0, + bottom: 0, + position: "absolute" + }; + this.applyStyles(styles); } - this.applyStyles(styles); this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text @@ -87,16 +89,18 @@ function CueStyleBox(window, cue, styleOptions) { }); } - this.move = function(box) { - this.applyStyles({ - top: this.formatStyle(box.top, "px"), - bottom: this.formatStyle(box.bottom, "px"), - left: this.formatStyle(box.left, "px"), - right: this.formatStyle(box.right, "px"), - height: this.formatStyle(box.height, "px"), - width: this.formatStyle(box.width, "px") - }); - }; + if (!cue.region) { + this.move = function(box) { + this.applyStyles({ + top: this.formatStyle(box.top, "px"), + bottom: this.formatStyle(box.bottom, "px"), + left: this.formatStyle(box.left, "px"), + right: this.formatStyle(box.right, "px"), + height: this.formatStyle(box.height, "px"), + width: this.formatStyle(box.width, "px") + }); + }; + } } CueStyleBox.prototype = Object.create(StyleBox.prototype); CueStyleBox.prototype.constructor = CueStyleBox; diff --git a/lib/process/move-box-to-line-position.js b/lib/process/move-box-to-line-position.js index e60847ae..f3993e23 100644 --- a/lib/process/move-box-to-line-position.js +++ b/lib/process/move-box-to-line-position.js @@ -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 "": diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 5ed9b738..09e55e0a 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -109,6 +109,7 @@ var processCues = function(window, cues, overlay, regions) { } var boxPositions = [], + regionBoxPositions = new Map(), containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; var styleOptions = { @@ -116,7 +117,7 @@ var processCues = function(window, cues, overlay, regions) { }; (function() { - var styleBox, cue, processedRegionCue = false; + var styleBox, cue; var regionCueCounts = {}; for (var i = 0; i < cues.length; i++) { @@ -133,29 +134,35 @@ var processCues = function(window, cues, overlay, regions) { styleBox = new CueStyleBox(window, cue, styleOptions); if (cue.region) { + if (!regionBoxPositions.has(cue.region)) { + regionBoxPositions.set(cue.region, []); + } + + let boxPositions = regionBoxPositions.get(cue.region); + var regionId = cue.region.id; if (!(regionId in regionCueCounts)) { - regionCueCounts[regionId] = 0; - } else { - regionCueCounts[regionId]++; + regionCueCounts[regionId] = -1; } - containerBox = handleRegionCue(cue, styleBox, regionCueCounts[regionId]) - processedRegionCue = true; + + handleRegionCue(cue, styleBox, regionCueCounts[regionId]++) + + boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); + + regionBoxPositions.set(cue.region, boxPositions); + } else { paddedOverlay.appendChild(styleBox.div); - } - if (cue.region.id === 'fred') { - console.log(cue.text, containerBox); + // Move the cue div to it's correct line position. + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions, cue.region); + + boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); } - // Move the cue div to it's correct line position. - moveBoxToLinePosition(window, styleBox, containerBox, boxPositions, cue.region); // Remember the computed div so that we don't have to recompute it later // if we don't have too. cue.displayState = styleBox.div; - - boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); } })(); }; diff --git a/lib/process/regions.js b/lib/process/regions.js index 7bc54904..fb3b2287 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -50,7 +50,7 @@ function readdRegionCue(cue) { cue.region.displayState.firstChild.style.height = 'auto'; } -function removeRegionCue(el, adjust) { +function removeRegionCue(el) { var regionDisplay = el.parentElement; var rH = parseInt(regionDisplay.style.height, 10); var cH = el.offsetHeight; @@ -70,33 +70,7 @@ function handleRegionCue(cue, styleBox, adjust) { regionDisplay.appendChild(styleBox.div); var cH = styleBox.div.offsetHeight; - - if (cue.region.id === 'fred') { - console.log(cue.text, 'before', rH, regionDisplay.style.height, regionDisplay.offsetHeight, regionDisplay.clientHeight); - } - regionDisplay.style.height = rH+cH + 'px'; - var containerBox = BoxPosition.getSimpleBoxPosition(regionDisplay); - if (adjust) { - containerBox.top -= adjust*cH; - containerBox.height *= adjust; - } - - if (cue.region.id === 'fred') { - console.log(cue.text, 'after', regionDisplay.style.height, regionDisplay.offsetHeight, regionDisplay.clientHeight); - } - if (cue.region.scroll === 'up') { - // styleBox.div.style.top = 'auto'; - // for (var j = 0; j < cue.region.displayState.firstChild.children.length; j++) { - // var c = cue.region.displayState.firstChild.children[j]; - // var top = parseInt(c.style.top, 10); - // console.log(c.style.top, '!!1'); - // c.style.top = 'auto';// top - styleBox.div.offsetHeight + 'px'; - // console.log(c.style.top, '!!'); - // } - } - - return containerBox; } function cueInRegion(cue) { diff --git a/lib/process/style-box.js b/lib/process/style-box.js index 1bf0b27a..3da5be09 100644 --- a/lib/process/style-box.js +++ b/lib/process/style-box.js @@ -5,9 +5,6 @@ function StyleBox() { // div on 'this'. StyleBox.prototype.applyStyles = function(styles, div) { div = div || this.div; - if (this.cue.region.id === 'fred' && 'top' in styles) { - console.log(this.cue.text,'top', styles.top); - } for (var prop in styles) { if (Object.prototype.hasOwnProperty.call(styles, prop)) { div.style[prop] = styles[prop]; From 34b18396cc2cf398c0ea9f318e3cd92130fcc477 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 7 Oct 2020 16:06:22 -0400 Subject: [PATCH 19/25] only transition when scroll:up --- lib/process/regions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/process/regions.js b/lib/process/regions.js index fb3b2287..c8cf43d5 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -37,8 +37,11 @@ function setupRegions(regions, overlay) { innerDisplay.style.height = '0px'; innerDisplay.style.position = "absolute"; innerDisplay.style.bottom = 0; - innerDisplay.style.transitionProperty = 'height'; - innerDisplay.style.transitionDuration = '0.433s'; + + if (region.scroll === 'up') { + innerDisplay.style.transitionProperty = 'height'; + innerDisplay.style.transitionDuration = '0.433s'; + } display.appendChild(innerDisplay); overlay.appendChild(display); From dc2228ab936238a52c6ab63d44131eb007bb51b4 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 7 Oct 2020 16:33:31 -0400 Subject: [PATCH 20/25] properly clear out the overlay --- lib/process/process-cues.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 09e55e0a..71306190 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -17,6 +17,8 @@ var CUE_BACKGROUND_PADDING = "1.5%"; // @param overlay A block level element (usually a div) that the computed cues // and regions will be placed into. var processCues = function(window, cues, overlay, regions) { + regions = regions || []; + if (!window || !cues || !overlay) { return null; } @@ -44,8 +46,13 @@ var processCues = function(window, cues, overlay, regions) { var cueEls = paddedOverlay.querySelectorAll('.vttjs-cue'); clearOldCues(cueEls); + if (regions.length === 0) { + var regions = overlay.querySelectorAll('.vttjs-region'); + } + for (var i = 0; i < regions.length; i++) { - var regionDisplay = regions[i].displayState.firstChild; + var region = regions[i]; + var regionDisplay = region.displayState ? region.displayState.firstChild : region.firstChild; var cueEls = regionDisplay.querySelectorAll('.vttjs-cue'); var [height, removed] = clearOldCues(cueEls, cues, true); @@ -55,6 +62,8 @@ var processCues = function(window, cues, overlay, regions) { }; function clearOldCues(cueEls, cues) { + cues = cues || []; + var keptCuesHeight = 0; var removed = 0; From eb80874828031ba948cf574dccd000a8f5f555ad Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 7 Oct 2020 16:44:04 -0400 Subject: [PATCH 21/25] re-use the cue for the final parsing --- lib/parser/parser.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/parser/parser.js b/lib/parser/parser.js index 0b476384..7d7d978b 100644 --- a/lib/parser/parser.js +++ b/lib/parser/parser.js @@ -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 @@ -118,9 +119,11 @@ Parser.prototype.parse = function (data) { } var alreadyCollectedLine = false; - var sawCue = false; - self.cue = null; - self.regionSettings = null; + 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. @@ -332,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 From 5dd72939d6ef463f76bc0ec876b92e39f19336f8 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 7 Oct 2020 17:08:21 -0400 Subject: [PATCH 22/25] unneeded for regular non-region cues --- lib/process/cue-style-box.js | 11 ----------- lib/process/process-cues.js | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/process/cue-style-box.js b/lib/process/cue-style-box.js index 9989af22..ffb74633 100644 --- a/lib/process/cue-style-box.js +++ b/lib/process/cue-style-box.js @@ -42,17 +42,6 @@ function CueStyleBox(window, cue, styleOptions) { this.applyStyles(styles); - if (!cue.region) { - styles = { - left: 0, - right: 0, - top: 0, - bottom: 0, - position: "absolute" - }; - this.applyStyles(styles); - } - this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 71306190..1a57965a 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -164,7 +164,7 @@ var processCues = function(window, cues, overlay, regions) { paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. - moveBoxToLinePosition(window, styleBox, containerBox, boxPositions, cue.region); + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); } From 912d29144c48c326eb75af246ac3e7baa0dd523a Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 7 Oct 2020 17:33:29 -0400 Subject: [PATCH 23/25] bring back styles for non-region cues. They were previously moved to a single block but they didn't get added to the correct elements and thus the cues weren't positioned appropriately. --- lib/process/cue-style-box.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/process/cue-style-box.js b/lib/process/cue-style-box.js index ffb74633..9a870bef 100644 --- a/lib/process/cue-style-box.js +++ b/lib/process/cue-style-box.js @@ -22,6 +22,13 @@ function CueStyleBox(window, cue, styleOptions) { 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 @@ -40,6 +47,10 @@ function CueStyleBox(window, cue, styleOptions) { whiteSpace: "pre-line" }; + if (!cue.region) { + styles.position = "absolute"; + } + this.applyStyles(styles); this.div.appendChild(this.cueDiv); From 27c5baa8fb932dae520dfabc3402daf8e435321f Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 7 Oct 2020 17:39:56 -0400 Subject: [PATCH 24/25] update processCue docs with region --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fbe59e7..6331a2d5 100644 --- a/README.md +++ b/README.md @@ -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 From 75701cb98d31688f4a12a790618706a9d1b3ce6c Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Wed, 7 Oct 2020 17:49:00 -0400 Subject: [PATCH 25/25] if regions weren't provided to processCues, do our best --- lib/process/cue-style-box.js | 22 ++++++++++------------ lib/process/process-cues.js | 9 +++++++-- lib/process/regions.js | 6 +++++- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/process/cue-style-box.js b/lib/process/cue-style-box.js index 9a870bef..eb55abe6 100644 --- a/lib/process/cue-style-box.js +++ b/lib/process/cue-style-box.js @@ -89,18 +89,16 @@ function CueStyleBox(window, cue, styleOptions) { }); } - if (!cue.region) { - this.move = function(box) { - this.applyStyles({ - top: this.formatStyle(box.top, "px"), - bottom: this.formatStyle(box.bottom, "px"), - left: this.formatStyle(box.left, "px"), - right: this.formatStyle(box.right, "px"), - height: this.formatStyle(box.height, "px"), - width: this.formatStyle(box.width, "px") - }); - }; - } + this.move = function(box) { + this.applyStyles({ + top: this.formatStyle(box.top, "px"), + bottom: this.formatStyle(box.bottom, "px"), + left: this.formatStyle(box.left, "px"), + right: this.formatStyle(box.right, "px"), + height: this.formatStyle(box.height, "px"), + width: this.formatStyle(box.width, "px") + }); + }; } CueStyleBox.prototype = Object.create(StyleBox.prototype); CueStyleBox.prototype.constructor = CueStyleBox; diff --git a/lib/process/process-cues.js b/lib/process/process-cues.js index 1a57965a..0ffaedec 100644 --- a/lib/process/process-cues.js +++ b/lib/process/process-cues.js @@ -19,10 +19,15 @@ var CUE_BACKGROUND_PADDING = "1.5%"; var processCues = function(window, cues, overlay, regions) { regions = regions || []; + if (!window || !cues || !overlay) { return null; } + function usingCues(cue) { + return cue.region && regions.length > 0; + } + // setup region overlays setupRegions(regions, overlay); @@ -74,7 +79,7 @@ var processCues = function(window, cues, overlay, regions) { var cue = cues[j]; if (cue.displayState && el === cue.displayState) { keep = true; - if (cue.region) { + if (usingCues(cue)) { keptCuesHeight += cue.displayState.offsetHeight; } break; @@ -142,7 +147,7 @@ var processCues = function(window, cues, overlay, regions) { // Compute the intial position and styles of the cue div. styleBox = new CueStyleBox(window, cue, styleOptions); - if (cue.region) { + if (usingCues(cue)) { if (!regionBoxPositions.has(cue.region)) { regionBoxPositions.set(cue.region, []); } diff --git a/lib/process/regions.js b/lib/process/regions.js index c8cf43d5..87bb7efa 100644 --- a/lib/process/regions.js +++ b/lib/process/regions.js @@ -67,7 +67,10 @@ function removeRegionCue(el) { } function handleRegionCue(cue, styleBox, adjust) { - var regionDisplay = cue.region.displayState.firstChild; + var regionDisplay = cue.region.displayState && cue.region.displayState.firstChild; + if (!regionDisplay) { + return; + } var rH = parseInt(regionDisplay.style.height, 10); regionDisplay.appendChild(styleBox.div); @@ -78,6 +81,7 @@ function handleRegionCue(cue, styleBox, adjust) { function cueInRegion(cue) { return cue.region && + cue.region.displayState && cue.displayState.parentElement === cue.region.displayState.firstChild; }