jassub 1.1.13 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jassub.es.js +37 -24
- package/dist/jassub.umd.js +1 -1
- package/package.json +1 -1
- package/src/jassub.js +39 -24
package/dist/jassub.es.js
CHANGED
|
@@ -41,15 +41,15 @@ if (!("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPla
|
|
|
41
41
|
}
|
|
42
42
|
const _JASSUB = class extends EventTarget {
|
|
43
43
|
constructor(options = {}) {
|
|
44
|
-
var _a, _b, _c;
|
|
44
|
+
var _a, _b, _c, _d, _e;
|
|
45
45
|
super();
|
|
46
46
|
if (!globalThis.Worker) {
|
|
47
47
|
this.destroy("Worker not supported");
|
|
48
48
|
}
|
|
49
49
|
_JASSUB._test();
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
50
|
+
const blendMode = options.blendMode || "js";
|
|
51
|
+
const asyncRender = typeof createImageBitmap !== "undefined" && ((_a = options.asyncRender) != null ? _a : true);
|
|
52
|
+
const offscreenRender = typeof OffscreenCanvas !== "undefined" && ((_b = options.offscreenRender) != null ? _b : true);
|
|
53
53
|
this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && ((_c = options.onDemandRender) != null ? _c : true);
|
|
54
54
|
this.timeOffset = options.timeOffset || 0;
|
|
55
55
|
this._video = options.video;
|
|
@@ -73,8 +73,8 @@ const _JASSUB = class extends EventTarget {
|
|
|
73
73
|
this._canvasParent.appendChild(this._canvas);
|
|
74
74
|
this._bufferCanvas = document.createElement("canvas");
|
|
75
75
|
this._bufferCtx = this._bufferCanvas.getContext("2d");
|
|
76
|
-
this._canvasctrl =
|
|
77
|
-
this._ctx = !
|
|
76
|
+
this._canvasctrl = offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas;
|
|
77
|
+
this._ctx = !offscreenRender && this._canvasctrl.getContext("2d");
|
|
78
78
|
this._lastRenderTime = 0;
|
|
79
79
|
this.debug = !!options.debug;
|
|
80
80
|
this.prescaleFactor = options.prescaleFactor || 1;
|
|
@@ -85,11 +85,12 @@ const _JASSUB = class extends EventTarget {
|
|
|
85
85
|
this._worker.onerror = (e) => this._error(e);
|
|
86
86
|
this._worker.postMessage({
|
|
87
87
|
target: "init",
|
|
88
|
-
asyncRender
|
|
88
|
+
asyncRender,
|
|
89
|
+
onDemandRender: this._onDemandRender,
|
|
89
90
|
width: this._canvas.width,
|
|
90
91
|
height: this._canvas.height,
|
|
91
92
|
preMain: true,
|
|
92
|
-
blendMode
|
|
93
|
+
blendMode,
|
|
93
94
|
subUrl: options.subUrl,
|
|
94
95
|
subContent: options.subContent || null,
|
|
95
96
|
fonts: options.fonts || [],
|
|
@@ -101,9 +102,9 @@ const _JASSUB = class extends EventTarget {
|
|
|
101
102
|
libassMemoryLimit: options.libassMemoryLimit || 0,
|
|
102
103
|
libassGlyphLimit: options.libassGlyphLimit || 0,
|
|
103
104
|
hasAlphaBug: _JASSUB._hasAlphaBug,
|
|
104
|
-
useLocalFonts: "queryLocalFonts" in self &&
|
|
105
|
+
useLocalFonts: "queryLocalFonts" in self && ((_d = options.useLocalFonts) != null ? _d : true)
|
|
105
106
|
});
|
|
106
|
-
if (
|
|
107
|
+
if (offscreenRender === true)
|
|
107
108
|
this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
|
|
108
109
|
this._boundResize = this.resize.bind(this);
|
|
109
110
|
this._boundTimeUpdate = this._timeupdate.bind(this);
|
|
@@ -111,14 +112,15 @@ const _JASSUB = class extends EventTarget {
|
|
|
111
112
|
this.setVideo(options.video);
|
|
112
113
|
if (this._onDemandRender) {
|
|
113
114
|
this.busy = false;
|
|
114
|
-
this.
|
|
115
|
+
this._lastDemandTime = null;
|
|
116
|
+
(_e = this._video) == null ? void 0 : _e.requestVideoFrameCallback(this._handleRVFC.bind(this));
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
static _test() {
|
|
118
120
|
if (_JASSUB._supportsWebAssembly !== null)
|
|
119
121
|
return null;
|
|
120
122
|
const canvas1 = document.createElement("canvas");
|
|
121
|
-
const ctx1 = canvas1.getContext("2d");
|
|
123
|
+
const ctx1 = canvas1.getContext("2d", { willReadFrequently: true });
|
|
122
124
|
if (typeof ImageData.prototype.constructor === "function") {
|
|
123
125
|
try {
|
|
124
126
|
new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
|
|
@@ -143,7 +145,7 @@ const _JASSUB = class extends EventTarget {
|
|
|
143
145
|
_JASSUB._supportsWebAssembly = false;
|
|
144
146
|
}
|
|
145
147
|
const canvas2 = document.createElement("canvas");
|
|
146
|
-
const ctx2 = canvas2.getContext("2d");
|
|
148
|
+
const ctx2 = canvas2.getContext("2d", { willReadFrequently: true });
|
|
147
149
|
canvas1.width = canvas2.width = 1;
|
|
148
150
|
canvas1.height = canvas2.height = 1;
|
|
149
151
|
ctx1.clearRect(0, 0, 1, 1);
|
|
@@ -242,7 +244,9 @@ const _JASSUB = class extends EventTarget {
|
|
|
242
244
|
if (video instanceof HTMLVideoElement) {
|
|
243
245
|
this._removeListeners();
|
|
244
246
|
this._video = video;
|
|
245
|
-
if (this._onDemandRender
|
|
247
|
+
if (this._onDemandRender) {
|
|
248
|
+
this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
|
|
249
|
+
} else {
|
|
246
250
|
this._playstate = video.paused;
|
|
247
251
|
video.addEventListener("timeupdate", this._boundTimeUpdate, false);
|
|
248
252
|
video.addEventListener("progress", this._boundTimeUpdate, false);
|
|
@@ -336,16 +340,15 @@ const _JASSUB = class extends EventTarget {
|
|
|
336
340
|
}
|
|
337
341
|
}
|
|
338
342
|
_getLocalFont({ font }) {
|
|
339
|
-
var _a
|
|
343
|
+
var _a;
|
|
340
344
|
try {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
query({ name: "local-fonts" }).then((permission) => {
|
|
345
|
+
if ((_a = navigator == null ? void 0 : navigator.permissions) == null ? void 0 : _a.query) {
|
|
346
|
+
navigator.permissions.query({ name: "local-fonts" }).then((permission) => {
|
|
344
347
|
if (permission.state === "granted") {
|
|
345
348
|
this._sendLocalFont(font);
|
|
346
349
|
}
|
|
347
350
|
});
|
|
348
|
-
} else
|
|
351
|
+
} else {
|
|
349
352
|
this._sendLocalFont(font);
|
|
350
353
|
}
|
|
351
354
|
} catch (e) {
|
|
@@ -353,16 +356,26 @@ const _JASSUB = class extends EventTarget {
|
|
|
353
356
|
}
|
|
354
357
|
}
|
|
355
358
|
_unbusy() {
|
|
356
|
-
this.
|
|
359
|
+
if (this._lastDemandTime) {
|
|
360
|
+
this._demandRender(this._lastDemandTime);
|
|
361
|
+
} else {
|
|
362
|
+
this.busy = false;
|
|
363
|
+
}
|
|
357
364
|
}
|
|
358
|
-
|
|
365
|
+
_handleRVFC(now, { mediaTime }) {
|
|
359
366
|
if (this._destroyed)
|
|
360
367
|
return null;
|
|
361
|
-
if (
|
|
368
|
+
if (this.busy) {
|
|
369
|
+
this._lastDemandTime = mediaTime;
|
|
370
|
+
} else {
|
|
362
371
|
this.busy = true;
|
|
363
|
-
this.
|
|
372
|
+
this._demandRender(mediaTime);
|
|
364
373
|
}
|
|
365
|
-
this._video.requestVideoFrameCallback(this.
|
|
374
|
+
this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
|
|
375
|
+
}
|
|
376
|
+
_demandRender(time) {
|
|
377
|
+
this._lastDemandTime = null;
|
|
378
|
+
this.sendMessage("demand", { time: time + this.timeOffset });
|
|
366
379
|
}
|
|
367
380
|
_render({ images, async, times }) {
|
|
368
381
|
const drawStartTime = Date.now();
|
package/dist/jassub.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(h,n){typeof exports=="object"&&typeof module!="undefined"?module.exports=n():typeof define=="function"&&define.amd?define(n):(h=typeof globalThis!="undefined"?globalThis:h||self,h.JASSUB=n())})(this,function(){"use strict";var _=Object.defineProperty;var v=(h,n,l)=>n in h?_(h,n,{enumerable:!0,configurable:!0,writable:!0,value:l}):h[n]=l;var u=(h,n,l)=>(v(h,typeof n!="symbol"?n+"":n,l),l);!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(l){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,i=(r,o)=>{const d=this.getVideoPlaybackQuality(),f=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames;if(f>t){const c=this.mozFrameDelay||d.totalFrameDelay-e.totalFrameDelay||0,m=o-r;l(o,{presentationTime:o+c*1e3,expectedDisplayTime:o+m,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+m/1e3,presentedFrames:f,processingDuration:c}),delete this._rvfcpolyfillmap[a]}else this._rvfcpolyfillmap[a]=requestAnimationFrame(c=>i(o,c))},a=Date.now(),s=performance.now();return this._rvfcpolyfillmap[a]=requestAnimationFrame(r=>i(s,r)),a},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(l){cancelAnimationFrame(this._rvfcpolyfillmap[l]),delete this._rvfcpolyfillmap[l]});const n=class extends EventTarget{constructor(e={}){var s,r,o;super(),globalThis.Worker||this.destroy("Worker not supported"),n._test();const t=e.blendMode||"js",i=typeof createImageBitmap!="undefined"&&((s=e.asyncRender)!=null?s:!0),a=typeof OffscreenCanvas!="undefined"&&((r=e.offscreenRender)!=null?r:!0);this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&((o=e.onDemandRender)!=null?o:!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._canvasParent=null,this._video?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._video.nextSibling?this._video.parentNode.insertBefore(this._canvasParent,this._video.nextSibling):this._video.parentNode.appendChild(this._canvasParent)):this._canvas||this.destroy("Don't know where to render: you should give video or canvas in options."),this._canvas=e.canvas||document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas),this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),this._canvasctrl=a?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!a&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!e.debug,this.prescaleFactor=e.prescaleFactor||1,this.prescaleHeightLimit=e.prescaleHeightLimit||1080,this.maxRenderHeight=e.maxRenderHeight||0,this._worker=new Worker(n._supportsWebAssembly?e.workerUrl||"jassub-worker.js":e.legacyWorkerUrl||"jassub-worker-legacy.js"),this._worker.onmessage=d=>this._onmessage(d),this._worker.onerror=d=>this._error(d),this._worker.postMessage({target:"init",asyncRender:i,width:this._canvas.width,height:this._canvas.height,preMain:!0,blendMode:t,subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,hasAlphaBug:n._hasAlphaBug,useLocalFonts:"queryLocalFonts"in self&&!!e.useLocalFonts}),a===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._video.requestVideoFrameCallback(this._demandRender.bind(this)))}static _test(){if(n._supportsWebAssembly!==null)return null;const e=document.createElement("canvas"),t=e.getContext("2d");if(typeof ImageData.prototype.constructor=="function")try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch{console.log("detected that ImageData is not constructable despite browser saying so"),window.ImageData=function(d,f,c){const m=t.createImageData(f,c);return d&&m.data.set(d),m}}try{if(typeof WebAssembly=="object"&&typeof WebAssembly.instantiate=="function"){const o=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));o instanceof WebAssembly.Module&&(n._supportsWebAssembly=new WebAssembly.Instance(o)instanceof WebAssembly.Instance)}}catch{n._supportsWebAssembly=!1}const i=document.createElement("canvas"),a=i.getContext("2d");e.width=i.width=1,e.height=i.height=1,t.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const s=a.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(e,0,0);const r=a.getImageData(0,0,1,1).data;n._hasAlphaBug=s[1]!==r[1],n._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),i.remove()}resize(e=0,t=0,i=0,a=0){let s=null;if((!e||!t)&&this._video){s=this._getVideoPosition();const r=this._computeCanvasSize((s.width||0)*(window.devicePixelRatio||1),(s.height||0)*(window.devicePixelRatio||1));e=r.width,t=r.height,i=s.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),a=s.x}s!=null&&(this._canvas.style.top=i+"px",this._canvas.style.left=a+"px",this._canvas.style.width=s.width+"px",this._canvas.style.height=s.height+"px"),this._canvasctrl.width===e&&this._canvasctrl.height===t||(this._resizeTimeoutBuffer?(clearTimeout(this._resizeTimeoutBuffer),this._resizeTimeoutBuffer=setTimeout(()=>{this._resizeTimeoutBuffer=void 0,this._canvasctrl.width=e,this._canvasctrl.height=t,this.sendMessage("canvas",{width:e,height:t})},100)):(this._canvasctrl.width=e,this._canvasctrl.height=t,this.sendMessage("canvas",{width:e,height:t}),this._resizeTimeoutBuffer=setTimeout(()=>{this._resizeTimeoutBuffer=void 0},100)))}_getVideoPosition(){const e=this._video.videoWidth/this._video.videoHeight,{offsetWidth:t,offsetHeight:i}=this._video,a=t/i;let s=t,r=i;a>e?s=Math.floor(i*e):r=Math.floor(t/e);const o=(t-s)/2,d=(i-r)/2;return{width:s,height:r,x:o,y:d}}_computeCanvasSize(e=0,t=0){const i=this.prescaleFactor<=0?1:this.prescaleFactor;if(t<=0||e<=0)e=0,t=0;else{const a=i<1?-1:1;let s=t;a*s*i<=a*this.prescaleHeightLimit?s*=i:a*s<a*this.prescaleHeightLimit&&(s=this.prescaleHeightLimit),this.maxRenderHeight>0&&s>this.maxRenderHeight&&(s=this.maxRenderHeight),e*=s/t,t=s}return{width:e,height:t}}_timeupdate({type:e}){const i={seeking:!0,waiting:!0,playing:!1}[e];i!=null&&(this._playstate=i),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender!==!0&&(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1)),e.videoWidth>0&&this.resize(),e.addEventListener("resize",this._boundResize),typeof ResizeObserver!="undefined"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e})}setTrack(e){this.sendMessage("setTrack",{content:e})}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,i){this.sendMessage("video",{isPaused:e,currentTime:t,rate:i})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:i})=>{e(t,i)})}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{event:e,index:t})}removeStyle(e){this.sendMessage("removeStyle",{index:e})}getStyles(e){this._fetchFromWorker({target:"getStyles"},(t,{styles:i})=>{e(t,i)})}addFont(e){this.sendMessage("addFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const i=t&&t.filter(a=>a.fullName.toLowerCase()===e);i&&i.length&&i[0].blob().then(a=>{a.arrayBuffer().then(s=>{this.addFont(new Uint8Array(s))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t,i;try{const a=((t=navigator==null?void 0:navigator.permissions)==null?void 0:t.request)||((i=navigator==null?void 0:navigator.permissions)==null?void 0:i.query);a?a({name:"local-fonts"}).then(s=>{s.state==="granted"&&this._sendLocalFont(e)}):"queryLocalFonts"in self&&this._sendLocalFont(e)}catch(a){console.warn("Local fonts API:",a)}}_unbusy(){this.busy=!1}_demandRender(e,t){if(this._destroyed)return null;this.busy||(this.busy=!0,this.sendMessage("demand",{time:t.mediaTime+this.timeOffset})),this._video.requestVideoFrameCallback(this._demandRender.bind(this))}_render({images:e,async:t,times:i}){const a=Date.now();this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?(this._ctx.drawImage(s.image,s.x,s.y),s.image.close()):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)));if(this.debug){i.drawTime=Date.now()-a;let s=0;for(const r in i)s+=i[r];console.log("Bitmaps: "+e.length+" Total: "+Math.round(s)+"ms",i)}}_fixAlpha(e){if(n._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this.dispatchEvent(new CustomEvent("ready"))}sendMessage(e,t={},i){i?this._worker.postMessage({target:e,transferable:i,...t},[...i]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const i=e.target,a=setTimeout(()=>{r(new Error("Error: Timeout while try to fetch "+i))},5e3),s=({data:o})=>{o.target===i&&(t(null,o),this._worker.removeEventListener("message",s),this._worker.removeEventListener("error",r),clearTimeout(a))},r=o=>{t(o),this._worker.removeEventListener("message",s),this._worker.removeEventListener("error",r),clearTimeout(a)};this._worker.addEventListener("message",s),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(i){this._error(i)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){throw e instanceof ErrorEvent||this.dispatchEvent(new ErrorEvent("error",{message:e instanceof Error?e.cause:e})),e instanceof Error?e:new Error(e instanceof ErrorEvent?e.message:"error",{cause:e})}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize))}destroy(e){e&&this._error(e),this._video&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}};let h=n;return u(h,"_supportsWebAssembly",null),u(h,"_hasAlphaBug",null),h});
|
|
1
|
+
(function(h,n){typeof exports=="object"&&typeof module!="undefined"?module.exports=n():typeof define=="function"&&define.amd?define(n):(h=typeof globalThis!="undefined"?globalThis:h||self,h.JASSUB=n())})(this,function(){"use strict";var _=Object.defineProperty;var v=(h,n,d)=>n in h?_(h,n,{enumerable:!0,configurable:!0,writable:!0,value:d}):h[n]=d;var f=(h,n,d)=>(v(h,typeof n!="symbol"?n+"":n,d),d);!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(d){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(r,o)=>{const l=this.getVideoPlaybackQuality(),m=this.mozPresentedFrames||this.mozPaintedFrames||l.totalVideoFrames-l.droppedVideoFrames;if(m>t){const c=this.mozFrameDelay||l.totalFrameDelay-e.totalFrameDelay||0,u=o-r;d(o,{presentationTime:o+c*1e3,expectedDisplayTime:o+u,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+u/1e3,presentedFrames:m,processingDuration:c}),delete this._rvfcpolyfillmap[a]}else this._rvfcpolyfillmap[a]=requestAnimationFrame(c=>s(o,c))},a=Date.now(),i=performance.now();return this._rvfcpolyfillmap[a]=requestAnimationFrame(r=>s(i,r)),a},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(d){cancelAnimationFrame(this._rvfcpolyfillmap[d]),delete this._rvfcpolyfillmap[d]});const n=class extends EventTarget{constructor(e={}){var i,r,o,l,m;super(),globalThis.Worker||this.destroy("Worker not supported"),n._test();const t=e.blendMode||"js",s=typeof createImageBitmap!="undefined"&&((i=e.asyncRender)!=null?i:!0),a=typeof OffscreenCanvas!="undefined"&&((r=e.offscreenRender)!=null?r:!0);this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&((o=e.onDemandRender)!=null?o:!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._canvasParent=null,this._video?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._video.nextSibling?this._video.parentNode.insertBefore(this._canvasParent,this._video.nextSibling):this._video.parentNode.appendChild(this._canvasParent)):this._canvas||this.destroy("Don't know where to render: you should give video or canvas in options."),this._canvas=e.canvas||document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas),this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),this._canvasctrl=a?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!a&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!e.debug,this.prescaleFactor=e.prescaleFactor||1,this.prescaleHeightLimit=e.prescaleHeightLimit||1080,this.maxRenderHeight=e.maxRenderHeight||0,this._worker=new Worker(n._supportsWebAssembly?e.workerUrl||"jassub-worker.js":e.legacyWorkerUrl||"jassub-worker-legacy.js"),this._worker.onmessage=c=>this._onmessage(c),this._worker.onerror=c=>this._error(c),this._worker.postMessage({target:"init",asyncRender:s,onDemandRender:this._onDemandRender,width:this._canvas.width,height:this._canvas.height,preMain:!0,blendMode:t,subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,hasAlphaBug:n._hasAlphaBug,useLocalFonts:"queryLocalFonts"in self&&((l=e.useLocalFonts)!=null?l:!0)}),a===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null,(m=this._video)==null||m.requestVideoFrameCallback(this._handleRVFC.bind(this)))}static _test(){if(n._supportsWebAssembly!==null)return null;const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(typeof ImageData.prototype.constructor=="function")try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch{console.log("detected that ImageData is not constructable despite browser saying so"),window.ImageData=function(l,m,c){const u=t.createImageData(m,c);return l&&u.data.set(l),u}}try{if(typeof WebAssembly=="object"&&typeof WebAssembly.instantiate=="function"){const o=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));o instanceof WebAssembly.Module&&(n._supportsWebAssembly=new WebAssembly.Instance(o)instanceof WebAssembly.Instance)}}catch{n._supportsWebAssembly=!1}const s=document.createElement("canvas"),a=s.getContext("2d",{willReadFrequently:!0});e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const i=a.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(e,0,0);const r=a.getImageData(0,0,1,1).data;n._hasAlphaBug=i[1]!==r[1],n._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),s.remove()}resize(e=0,t=0,s=0,a=0){let i=null;if((!e||!t)&&this._video){i=this._getVideoPosition();const r=this._computeCanvasSize((i.width||0)*(window.devicePixelRatio||1),(i.height||0)*(window.devicePixelRatio||1));e=r.width,t=r.height,s=i.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),a=i.x}i!=null&&(this._canvas.style.top=s+"px",this._canvas.style.left=a+"px",this._canvas.style.width=i.width+"px",this._canvas.style.height=i.height+"px"),this._canvasctrl.width===e&&this._canvasctrl.height===t||(this._resizeTimeoutBuffer?(clearTimeout(this._resizeTimeoutBuffer),this._resizeTimeoutBuffer=setTimeout(()=>{this._resizeTimeoutBuffer=void 0,this._canvasctrl.width=e,this._canvasctrl.height=t,this.sendMessage("canvas",{width:e,height:t})},100)):(this._canvasctrl.width=e,this._canvasctrl.height=t,this.sendMessage("canvas",{width:e,height:t}),this._resizeTimeoutBuffer=setTimeout(()=>{this._resizeTimeoutBuffer=void 0},100)))}_getVideoPosition(){const e=this._video.videoWidth/this._video.videoHeight,{offsetWidth:t,offsetHeight:s}=this._video,a=t/s;let i=t,r=s;a>e?i=Math.floor(s*e):r=Math.floor(t/e);const o=(t-i)/2,l=(s-r)/2;return{width:i,height:r,x:o,y:l}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let i=t;a*i*s<=a*this.prescaleHeightLimit?i*=s:a*i<a*this.prescaleHeightLimit&&(i=this.prescaleHeightLimit),this.maxRenderHeight>0&&i>this.maxRenderHeight&&(i=this.maxRenderHeight),e*=i/t,t=i}return{width:e,height:t}}_timeupdate({type:e}){const s={seeking:!0,waiting:!0,playing:!1}[e];s!=null&&(this._playstate=s),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1)),e.videoWidth>0&&this.resize(),e.addEventListener("resize",this._boundResize),typeof ResizeObserver!="undefined"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e})}setTrack(e){this.sendMessage("setTrack",{content:e})}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{event:e,index:t})}removeStyle(e){this.sendMessage("removeStyle",{index:e})}getStyles(e){this._fetchFromWorker({target:"getStyles"},(t,{styles:s})=>{e(t,s)})}addFont(e){this.sendMessage("addFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t&&t.filter(a=>a.fullName.toLowerCase()===e);s&&s.length&&s[0].blob().then(a=>{a.arrayBuffer().then(i=>{this.addFont(new Uint8Array(i))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t;try{(t=navigator==null?void 0:navigator.permissions)!=null&&t.query?navigator.permissions.query({name:"local-fonts"}).then(s=>{s.state==="granted"&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(s){console.warn("Local fonts API:",s)}}_unbusy(){this._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t}){if(this._destroyed)return null;this.busy?this._lastDemandTime=t:(this.busy=!0,this._demandRender(t)),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender(e){this._lastDemandTime=null,this.sendMessage("demand",{time:e+this.timeOffset})}_render({images:e,async:t,times:s}){const a=Date.now();this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const i of e)i.image&&(t?(this._ctx.drawImage(i.image,i.x,i.y),i.image.close()):(this._bufferCanvas.width=i.w,this._bufferCanvas.height=i.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(i.image)),i.w,i.h),0,0),this._ctx.drawImage(this._bufferCanvas,i.x,i.y)));if(this.debug){s.drawTime=Date.now()-a;let i=0;for(const r in s)i+=s[r];console.log("Bitmaps: "+e.length+" Total: "+Math.round(i)+"ms",s)}}_fixAlpha(e){if(n._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this.dispatchEvent(new CustomEvent("ready"))}sendMessage(e,t={},s){s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,a=setTimeout(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),i=({data:o})=>{o.target===s&&(t(null,o),this._worker.removeEventListener("message",i),this._worker.removeEventListener("error",r),clearTimeout(a))},r=o=>{t(o),this._worker.removeEventListener("message",i),this._worker.removeEventListener("error",r),clearTimeout(a)};this._worker.addEventListener("message",i),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){throw e instanceof ErrorEvent||this.dispatchEvent(new ErrorEvent("error",{message:e instanceof Error?e.cause:e})),e instanceof Error?e:new Error(e instanceof ErrorEvent?e.message:"error",{cause:e})}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize))}destroy(e){e&&this._error(e),this._video&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}};let h=n;return f(h,"_supportsWebAssembly",null),f(h,"_hasAlphaBug",null),h});
|
package/package.json
CHANGED
package/src/jassub.js
CHANGED
|
@@ -37,9 +37,9 @@ export default class JASSUB extends EventTarget {
|
|
|
37
37
|
this.destroy('Worker not supported')
|
|
38
38
|
}
|
|
39
39
|
JASSUB._test()
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
40
|
+
const blendMode = options.blendMode || 'js'
|
|
41
|
+
const asyncRender = typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true)
|
|
42
|
+
const offscreenRender = typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true)
|
|
43
43
|
this._onDemandRender = 'requestVideoFrameCallback' in HTMLVideoElement.prototype && (options.onDemandRender ?? true)
|
|
44
44
|
|
|
45
45
|
this.timeOffset = options.timeOffset || 0
|
|
@@ -68,8 +68,8 @@ export default class JASSUB extends EventTarget {
|
|
|
68
68
|
this._bufferCanvas = document.createElement('canvas')
|
|
69
69
|
this._bufferCtx = this._bufferCanvas.getContext('2d')
|
|
70
70
|
|
|
71
|
-
this._canvasctrl =
|
|
72
|
-
this._ctx = !
|
|
71
|
+
this._canvasctrl = offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas
|
|
72
|
+
this._ctx = !offscreenRender && this._canvasctrl.getContext('2d')
|
|
73
73
|
|
|
74
74
|
this._lastRenderTime = 0
|
|
75
75
|
this.debug = !!options.debug
|
|
@@ -84,11 +84,12 @@ export default class JASSUB extends EventTarget {
|
|
|
84
84
|
|
|
85
85
|
this._worker.postMessage({
|
|
86
86
|
target: 'init',
|
|
87
|
-
asyncRender
|
|
87
|
+
asyncRender,
|
|
88
|
+
onDemandRender: this._onDemandRender,
|
|
88
89
|
width: this._canvas.width,
|
|
89
90
|
height: this._canvas.height,
|
|
90
91
|
preMain: true,
|
|
91
|
-
blendMode
|
|
92
|
+
blendMode,
|
|
92
93
|
subUrl: options.subUrl,
|
|
93
94
|
subContent: options.subContent || null,
|
|
94
95
|
fonts: options.fonts || [],
|
|
@@ -100,9 +101,9 @@ export default class JASSUB extends EventTarget {
|
|
|
100
101
|
libassMemoryLimit: options.libassMemoryLimit || 0,
|
|
101
102
|
libassGlyphLimit: options.libassGlyphLimit || 0,
|
|
102
103
|
hasAlphaBug: JASSUB._hasAlphaBug,
|
|
103
|
-
useLocalFonts: ('queryLocalFonts' in self) &&
|
|
104
|
+
useLocalFonts: ('queryLocalFonts' in self) && (options.useLocalFonts ?? true)
|
|
104
105
|
})
|
|
105
|
-
if (
|
|
106
|
+
if (offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
|
|
106
107
|
|
|
107
108
|
this._boundResize = this.resize.bind(this)
|
|
108
109
|
this._boundTimeUpdate = this._timeupdate.bind(this)
|
|
@@ -111,7 +112,8 @@ export default class JASSUB extends EventTarget {
|
|
|
111
112
|
|
|
112
113
|
if (this._onDemandRender) {
|
|
113
114
|
this.busy = false
|
|
114
|
-
this.
|
|
115
|
+
this._lastDemandTime = null
|
|
116
|
+
this._video?.requestVideoFrameCallback(this._handleRVFC.bind(this))
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -124,7 +126,7 @@ export default class JASSUB extends EventTarget {
|
|
|
124
126
|
if (JASSUB._supportsWebAssembly !== null) return null
|
|
125
127
|
|
|
126
128
|
const canvas1 = document.createElement('canvas')
|
|
127
|
-
const ctx1 = canvas1.getContext('2d')
|
|
129
|
+
const ctx1 = canvas1.getContext('2d', { willReadFrequently: true })
|
|
128
130
|
// test ImageData constructor
|
|
129
131
|
if (typeof ImageData.prototype.constructor === 'function') {
|
|
130
132
|
try {
|
|
@@ -155,7 +157,7 @@ export default class JASSUB extends EventTarget {
|
|
|
155
157
|
// Test for alpha bug, where e.g. WebKit can render a transparent pixel
|
|
156
158
|
// (with alpha == 0) as non-black which then leads to visual artifacts.
|
|
157
159
|
const canvas2 = document.createElement('canvas')
|
|
158
|
-
const ctx2 = canvas2.getContext('2d')
|
|
160
|
+
const ctx2 = canvas2.getContext('2d', { willReadFrequently: true })
|
|
159
161
|
|
|
160
162
|
canvas1.width = canvas2.width = 1
|
|
161
163
|
canvas1.height = canvas2.height = 1
|
|
@@ -277,7 +279,9 @@ export default class JASSUB extends EventTarget {
|
|
|
277
279
|
if (video instanceof HTMLVideoElement) {
|
|
278
280
|
this._removeListeners()
|
|
279
281
|
this._video = video
|
|
280
|
-
if (this._onDemandRender
|
|
282
|
+
if (this._onDemandRender) {
|
|
283
|
+
this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))
|
|
284
|
+
} else {
|
|
281
285
|
this._playstate = video.paused
|
|
282
286
|
|
|
283
287
|
video.addEventListener('timeupdate', this._boundTimeUpdate, false)
|
|
@@ -499,16 +503,15 @@ export default class JASSUB extends EventTarget {
|
|
|
499
503
|
|
|
500
504
|
_getLocalFont ({ font }) {
|
|
501
505
|
try {
|
|
502
|
-
// electron by default has all permissions enabled, and it doesn't have
|
|
503
|
-
// if this happens,
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
query({ name: 'local-fonts' }).then(permission => {
|
|
506
|
+
// electron by default has all permissions enabled, and it doesn't have perm query
|
|
507
|
+
// if this happens, just send it
|
|
508
|
+
if (navigator?.permissions?.query) {
|
|
509
|
+
navigator.permissions.query({ name: 'local-fonts' }).then(permission => {
|
|
507
510
|
if (permission.state === 'granted') {
|
|
508
511
|
this._sendLocalFont(font)
|
|
509
512
|
}
|
|
510
513
|
})
|
|
511
|
-
} else
|
|
514
|
+
} else {
|
|
512
515
|
this._sendLocalFont(font)
|
|
513
516
|
}
|
|
514
517
|
} catch (e) {
|
|
@@ -517,16 +520,28 @@ export default class JASSUB extends EventTarget {
|
|
|
517
520
|
}
|
|
518
521
|
|
|
519
522
|
_unbusy () {
|
|
520
|
-
|
|
523
|
+
// play catchup, leads to more frames being painted, but also more jitter
|
|
524
|
+
if (this._lastDemandTime) {
|
|
525
|
+
this._demandRender(this._lastDemandTime)
|
|
526
|
+
} else {
|
|
527
|
+
this.busy = false
|
|
528
|
+
}
|
|
521
529
|
}
|
|
522
530
|
|
|
523
|
-
|
|
531
|
+
_handleRVFC (now, { mediaTime }) {
|
|
524
532
|
if (this._destroyed) return null
|
|
525
|
-
if (
|
|
533
|
+
if (this.busy) {
|
|
534
|
+
this._lastDemandTime = mediaTime
|
|
535
|
+
} else {
|
|
526
536
|
this.busy = true
|
|
527
|
-
this.
|
|
537
|
+
this._demandRender(mediaTime)
|
|
528
538
|
}
|
|
529
|
-
this._video.requestVideoFrameCallback(this.
|
|
539
|
+
this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
_demandRender (time) {
|
|
543
|
+
this._lastDemandTime = null
|
|
544
|
+
this.sendMessage('demand', { time: time + this.timeOffset })
|
|
530
545
|
}
|
|
531
546
|
|
|
532
547
|
_render ({ images, async, times }) {
|