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 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 _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);
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 = _offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas;
77
- this._ctx = !_offscreenRender && this._canvasctrl.getContext("2d");
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: _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: _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 && !!options.useLocalFonts
105
+ useLocalFonts: "queryLocalFonts" in self && ((_d = options.useLocalFonts) != null ? _d : true)
105
106
  });
106
- if (_offscreenRender === true)
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._video.requestVideoFrameCallback(this._demandRender.bind(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 !== true) {
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, _b;
343
+ var _a;
340
344
  try {
341
- const query = ((_a = navigator == null ? void 0 : navigator.permissions) == null ? void 0 : _a.request) || ((_b = navigator == null ? void 0 : navigator.permissions) == null ? void 0 : _b.query);
342
- if (query) {
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 if ("queryLocalFonts" in self) {
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.busy = false;
359
+ if (this._lastDemandTime) {
360
+ this._demandRender(this._lastDemandTime);
361
+ } else {
362
+ this.busy = false;
363
+ }
357
364
  }
358
- _demandRender(now, metadata) {
365
+ _handleRVFC(now, { mediaTime }) {
359
366
  if (this._destroyed)
360
367
  return null;
361
- if (!this.busy) {
368
+ if (this.busy) {
369
+ this._lastDemandTime = mediaTime;
370
+ } else {
362
371
  this.busy = true;
363
- this.sendMessage("demand", { time: metadata.mediaTime + this.timeOffset });
372
+ this._demandRender(mediaTime);
364
373
  }
365
- this._video.requestVideoFrameCallback(this._demandRender.bind(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();
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jassub",
3
- "version": "1.1.13",
3
+ "version": "1.2.0",
4
4
  "description": "libass Subtitle Renderer and Parser library for browsers",
5
5
  "main": "src/jassub.js",
6
6
  "files": [
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 _blendMode = options.blendMode || 'js'
41
- const _asyncRender = typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true)
42
- const _offscreenRender = typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true)
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 = _offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas
72
- this._ctx = !_offscreenRender && this._canvasctrl.getContext('2d')
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: _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: _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) && !!options.useLocalFonts
104
+ useLocalFonts: ('queryLocalFonts' in self) && (options.useLocalFonts ?? true)
104
105
  })
105
- if (_offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
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._video.requestVideoFrameCallback(this._demandRender.bind(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 !== true) {
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 requesting
503
- // if this happens, make sure you can query fonts
504
- const query = navigator?.permissions?.request || navigator?.permissions?.query
505
- if (query) {
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 if ('queryLocalFonts' in self) {
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
- this.busy = false
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
- _demandRender (now, metadata) {
531
+ _handleRVFC (now, { mediaTime }) {
524
532
  if (this._destroyed) return null
525
- if (!this.busy) {
533
+ if (this.busy) {
534
+ this._lastDemandTime = mediaTime
535
+ } else {
526
536
  this.busy = true
527
- this.sendMessage('demand', { time: metadata.mediaTime + this.timeOffset })
537
+ this._demandRender(mediaTime)
528
538
  }
529
- this._video.requestVideoFrameCallback(this._demandRender.bind(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 }) {