update torrent-player

This commit is contained in:
ThaUnknown 2021-05-04 23:20:48 +02:00
parent f2ebf045a1
commit af3f12fccb
5 changed files with 2132 additions and 7561 deletions

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -1,19 +1,14 @@
class SubtitlesOctopus {
constructor (options) {
let 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) {
}
if (!supportsWebAssembly) {
throw new Error('WASM not supported!')
}
const self = this
self.canvas = options.canvas // HTML canvas element (optional if video specified)
self.renderMode = options.renderMode || (options.lossyRender ? 'fast' : (options.blendRender ? 'blend' : 'normal'))
if (self.renderMode === 'offscreenCanvas' && typeof OffscreenCanvas === 'undefined') {
self.renderMode = 'fast'
}
if ((self.renderMode === 'fast' || self.renderMode === 'offscreenCanvas') && typeof createImageBitmap === 'undefined') {
self.renderMode = 'normal'
}
// play with those when you need some speed, e.g. for slow devices
self.dropAllAnimations = options.dropAllAnimations || false
@ -25,13 +20,15 @@ class SubtitlesOctopus {
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.fallbackFont = options.fallbackFont
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 SubtitleDuet is ready (optional)
self.workerUrl = options.workerUrl || 'subtitleDuetWorker.js' // Link to WebAssembly worker
self.onReadyEvent = options.onReady // Function called when SubtitlesOctopus is ready (optional)
self.workerUrl = options.workerUrl || 'subtitles-octopus-worker.js' // Link to WebAssembly 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).
@ -41,6 +38,48 @@ class SubtitlesOctopus {
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 = undefined;
(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"
const image = new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1)
return
} catch (e) {
console.log('detected that ImageData is not constructable despite browser saying so')
}
}
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
window.ImageData = function () {
let i = 0
let data
if (arguments[0] instanceof Uint8ClampedArray) data = arguments[i++]
const width = arguments[i++]
const height = arguments[i]
const 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) {
@ -75,16 +114,20 @@ class SubtitlesOctopus {
URL: document.URL,
currentScript: self.workerUrl,
preMain: true,
renderMode: self.renderMode,
subUrl: self.subUrl,
subContent: self.subContent,
fallbackFont: self.fallbackFont,
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.pushOffscreenCanvas()
if (self.renderMode === 'offscreenCanvas') self.pushOffscreenCanvas()
self.initDone = true
}
self.pushOffscreenCanvas = function () {
@ -101,7 +144,6 @@ class SubtitlesOctopus {
self.canvas = document.createElement('canvas')
self.canvas.className = 'libassjs-canvas'
self.canvas.style.display = 'none'
self.canvas.id = 'subtitleCanvas'
self.canvasParent = document.createElement('div')
self.canvasParent.className = 'libassjs-canvas-parent'
@ -117,6 +159,28 @@ class SubtitlesOctopus {
self.workerError('Don\'t know where to render: you should give video or canvas in options.')
}
}
if (self.hasAlphaBug === undefined && self.renderMode !== 'offscreenCanvas') {
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
const testBuf = new Uint8ClampedArray([0, 255, 0, 0])
const testImage = new ImageData(testBuf, 1, 1)
self.bufferCanvasCtx.clearRect(0, 0, 1, 1)
self.ctx.clearRect(0, 0, 1, 1)
const prePut = self.ctx.getImageData(0, 0, 1, 1).data
self.bufferCanvasCtx.putImageData(testImage, 0, 0)
self.ctx.drawImage(self.bufferCanvas, 0, 0)
const 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) {
@ -152,15 +216,9 @@ class SubtitlesOctopus {
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 = new ResizeObserver(() => self.resize())
self.ro.observe(self.video)
}
@ -196,9 +254,238 @@ class SubtitlesOctopus {
self.subUrl = subUrl
}
function _cleanPastRendered (currentTime, seekClean) {
let retainedItems = []
for (const item of self.renderedItems) {
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')
let size = 0; const limit = self.renderAhead * 0.3 /* try to take no more than 1/3 of buffer */
const retain = []
for (const item of retainedItems) {
size += item.size
if (size >= limit) { break }
retain.push(item)
}
retainedItems = retain
}
}
}
const 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
}
let size = 0
for (let i = 0, len = self.renderedItems.length; i < len; i++) {
const 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) {
const eventOver = event.eventFinish < currentTime
if (self.oneshotState.eventStart == event.eventStart && self.oneshotState.eventOver == eventOver) { return }
self.oneshotState.eventStart = event.eventStart
self.oneshotState.eventOver = eventOver
const 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 (const image of event.items) {
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) {
const 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 }
const currentTime = self.video.currentTime + self.timeOffset
let finishTime = -1; let eventShown = false; let animated = false
for (const item of self.renderedItems) {
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) {
const newCache = []
if (isResizing && self.oneshotState.prevHeight && self.oneshotState.prevWidth) {
if (self.oneshotState.prevHeight == self.canvas.height && self.oneshotState.prevWidth == self.canvas.width) return
let timeLimit = 10; let 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
}
const stopTime = self.video.currentTime + self.timeOffset + timeLimit
let size = 0
for (let i = 0; i < self.renderedItems.length; i++) {
const 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 () {
const data = self.renderFramesData
const beforeDrawTime = performance.now()
self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height)
for (let i = 0; i < data.canvases.length; i++) {
const image = data.canvases[i]
self.bufferCanvas.width = image.w
self.bufferCanvas.height = image.h
const imageBuffer = new Uint8ClampedArray(image.buffer)
if (self.hasAlphaBug) {
for (let j = 3; j < imageBuffer.length; j = j + 4) {
imageBuffer[j] = (imageBuffer[j] >= 1) ? imageBuffer[j] : 1
}
}
const 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) {
const drawTime = Math.round(performance.now() - beforeDrawTime)
const 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 () {
const data = self.renderFramesData
const beforeDrawTime = performance.now()
self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height)
for (let i = 0; i < data.bitmaps.length; i++) {
const image = data.bitmaps[i]
self.ctx.drawImage(image.bitmap, image.x, image.y)
}
if (self.debug) {
const 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) {
@ -239,55 +526,158 @@ class SubtitlesOctopus {
window[data.method]()
break
}
case 'ready': {
break
}
case 'canvas': {
switch (data.op) {
case 'getContext': {
if (self.renderMode !== 'offscreenCanvas') 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 'tick': {
self.frameId = data.id
self.worker.postMessage({
target: 'tock',
id: self.frameId
})
break
}
case 'custom': {
if (self.onCustomMessage) {
self.onCustomMessage(event)
} else {
console.error('Custom message received but client onCustomMessage not implemented.')
case 'oneshot-result': {
if (data.iteration != self.oneshotState.iteration) {
console.debug('received stale prerender, ignoring')
return
}
break
}
case 'setimmediate': {
self.worker.postMessage({
target: 'setimmediate'
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
})
}
const items = []
let size = 0
for (let i = 0, len = data.canvases.length; i < len; i++) {
const 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
}
let eventSplitted = false
if ((data.emptyFinish > 0 && data.emptyFinish - data.eventStart < 1.0 / self.targetFps) || data.animated) {
const 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
})
break
}
case 'get-events': {
console.log(data.target)
console.log(data.events)
break
}
case 'get-styles': {
console.log(data.target)
console.log(data.styles)
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 data.target
}
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 {
console.error('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
}
case 'ready': {
break
}
default:
throw data.target
}
}
@ -344,10 +734,7 @@ class SubtitlesOctopus {
return
}
if (self.canvas.width !== width ||
self.canvas.height !== height ||
self.canvas.style.top !== top ||
self.canvas.style.left !== left) {
if (self.canvas.style.top !== top || self.canvas.style.left !== left) {
if (videoSize != null) {
self.canvasParent.style.position = 'relative'
self.canvas.style.display = 'block'
@ -358,18 +745,20 @@ class SubtitlesOctopus {
}
if (!(self.canvas.width === width && self.canvas.height === height)) {
// only re-paint if dimensions actually changed
if (self.initDone) {
if (self.renderMode === 'offscreenCanvas' && self.initDone) {
self.canvasParent.remove()
self.canvasParent = undefined
self.createCanvas()
}
function rePaint () {
if (self.canvasParent && self.initDone) {
if (self.renderMode === 'offscreenCanvas' && self.canvasParent && self.initDone) {
self.canvasParent.remove()
self.canvasParent = undefined
self.createCanvas()
}
self.canvas.width = width
self.canvas.height = height
if (videoSize != null) {
self.canvasParent.style.position = 'relative'
self.canvas.style.display = 'block'
@ -378,7 +767,8 @@ class SubtitlesOctopus {
self.canvas.style.left = left + 'px'
self.canvas.style.pointerEvents = 'none'
}
if (self.initDone) {
if (self.renderMode === 'offscreenCanvas' && self.initDone) {
self.pushOffscreenCanvas()
}
self.worker.postMessage({
@ -386,6 +776,7 @@ class SubtitlesOctopus {
width: self.canvas.width,
height: self.canvas.height
})
self.resetRenderAheadCache(true)
}
// dont spam re-paints like crazy when re-sizing with animations, but still update instantly without them
if (self.resizeTimeoutBuffer) {
@ -404,10 +795,6 @@ class SubtitlesOctopus {
}
}
self.resizeWithTimeout = function () {
self.resize()
}
self.runBenchmark = function () {
self.worker.postMessage({
target: 'runBenchmark'
@ -435,6 +822,7 @@ class SubtitlesOctopus {
target: 'set-track-by-url',
url: url
})
self.resetRenderAheadCache(false)
}
self.setTrack = function (content) {
@ -442,12 +830,14 @@ class SubtitlesOctopus {
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
@ -539,3 +929,15 @@ class SubtitlesOctopus {
self.init()
}
}
if (typeof SubtitlesOctopusOnLoad === 'function') {
SubtitlesOctopusOnLoad()
}
if (typeof exports !== 'undefined') {
exports = SubtitlesOctopus
}
if (typeof module !== 'undefined') {
module.exports = SubtitlesOctopus
}

View file

@ -81,7 +81,7 @@ class WebTorrentPlayer extends WebTorrent {
this.video.addEventListener('loadedmetadata', () => this.findSubtitleFiles(this.currentFile))
this.subtitleData = {
fonts: ['https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmEU9fBBc4.woff2'],
fonts: [],
headers: [],
tracks: [],
current: undefined,
@ -416,7 +416,7 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
this.setProgress(0)
// look for file and delete its store, idk how to do this
Object.assign(this.subtitleData, {
fonts: ['https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmEU9fBBc4.woff2'],
fonts: [],
headers: [],
tracks: [],
current: undefined,
@ -907,8 +907,9 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
subContent: this.subtitleData.headers[this.subtitleData.current].header.slice(0, -1),
renderMode: 'offscreenCanvas',
fonts: this.subtitleData.fonts,
fallbackFont: 'https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmEU9fBBc4.woff2',
workerUrl: 'js/subtitles-octopus-worker.js',
timeOffset: 0.083,
timeOffset: 0.041,
onReady: () => { // weird hack for laggy subtitles, this is some issue in SO
if (!this.video.paused) {
this.video.pause()