miru/app/js/subtitles-octopus.js
2020-11-22 12:49:24 +01:00

926 lines
No EOL
37 KiB
JavaScript

var SubtitlesOctopus = function (options) {
var supportsWebAssembly = false;
try {
if (typeof WebAssembly === "object"
&& typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module)
supportsWebAssembly = (new WebAssembly.Instance(module) instanceof WebAssembly.Instance);
}
} catch (e) {
}
console.log("WebAssembly support detected: " + (supportsWebAssembly ? "yes" : "no"));
var self = this;
self.canvas = options.canvas; // HTML canvas element (optional if video specified)
self.renderMode = options.renderMode || (options.lossyRender ? 'fast' : (options.blendRender ? 'blend' : 'normal'));
// play with those when you need some speed, e.g. for slow devices
self.dropAllAnimations = options.dropAllAnimations || false;
self.libassMemoryLimit = options.libassMemoryLimit || 0; // set libass bitmap cache memory limit in MiB (approximate)
self.libassGlyphLimit = options.libassGlyphLimit || 0; // set libass glyph cache memory limit in MiB (approximate)
self.targetFps = options.targetFps || 60;
self.prescaleTradeoff = options.prescaleTradeoff || null; // render subtitles less than viewport when less than 1.0 to improve speed, render to more than 1.0 to improve quality; set to null to disable scaling
self.softHeightLimit = options.softHeightLimit || 1080; // don't apply prescaleTradeoff < 1 when viewport height is less that this limit
self.hardHeightLimit = options.hardHeightLimit || 2160; // don't ever go above this limit
self.resizeVariation = options.resizeVariation || 0.2; // by how many a size can vary before it would cause clearance of prerendered buffer
self.renderAhead = options.renderAhead || 0; // how many MiB to render ahead and store; 0 to disable (approximate)
self.isOurCanvas = false; // (internal) we created canvas and manage it
self.video = options.video; // HTML video element (optional if canvas specified)
self.canvasParent = null; // (internal) HTML canvas parent element
self.fonts = options.fonts || []; // Array with links to fonts used in sub (optional)
self.availableFonts = options.availableFonts || []; // Object with all available fonts (optional). Key is font name in lower case, value is link: {"arial": "/font1.ttf"}
self.onReadyEvent = options.onReady; // Function called when SubtitlesOctopus is ready (optional)
if (supportsWebAssembly) {
self.workerUrl = options.workerUrl || 'subtitles-octopus-worker.js'; // Link to WebAssembly worker
} else {
self.workerUrl = options.legacyWorkerUrl || 'subtitles-octopus-worker-legacy.js'; // Link to legacy worker
}
self.subUrl = options.subUrl; // Link to sub file (optional if subContent specified)
self.subContent = options.subContent || null; // Sub content (optional if subUrl specified)
self.onErrorEvent = options.onError; // Function called in case of critical error meaning sub wouldn't be shown and you should use alternative method (for instance it occurs if browser doesn't support web workers).
self.debug = options.debug || false; // When debug enabled, some performance info printed in console.
self.lastRenderTime = 0; // (internal) Last time we got some frame from worker
self.pixelRatio = window.devicePixelRatio || 1; // (internal) Device pixel ratio (for high dpi devices)
self.timeOffset = options.timeOffset || 0; // Time offset would be applied to currentTime from video (option)
self.renderedItems = []; // used to store items rendered ahead when renderAhead > 0
self.renderAhead = self.renderAhead * 1024 * 1024 * 0.9 /* try to eat less than requested */;
self.oneshotState = {
eventStart: null,
eventOver: false,
iteration: 0,
renderRequested: false,
requestNextTimestamp: -1,
prevWidth: null,
prevHeight: null
}
self.hasAlphaBug = false;
(function() {
if (typeof ImageData.prototype.constructor === 'function') {
try {
// try actually calling ImageData, as on some browsers it's reported
// as existing but calling it errors out as "TypeError: Illegal constructor"
new window.ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
return;
} catch (e) {
console.log("detected that ImageData is not constructable despite browser saying so");
}
}
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
window.ImageData = function () {
var i = 0;
if (arguments[0] instanceof Uint8ClampedArray) {
var data = arguments[i++];
}
var width = arguments[i++];
var height = arguments[i];
var imageData = ctx.createImageData(width, height);
if (data) imageData.data.set(data);
return imageData;
}
})();
self.workerError = function (error) {
console.error('Worker error: ', error);
if (self.onErrorEvent) {
self.onErrorEvent(error);
}
if (!self.debug) {
self.dispose();
throw new Error('Worker error: ' + error);
}
};
// Not tested for repeated usage yet
self.init = function () {
if (!window.Worker) {
self.workerError('worker not supported');
return;
}
// Worker
if (!self.worker) {
self.worker = new Worker(self.workerUrl);
self.worker.onmessage = self.onWorkerMessage;
self.worker.onerror = self.workerError;
}
self.workerActive = false;
self.createCanvas();
self.setVideo(options.video);
self.setSubUrl(options.subUrl);
self.worker.postMessage({
target: 'worker-init',
width: self.canvas.width,
height: self.canvas.height,
URL: document.URL,
currentScript: self.workerUrl,
preMain: true,
renderMode: self.renderMode,
subUrl: self.subUrl,
subContent: self.subContent,
fonts: self.fonts,
availableFonts: self.availableFonts,
debug: self.debug,
targetFps: self.targetFps,
libassMemoryLimit: self.libassMemoryLimit,
libassGlyphLimit: self.libassGlyphLimit,
renderOnDemand: self.renderAhead > 0,
dropAllAnimations: self.dropAllAnimations
});
};
self.createCanvas = function () {
if (!self.canvas) {
if (self.video) {
self.isOurCanvas = true;
self.canvas = document.createElement('canvas');
self.canvas.className = 'libassjs-canvas';
self.canvas.style.display = 'none';
self.canvasParent = document.createElement('div');
self.canvasParent.className = 'libassjs-canvas-parent';
self.canvasParent.appendChild(self.canvas);
if (self.video.nextSibling) {
self.video.parentNode.insertBefore(self.canvasParent, self.video.nextSibling);
}
else {
self.video.parentNode.appendChild(self.canvasParent);
}
}
else {
if (!self.canvas) {
self.workerError('Don\'t know where to render: you should give video or canvas in options.');
}
}
}
self.ctx = self.canvas.getContext('2d');
self.bufferCanvas = document.createElement('canvas');
self.bufferCanvasCtx = self.bufferCanvas.getContext('2d');
// test for alpha bug, where e.g. WebKit can render a transparent pixel
// (with alpha == 0) as non-black which then leads to visual artifacts
self.bufferCanvas.width = 1;
self.bufferCanvas.height = 1;
var testBuf = new Uint8ClampedArray([0, 255, 0, 0]);
var testImage = new ImageData(testBuf, 1, 1);
self.bufferCanvasCtx.clearRect(0, 0, 1, 1);
self.ctx.clearRect(0, 0, 1, 1);
var prePut = self.ctx.getImageData(0, 0, 1, 1).data;
self.bufferCanvasCtx.putImageData(testImage, 0, 0);
self.ctx.drawImage(self.bufferCanvas, 0, 0);
var postPut = self.ctx.getImageData(0, 0, 1, 1).data;
self.hasAlphaBug = prePut[1] != postPut[1];
if (self.hasAlphaBug) {
console.log("Detected a browser having issue with transparent pixels, applying workaround");
}
};
self.setVideo = function (video) {
self.video = video;
if (self.video) {
var timeupdate = function () {
self.setCurrentTime(video.currentTime + self.timeOffset);
}
self.video.addEventListener("timeupdate", timeupdate, false);
self.video.addEventListener("playing", function () {
self.setIsPaused(false, video.currentTime + self.timeOffset);
}, false);
self.video.addEventListener("pause", function () {
self.setIsPaused(true, video.currentTime + self.timeOffset);
}, false);
self.video.addEventListener("seeking", function () {
self.video.removeEventListener("timeupdate", timeupdate);
}, false);
self.video.addEventListener("seeked", function () {
self.video.addEventListener("timeupdate", timeupdate, false);
self.setCurrentTime(video.currentTime + self.timeOffset);
if (self.renderAhead > 0) {
_cleanPastRendered(video.currentTime + self.timeOffset, true);
}
}, false);
self.video.addEventListener("ratechange", function () {
self.setRate(video.playbackRate);
}, false);
self.video.addEventListener("timeupdate", function () {
self.setCurrentTime(video.currentTime + self.timeOffset);
}, false);
self.video.addEventListener("waiting", function () {
self.setIsPaused(true, video.currentTime + self.timeOffset);
}, false);
document.addEventListener("fullscreenchange", self.resizeWithTimeout, false);
document.addEventListener("mozfullscreenchange", self.resizeWithTimeout, false);
document.addEventListener("webkitfullscreenchange", self.resizeWithTimeout, false);
document.addEventListener("msfullscreenchange", self.resizeWithTimeout, false);
window.addEventListener("resize", self.resizeWithTimeout, false);
// Support Element Resize Observer
if (typeof ResizeObserver !== "undefined") {
self.ro = new ResizeObserver(self.resizeWithTimeout);
self.ro.observe(self.video);
}
if (self.video.videoWidth > 0) {
self.resize();
}
else {
self.video.addEventListener("loadedmetadata", function (e) {
e.target.removeEventListener(e.type, arguments.callee);
self.resize();
}, false);
}
}
};
self.getVideoPosition = function () {
var videoRatio = self.video.videoWidth / self.video.videoHeight;
var width = self.video.offsetWidth, height = self.video.offsetHeight;
var elementRatio = width / height;
var realWidth = width, realHeight = height;
if (elementRatio > videoRatio) realWidth = Math.floor(height * videoRatio);
else realHeight = Math.floor(width / videoRatio);
var x = (width - realWidth) / 2;
var y = (height - realHeight) / 2;
return {
width: realWidth,
height: realHeight,
x: x,
y: y
};
};
self.setSubUrl = function (subUrl) {
self.subUrl = subUrl;
};
function _cleanPastRendered(currentTime, seekClean) {
var retainedItems = [];
for (var i = 0, len = self.renderedItems.length; i < len; i++) {
var item = self.renderedItems[i];
if (item.emptyFinish < 0 || item.emptyFinish >= currentTime) {
// item is not yet finished, retain it
retainedItems.push(item);
}
}
if (seekClean && retainedItems.length > 0) {
// items are ordered by event start time when we push to self.renderedItems,
// so first item is the earliest
if (currentTime < retainedItems[0].eventStart) {
if (retainedItems[0].eventStart - currentTime > 60) {
console.info("seeked back too far, cleaning prerender buffer");
retainedItems = [];
} else {
console.info("seeked backwards, need to free up some buffer");
var size = 0, limit = self.renderAhead * 0.3 /* try to take no more than 1/3 of buffer */;
var retain = [];
for (var i = 0, len = retainedItems.length; i < len; i++) {
var item = retainedItems[i];
size += item.size;
if (size >= limit) break;
retain.push(item);
}
retainedItems = retain;
}
}
}
var removed = retainedItems.length < self.renderedItems;
self.renderedItems = retainedItems;
return removed;
}
function tryRequestOneshot(currentTime, renderNow) {
if (!self.renderAhead || self.renderAhead <= 0) return;
if (self.oneshotState.renderRequested && !renderNow) return;
if (typeof currentTime === 'undefined') {
if (!self.video) return;
currentTime = self.video.currentTime + self.timeOffset;
}
var size = 0;
for (var i = 0, len = self.renderedItems.length; i < len; i++) {
var item = self.renderedItems[i];
if (item.emptyFinish < 0) {
console.info('oneshot already reached end-of-events');
return;
}
if (currentTime >= item.eventStart && currentTime < item.emptyFinish) {
// an event for requested time already exists
console.debug('not requesting a render for ' + currentTime +
' as event already covering it exists (start=' +
item.eventStart + ', empty=' + item.emptyFinish + ')');
return;
}
size += item.size;
}
if (size <= self.renderAhead) {
lastRendered = currentTime - (renderNow ? 0 : 0.001);
if (!self.oneshotState.renderRequested) {
self.oneshotState.renderRequested = true;
self.worker.postMessage({
target: 'oneshot-render',
lastRendered: lastRendered,
renderNow: renderNow,
iteration: self.oneshotState.iteration
});
} else {
if (self.workerActive) {
console.info('worker busy, requesting to seek');
}
self.oneshotState.requestNextTimestamp = lastRendered;
}
}
}
function _renderSubtitleEvent(event, currentTime) {
var eventOver = event.eventFinish < currentTime;
if (self.oneshotState.eventStart == event.eventStart && self.oneshotState.eventOver == eventOver) return;
self.oneshotState.eventStart = event.eventStart;
self.oneshotState.eventOver = eventOver;
var beforeDrawTime = performance.now();
if (event.viewport.width != self.canvas.width || event.viewport.height != self.canvas.height) {
self.canvas.width = event.viewport.width;
self.canvas.height = event.viewport.height;
}
self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
if (!eventOver) {
for (var i = 0; i < event.items.length; i++) {
var image = event.items[i];
self.bufferCanvas.width = image.w;
self.bufferCanvas.height = image.h;
self.bufferCanvasCtx.putImageData(image.image, 0, 0);
self.ctx.drawImage(self.bufferCanvas, image.x, image.y);
}
}
if (self.debug) {
var drawTime = Math.round(performance.now() - beforeDrawTime);
console.log('render: ' + Math.round(event.spentTime - event.blendTime) + ' ms, blend: ' + Math.round(event.blendTime) + ' ms, draw: ' + drawTime + ' ms');
}
}
function oneshotRender() {
window.requestAnimationFrame(oneshotRender);
if (!self.video) return;
var currentTime = self.video.currentTime + self.timeOffset;
var finishTime = -1, eventShown = false, animated = false;
for (var i = 0, len = self.renderedItems.length; i < len; i++) {
var item = self.renderedItems[i];
if (!eventShown && item.eventStart <= currentTime && (item.emptyFinish < 0 || item.emptyFinish > currentTime)) {
_renderSubtitleEvent(item, currentTime);
eventShown = true;
finishTime = item.emptyFinish;
} else if (finishTime >= 0) {
// we've already found a known event, now find
// the farthest point of consequent events
// NOTE: self.renderedItems may have gaps due to seeking
if (item.eventStart - finishTime < 0.01) {
finishTime = item.emptyFinish;
animated = item.animated;
} else {
break;
}
}
}
if (!eventShown) {
if (Math.abs(self.oneshotState.requestNextTimestamp - currentTime) > 0.01) {
_cleanPastRendered(currentTime);
tryRequestOneshot(currentTime, true);
}
} else if (_cleanPastRendered(currentTime) && finishTime >= 0) {
tryRequestOneshot(finishTime, animated);
}
}
self.resetRenderAheadCache = function (isResizing) {
if (self.renderAhead > 0) {
var newCache = [];
if (isResizing && self.oneshotState.prevHeight && self.oneshotState.prevWidth) {
if (self.oneshotState.prevHeight == self.canvas.height &&
self.oneshotState.prevWidth == self.canvas.width) return;
var timeLimit = 10, sizeLimit = self.renderAhead * 0.3;
if (self.canvas.height >= self.oneshotState.prevHeight * (1.0 - self.resizeVariation) &&
self.canvas.height <= self.oneshotState.prevHeight * (1.0 + self.resizeVariation) &&
self.canvas.width >= self.oneshotState.prevWidth * (1.0 - self.resizeVariation) &&
self.canvas.width <= self.oneshotState.prevWidth * (1.0 + self.resizeVariation)) {
console.debug('viewport changes are small, leaving more of prerendered buffer');
timeLimit = 30;
sizeLimit = self.renderAhead * 0.5;
}
var stopTime = self.video.currentTime + self.timeOffset + timeLimit;
var size = 0;
for (var i = 0; i < self.renderedItems.length; i++) {
var item = self.renderedItems[i];
if (item.emptyFinish < 0 || item.emptyFinish >= stopTime) break;
size += item.size;
if (size >= sizeLimit) break;
newCache.push(item);
}
}
console.info('resetting prerender cache');
self.renderedItems = newCache;
self.oneshotState.eventStart = null;
self.oneshotState.iteration++;
self.oneshotState.renderRequested = false;
self.oneshotState.prevHeight = self.canvas.height;
self.oneshotState.prevWidth = self.canvas.width;
window.requestAnimationFrame(oneshotRender);
tryRequestOneshot(undefined, true);
}
}
self.renderFrameData = null;
function renderFrames() {
var data = self.renderFramesData;
var beforeDrawTime = performance.now();
self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
for (var i = 0; i < data.canvases.length; i++) {
var image = data.canvases[i];
self.bufferCanvas.width = image.w;
self.bufferCanvas.height = image.h;
var imageBuffer = new Uint8ClampedArray(image.buffer);
if (self.hasAlphaBug) {
for (var j = 3; j < imageBuffer.length; j = j + 4) {
imageBuffer[j] = (imageBuffer[j] >= 1) ? imageBuffer[j] : 1;
}
}
var imageData = new ImageData(imageBuffer, image.w, image.h);
self.bufferCanvasCtx.putImageData(imageData, 0, 0);
self.ctx.drawImage(self.bufferCanvas, image.x, image.y);
}
if (self.debug) {
var drawTime = Math.round(performance.now() - beforeDrawTime);
var blendTime = data.blendTime;
if (typeof blendTime !== 'undefined') {
console.log('render: ' + Math.round(data.spentTime - blendTime) + ' ms, blend: ' + Math.round(blendTime) + ' ms, draw: ' + drawTime + ' ms; TOTAL=' + Math.round(data.spentTime + drawTime) + ' ms');
} else {
console.log(Math.round(data.spentTime) + ' ms (+ ' + drawTime + ' ms draw)');
}
self.renderStart = performance.now();
}
}
/**
* Lossy Render Mode
*
*/
function renderFastFrames() {
var data = self.renderFramesData;
var beforeDrawTime = performance.now();
self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
for (var i = 0; i < data.bitmaps.length; i++) {
var image = data.bitmaps[i];
self.ctx.drawImage(image.bitmap, image.x, image.y);
}
if (self.debug) {
var drawTime = Math.round(performance.now() - beforeDrawTime);
console.log(data.bitmaps.length + ' bitmaps, libass: ' + Math.round(data.libassTime) + 'ms, decode: ' + Math.round(data.decodeTime) + 'ms, draw: ' + drawTime + 'ms');
self.renderStart = performance.now();
}
}
self.workerActive = false;
self.frameId = 0;
self.onWorkerMessage = function (event) {
//dump('\nclient got ' + JSON.stringify(event.data).substr(0, 150) + '\n');
if (!self.workerActive) {
self.workerActive = true;
if (self.onReadyEvent) {
self.onReadyEvent();
}
}
var data = event.data;
switch (data.target) {
case 'stdout': {
console.log(data.content);
break;
}
case 'console-log': {
console.log.apply(console, JSON.parse(data.content));
break;
}
case 'console-debug': {
console.debug.apply(console, JSON.parse(data.content));
break;
}
case 'console-info': {
console.info.apply(console, JSON.parse(data.content));
break;
}
case 'console-warn': {
console.warn.apply(console, JSON.parse(data.content));
break;
}
case 'console-error': {
console.error.apply(console, JSON.parse(data.content));
break;
}
case 'stderr': {
console.error(data.content);
break;
}
case 'window': {
window[data.method]();
break;
}
case 'canvas': {
switch (data.op) {
case 'getContext': {
self.ctx = self.canvas.getContext(data.type, data.attributes);
break;
}
case 'resize': {
self.resize(data.width, data.height);
break;
}
case 'renderCanvas': {
if (self.lastRenderTime < data.time) {
self.lastRenderTime = data.time;
self.renderFramesData = data;
window.requestAnimationFrame(renderFrames);
}
break;
}
case 'renderFastCanvas': {
if (self.lastRenderTime < data.time) {
self.lastRenderTime = data.time;
self.renderFramesData = data;
window.requestAnimationFrame(renderFastFrames);
}
break;
}
case 'setObjectProperty': {
self.canvas[data.object][data.property] = data.value;
break;
}
case 'oneshot-result': {
if (data.iteration != self.oneshotState.iteration) {
console.debug('received stale prerender, ignoring');
return;
}
if (self.debug) {
console.info('oneshot received (start=' +
data.eventStart + ', empty=' + data.emptyFinish +
'), render: ' + Math.round(data.spentTime) + ' ms');
}
self.oneshotState.renderRequested = false;
if (Math.abs(data.lastRenderedTime - self.oneshotState.requestNextTimestamp) < 0.01) {
self.oneshotState.requestNextTimestamp = -1;
}
if (data.eventStart - data.lastRenderedTime > 0.01) {
// generate bogus empty element, so all timeline is covered anyway
self.renderedItems.push({
eventStart: data.lastRenderedTime,
eventFinish: data.lastRenderedTime - 0.001,
emptyFinish: data.eventStart,
viewport: data.viewport,
spentTime: 0,
blendTime: 0,
items: [],
animated: false,
size: 0
});
}
var items = [];
var size = 0;
for (var i = 0, len = data.canvases.length; i < len; i++) {
var item = data.canvases[i];
items.push({
w: item.w,
h: item.h,
x: item.x,
y: item.y,
image: new ImageData(new Uint8ClampedArray(item.buffer), item.w, item.h)
});
size += item.buffer.byteLength;
}
var eventSplitted = false;
if ((data.emptyFinish > 0 && data.emptyFinish - data.eventStart < 1.0 / self.targetFps) || data.animated) {
var newFinish = data.eventStart + 1.0 / self.targetFps;
data.emptyFinish = newFinish;
data.eventFinish = newFinish;
eventSplitted = true;
}
self.renderedItems.push({
eventStart: data.eventStart,
eventFinish: data.eventFinish,
emptyFinish: data.emptyFinish,
spentTime: data.spentTime,
blendTime: data.blendTime,
viewport: data.viewport,
items: items,
animated: data.animated,
size: size
});
self.renderedItems.sort(function (a, b) {
return a.eventStart - b.eventStart;
});
if (self.oneshotState.requestNextTimestamp >= 0) {
// requesting an out of order event render
tryRequestOneshot(self.oneshotState.requestNextTimestamp, true);
} else if (data.eventStart < 0) {
console.info('oneshot received "end of frames" event');
} else if (data.emptyFinish >= 0) {
// there's some more event to render, try requesting next event
tryRequestOneshot(data.emptyFinish, eventSplitted);
} else {
console.info('there are no more events to prerender');
}
break;
}
default:
throw 'eh?';
}
break;
}
case 'tick': {
self.frameId = data.id;
self.worker.postMessage({
target: 'tock',
id: self.frameId
});
break;
}
case 'custom': {
if (self['onCustomMessage']) {
self['onCustomMessage'](event);
} else {
throw 'Custom message received but client onCustomMessage not implemented.';
}
break;
}
case 'setimmediate': {
self.worker.postMessage({
target: 'setimmediate'
});
break;
}
case 'get-events': {
console.log(data.target);
console.log(data.events);
break;
}
case 'get-styles': {
console.log(data.target);
console.log(data.styles);
break;
}
default:
throw 'what? ' + data.target;
}
};
function _computeCanvasSize(width, height) {
if (self.prescaleTradeoff === null) {
if (height > self.hardHeightLimit) {
width = width * self.hardHeightLimit / height;
height = self.hardHeightLimit;
}
} else if (self.prescaleTradeoff > 1) {
if (height * self.prescaleTradeoff <= self.softHeightLimit) {
width *= self.prescaleTradeoff;
height *= self.prescaleTradeoff;
} else if (height < self.softHeightLimit) {
width = width * self.softHeightLimit / height;
height = self.softHeightLimit;
} else if (height >= self.hardHeightLimit) {
width = width * self.hardHeightLimit / height;
height = self.hardHeightLimit;
}
} else if (height >= self.softHeightLimit) {
if (height * self.prescaleTradeoff <= self.softHeightLimit) {
width = width * self.softHeightLimit / height;
height = self.softHeightLimit;
} else if (height * self.prescaleTradeoff <= self.hardHeightLimit) {
width *= self.prescaleTradeoff;
height *= self.prescaleTradeoff;
} else {
width = width * self.hardHeightLimit / height;
height = self.hardHeightLimit;
}
}
return {'width': width, 'height': height};
}
self.resize = function (width, height, top, left) {
var videoSize = null;
top = top || 0;
left = left || 0;
if ((!width || !height) && self.video) {
videoSize = self.getVideoPosition();
var newsize = _computeCanvasSize(videoSize.width * self.pixelRatio, videoSize.height * self.pixelRatio);
width = newsize.width;
height = newsize.height;
var offset = self.canvasParent.getBoundingClientRect().top - self.video.getBoundingClientRect().top;
top = videoSize.y - offset;
left = videoSize.x;
}
if (!width || !height) {
if (!self.video) {
console.error('width or height is 0. You should specify width & height for resize.');
}
return;
}
if (
self.canvas.width != width ||
self.canvas.height != height ||
self.canvas.style.top != top ||
self.canvas.style.left != left
) {
self.canvas.width = width;
self.canvas.height = height;
if (videoSize != null) {
self.canvasParent.style.position = 'relative';
self.canvas.style.display = 'block';
self.canvas.style.position = 'absolute';
self.canvas.style.width = videoSize.width + 'px';
self.canvas.style.height = videoSize.height + 'px';
self.canvas.style.top = top + 'px';
self.canvas.style.left = left + 'px';
self.canvas.style.pointerEvents = 'none';
}
self.worker.postMessage({
target: 'canvas',
width: self.canvas.width,
height: self.canvas.height
});
self.resetRenderAheadCache(true);
}
};
self.resizeWithTimeout = function () {
self.resize();
};
self.runBenchmark = function () {
self.worker.postMessage({
target: 'runBenchmark'
});
};
self.customMessage = function (data, options) {
options = options || {};
self.worker.postMessage({
target: 'custom',
userData: data,
preMain: options.preMain
});
};
self.setCurrentTime = function (currentTime) {
self.worker.postMessage({
target: 'video',
currentTime: currentTime
});
};
self.setTrackByUrl = function (url) {
self.worker.postMessage({
target: 'set-track-by-url',
url: url
});
self.resetRenderAheadCache(false);
};
self.setTrack = function (content) {
self.worker.postMessage({
target: 'set-track',
content: content
});
self.resetRenderAheadCache(false);
};
self.freeTrack = function (content) {
self.worker.postMessage({
target: 'free-track'
});
self.resetRenderAheadCache(false);
};
self.render = self.setCurrentTime;
self.setIsPaused = function (isPaused, currentTime) {
self.worker.postMessage({
target: 'video',
isPaused: isPaused,
currentTime: currentTime
});
};
self.setRate = function (rate) {
self.worker.postMessage({
target: 'video',
rate: rate
});
};
self.dispose = function () {
self.worker.postMessage({
target: 'destroy'
});
self.worker.terminate();
self.workerActive = false;
// Remove the canvas element to remove residual subtitles rendered on player
if (self.video) {
self.video.parentNode.removeChild(self.canvasParent);
}
};
self.createEvent = function (event) {
self.worker.postMessage({
target: 'create-event',
event: event
});
};
self.getEvents = function () {
self.worker.postMessage({
target: 'get-events'
});
};
self.setEvent = function (event, index) {
self.worker.postMessage({
target: 'set-event',
event: event,
index: index
});
};
self.removeEvent = function (index) {
self.worker.postMessage({
target: 'remove-event',
index: index
});
};
self.createStyle = function (style) {
self.worker.postMessage({
target: 'create-style',
style: style
});
};
self.getStyles = function () {
self.worker.postMessage({
target: 'get-styles'
});
};
self.setStyle = function (style, index) {
self.worker.postMessage({
target: 'set-style',
style: style,
index: index
});
};
self.removeStyle = function (index) {
self.worker.postMessage({
target: 'remove-style',
index: index
});
};
self.init();
};
if (typeof SubtitlesOctopusOnLoad == 'function') {
SubtitlesOctopusOnLoad();
}
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = SubtitlesOctopus
}
}