jassub 1.0.1 → 1.1.2

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.
Binary file
package/dist/jassub.es.js CHANGED
@@ -107,14 +107,15 @@ const _JASSUB = class extends EventTarget {
107
107
  subUrl: options.subUrl,
108
108
  subContent: options.subContent || null,
109
109
  fonts: options.fonts || [],
110
- availableFonts: options.availableFonts || [],
111
- fallbackFont: options.fallbackFont || "./default.woff2",
110
+ availableFonts: options.availableFonts || { "liberation sans": "./default.woff2" },
111
+ fallbackFont: options.fallbackFont || "liberation sans",
112
112
  debug: this.debug,
113
113
  targetFps: options.targetFps || 24,
114
114
  dropAllAnimations: options.dropAllAnimations,
115
115
  libassMemoryLimit: options.libassMemoryLimit || 0,
116
116
  libassGlyphLimit: options.libassGlyphLimit || 0,
117
- hasAlphaBug: _JASSUB._hasAlphaBug
117
+ hasAlphaBug: _JASSUB._hasAlphaBug,
118
+ useLocalFonts: "queryLocalFonts" in self && !!options.useLocalFonts
118
119
  });
119
120
  if (_offscreenRender === true)
120
121
  this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
@@ -256,21 +257,19 @@ const _JASSUB = class extends EventTarget {
256
257
  this._video = video;
257
258
  if (this._onDemandRender !== true) {
258
259
  this._playstate = video.paused;
259
- video.addEventListener("timeupdate", (e) => this._timeupdate(e), false);
260
- video.addEventListener("progress", (e) => this._timeupdate(e), false);
261
- video.addEventListener("waiting", (e) => this._timeupdate(e), false);
262
- video.addEventListener("seeking", (e) => this._timeupdate(e), false);
263
- video.addEventListener("playing", (e) => this._timeupdate(e), false);
264
- video.addEventListener("ratechange", (e) => this.setRate(e), false);
260
+ video.addEventListener("timeupdate", this._timeupdate.bind(this), false);
261
+ video.addEventListener("progress", this._timeupdate.bind(this), false);
262
+ video.addEventListener("waiting", this._timeupdate.bind(this), false);
263
+ video.addEventListener("seeking", this._timeupdate.bind(this), false);
264
+ video.addEventListener("playing", this._timeupdate.bind(this), false);
265
+ video.addEventListener("ratechange", this.setRate.bind(this), false);
265
266
  }
266
- if (video.videoWidth > 0) {
267
+ if (video.videoWidth > 0)
267
268
  this.resize();
268
- } else {
269
- video.addEventListener("loadedmetadata", () => this.resize(0, 0, 0, 0), false);
270
- }
269
+ video.addEventListener("resize", this.resize.bind(this));
271
270
  if (typeof ResizeObserver !== "undefined") {
272
271
  if (!this._ro)
273
- this._ro = new ResizeObserver(() => this.resize(0, 0, 0, 0));
272
+ this._ro = new ResizeObserver(() => this.resize());
274
273
  this._ro.observe(video);
275
274
  }
276
275
  } else {
@@ -330,6 +329,29 @@ const _JASSUB = class extends EventTarget {
330
329
  callback(err, styles);
331
330
  });
332
331
  }
332
+ addFont(font) {
333
+ this.sendMessage("addFont", { font });
334
+ }
335
+ _getLocalFont({ font }) {
336
+ try {
337
+ navigator.permissions.request({ name: "local-fonts" }).then((permission) => {
338
+ if (permission.state === "granted") {
339
+ queryLocalFonts().then((fontData) => {
340
+ const filtered = fontData && fontData.filter((obj) => obj.fullName.toLowerCase() === font);
341
+ if (filtered && filtered.length) {
342
+ filtered[0].blob().then((blob) => {
343
+ blob.arrayBuffer().then((buffer) => {
344
+ this.addFont(new Uint8Array(buffer));
345
+ });
346
+ });
347
+ }
348
+ });
349
+ }
350
+ });
351
+ } catch (e) {
352
+ console.warn("Local fonts API:", e);
353
+ }
354
+ }
333
355
  _unbusy() {
334
356
  this.busy = false;
335
357
  }
@@ -438,6 +460,7 @@ const _JASSUB = class extends EventTarget {
438
460
  this._video.removeEventListener("seeking", this._timeupdate);
439
461
  this._video.removeEventListener("playing", this._timeupdate);
440
462
  this._video.removeEventListener("ratechange", this.setRate);
463
+ this._video.removeEventListener("resize", this.resize);
441
464
  }
442
465
  }
443
466
  destroy(err) {
@@ -1 +1 @@
1
- (function(h,a){typeof exports=="object"&&typeof module!="undefined"?module.exports=a():typeof define=="function"&&define.amd?define(a):(h=typeof globalThis!="undefined"?globalThis:h||self,h.JASSUB=a())})(this,function(){"use strict";var p=Object.defineProperty;var g=Object.getOwnPropertySymbols;var y=Object.prototype.hasOwnProperty,b=Object.prototype.propertyIsEnumerable;var u=(h,a,l)=>a in h?p(h,a,{enumerable:!0,configurable:!0,writable:!0,value:l}):h[a]=l,v=(h,a)=>{for(var l in a||(a={}))y.call(a,l)&&u(h,l,a[l]);if(g)for(var l of g(a))b.call(a,l)&&u(h,l,a[l]);return h};var _=(h,a,l)=>(u(h,typeof a!="symbol"?a+"":a,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||e.totalVideoFrames-e.droppedVideoFrames,i=(r,o)=>{const d=this.getVideoPlaybackQuality(),f=this.mozPresentedFrames||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[n]}else this._rvfcpolyfillmap[n]=requestAnimationFrame(c=>i(o,c))},n=Date.now(),s=performance.now();return this._rvfcpolyfillmap[n]=requestAnimationFrame(r=>i(s,r)),n},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(l){cancelAnimationFrame(this._rvfcpolyfillmap[l]),delete this._rvfcpolyfillmap[l]});const a=class extends EventTarget{constructor(e={}){var s,r,o;super(),globalThis.Worker||this.destroy("Worker not supported"),a._test();const t=e.blendMode||"js",i=typeof createImageBitmap!="undefined"&&((s=e.asyncRender)!=null?s:!0),n=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=n?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!n&&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(a._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||[],fallbackFont:e.fallbackFont||"./default.woff2",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,hasAlphaBug:a._hasAlphaBug}),n===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._video.requestVideoFrameCallback(this._demandRender.bind(this)))}static _test(){if(a._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&&(a._supportsWebAssembly=new WebAssembly.Instance(o)instanceof WebAssembly.Instance)}}catch{a._supportsWebAssembly=!1}const i=document.createElement("canvas"),n=i.getContext("2d");e.width=i.width=1,e.height=i.height=1,t.clearRect(0,0,1,1),n.clearRect(0,0,1,1);const s=n.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),n.drawImage(e,0,0);const r=n.getImageData(0,0,1,1).data;a._hasAlphaBug=s[1]!==r[1],a._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),i.remove()}resize(e=0,t=0,i=0,n=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),n=s.x}(this._canvas.style.top!==i+"px"||this._canvas.style.left!==n+"px")&&(s!=null&&(this._canvas.style.top=i+"px",this._canvas.style.left=n+"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,n=t/i;let s=t,r=i;n>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 n=i<1?-1:1;let s=t;n*s*i<=n*this.prescaleHeightLimit?s*=i:n*s<n*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",t=>this._timeupdate(t),!1),e.addEventListener("progress",t=>this._timeupdate(t),!1),e.addEventListener("waiting",t=>this._timeupdate(t),!1),e.addEventListener("seeking",t=>this._timeupdate(t),!1),e.addEventListener("playing",t=>this._timeupdate(t),!1),e.addEventListener("ratechange",t=>this.setRate(t),!1)),e.videoWidth>0?this.resize():e.addEventListener("loadedmetadata",()=>this.resize(0,0,0,0),!1),typeof ResizeObserver!="undefined"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize(0,0,0,0))),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)})}_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 n=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()-n;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(a._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(v({target:e,transferable:i},t),[...i]):this._worker.postMessage(v({target:e},t))}_fetchFromWorker(e,t){try{const i=e.target,n=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(n))},r=o=>{t(o),this._worker.removeEventListener("message",s),this._worker.removeEventListener("error",r),clearTimeout(n)};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._timeupdate),this._video.removeEventListener("progress",this._timeupdate),this._video.removeEventListener("waiting",this._timeupdate),this._video.removeEventListener("seeking",this._timeupdate),this._video.removeEventListener("playing",this._timeupdate),this._video.removeEventListener("ratechange",this.setRate))}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=a;return _(h,"_supportsWebAssembly",null),_(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 p=Object.defineProperty;var g=Object.getOwnPropertySymbols;var y=Object.prototype.hasOwnProperty,b=Object.prototype.propertyIsEnumerable;var u=(h,n,l)=>n in h?p(h,n,{enumerable:!0,configurable:!0,writable:!0,value:l}):h[n]=l,v=(h,n)=>{for(var l in n||(n={}))y.call(n,l)&&u(h,l,n[l]);if(g)for(var l of g(n))b.call(n,l)&&u(h,l,n[l]);return h};var _=(h,n,l)=>(u(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||e.totalVideoFrames-e.droppedVideoFrames,i=(r,o)=>{const d=this.getVideoPlaybackQuality(),f=this.mozPresentedFrames||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.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}(this._canvas.style.top!==i+"px"||this._canvas.style.left!==a+"px")&&(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._timeupdate.bind(this),!1),e.addEventListener("progress",this._timeupdate.bind(this),!1),e.addEventListener("waiting",this._timeupdate.bind(this),!1),e.addEventListener("seeking",this._timeupdate.bind(this),!1),e.addEventListener("playing",this._timeupdate.bind(this),!1),e.addEventListener("ratechange",this.setRate.bind(this),!1)),e.videoWidth>0&&this.resize(),e.addEventListener("resize",this.resize.bind(this)),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})}_getLocalFont({font:e}){try{navigator.permissions.request({name:"local-fonts"}).then(t=>{t.state==="granted"&&queryLocalFonts().then(i=>{const a=i&&i.filter(s=>s.fullName.toLowerCase()===e);a&&a.length&&a[0].blob().then(s=>{s.arrayBuffer().then(r=>{this.addFont(new Uint8Array(r))})})})})}catch(t){console.warn("Local fonts API:",t)}}_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(v({target:e,transferable:i},t),[...i]):this._worker.postMessage(v({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._timeupdate),this._video.removeEventListener("progress",this._timeupdate),this._video.removeEventListener("waiting",this._timeupdate),this._video.removeEventListener("seeking",this._timeupdate),this._video.removeEventListener("playing",this._timeupdate),this._video.removeEventListener("ratechange",this.setRate),this._video.removeEventListener("resize",this.resize))}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 _(h,"_supportsWebAssembly",null),_(h,"_hasAlphaBug",null),h});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jassub",
3
- "version": "1.0.1",
3
+ "version": "1.1.2",
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
@@ -25,8 +25,9 @@ export default class JASSUB extends EventTarget {
25
25
  * @param {String} [options.subUrl=options.subContent] The URL of the subtitle file to play.
26
26
  * @param {String} [options.subContent=options.subUrl] The content of the subtitle file to play.
27
27
  * @param {String[]|Uint8Array[]} [options.fonts] An array of links or Uint8Arrays to the fonts used in the subtitle. If Uint8Array is used the array is copied, not referenced. This forces all the fonts in this array to be loaded by the renderer, regardless of if they are used.
28
- * @param {Object} [options.availableFonts] Object with all available fonts - Key is font name in lower case, value is link: { arial: '/font1.ttf' }. These fonts are selectively loaded if detected as used in the current subtitle track.
29
- * @param {String} [options.fallbackFont='default.woff2'] The URL of the fallback font to use.
28
+ * @param {Object} [options.availableFonts={'liberation sans': './default.woff2'}] Object with all available fonts - Key is font family in lower case, value is link or Uint8Array: { arial: '/font1.ttf' }. These fonts are selectively loaded if detected as used in the current subtitle track.
29
+ * @param {String} [options.fallbackFont='liberation sans'] The font family key of the fallback font in availableFonts to use if the other font for the style is missing special glyphs or unicode.
30
+ * @param {Boolean} [options.useLocalFonts=false] If the Local Font Access API is enabled [chrome://flags/#font-access], the library will query for permissions to use local fonts and use them if any are missing. The permission can be queried beforehand using navigator.permissions.request({ name: 'local-fonts' }).
30
31
  * @param {Number} [options.libassMemoryLimit] libass bitmap cache memory limit in MiB (approximate).
31
32
  * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate).
32
33
  */
@@ -91,14 +92,15 @@ export default class JASSUB extends EventTarget {
91
92
  subUrl: options.subUrl,
92
93
  subContent: options.subContent || null,
93
94
  fonts: options.fonts || [],
94
- availableFonts: options.availableFonts || [],
95
- fallbackFont: options.fallbackFont || './default.woff2',
95
+ availableFonts: options.availableFonts || { 'liberation sans': './default.woff2' },
96
+ fallbackFont: options.fallbackFont || 'liberation sans',
96
97
  debug: this.debug,
97
98
  targetFps: options.targetFps || 24,
98
99
  dropAllAnimations: options.dropAllAnimations,
99
100
  libassMemoryLimit: options.libassMemoryLimit || 0,
100
101
  libassGlyphLimit: options.libassGlyphLimit || 0,
101
- hasAlphaBug: JASSUB._hasAlphaBug
102
+ hasAlphaBug: JASSUB._hasAlphaBug,
103
+ useLocalFonts: ('queryLocalFonts' in self) && !!options.useLocalFonts
102
104
  })
103
105
  if (_offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
104
106
  this.setVideo(options.video)
@@ -275,21 +277,18 @@ export default class JASSUB extends EventTarget {
275
277
  if (this._onDemandRender !== true) {
276
278
  this._playstate = video.paused
277
279
 
278
- video.addEventListener('timeupdate', e => this._timeupdate(e), false)
279
- video.addEventListener('progress', e => this._timeupdate(e), false)
280
- video.addEventListener('waiting', e => this._timeupdate(e), false)
281
- video.addEventListener('seeking', e => this._timeupdate(e), false)
282
- video.addEventListener('playing', e => this._timeupdate(e), false)
283
- video.addEventListener('ratechange', e => this.setRate(e), false)
284
- }
285
- if (video.videoWidth > 0) {
286
- this.resize()
287
- } else {
288
- video.addEventListener('loadedmetadata', () => this.resize(0, 0, 0, 0), false)
280
+ video.addEventListener('timeupdate', this._timeupdate.bind(this), false)
281
+ video.addEventListener('progress', this._timeupdate.bind(this), false)
282
+ video.addEventListener('waiting', this._timeupdate.bind(this), false)
283
+ video.addEventListener('seeking', this._timeupdate.bind(this), false)
284
+ video.addEventListener('playing', this._timeupdate.bind(this), false)
285
+ video.addEventListener('ratechange', this.setRate.bind(this), false)
289
286
  }
287
+ if (video.videoWidth > 0) this.resize()
288
+ video.addEventListener('resize', this.resize.bind(this))
290
289
  // Support Element Resize Observer
291
290
  if (typeof ResizeObserver !== 'undefined') {
292
- if (!this._ro) this._ro = new ResizeObserver(() => this.resize(0, 0, 0, 0))
291
+ if (!this._ro) this._ro = new ResizeObserver(() => this.resize())
293
292
  this._ro.observe(video)
294
293
  }
295
294
  } else {
@@ -470,6 +469,35 @@ export default class JASSUB extends EventTarget {
470
469
  })
471
470
  }
472
471
 
472
+ /**
473
+ * Adds a font to the renderer.
474
+ * @param {String|Uint8Array} font Font to add.
475
+ */
476
+ addFont (font) {
477
+ this.sendMessage('addFont', { font })
478
+ }
479
+
480
+ _getLocalFont ({ font }) {
481
+ try {
482
+ navigator.permissions.request({ name: 'local-fonts' }).then(permission => {
483
+ if (permission.state === 'granted') {
484
+ queryLocalFonts().then(fontData => {
485
+ const filtered = fontData && fontData.filter(obj => obj.fullName.toLowerCase() === font)
486
+ if (filtered && filtered.length) {
487
+ filtered[0].blob().then(blob => {
488
+ blob.arrayBuffer().then(buffer => {
489
+ this.addFont(new Uint8Array(buffer))
490
+ })
491
+ })
492
+ }
493
+ })
494
+ }
495
+ })
496
+ } catch (e) {
497
+ console.warn('Local fonts API:', e)
498
+ }
499
+ }
500
+
473
501
  _unbusy () {
474
502
  this.busy = false
475
503
  }
@@ -596,6 +624,7 @@ export default class JASSUB extends EventTarget {
596
624
  this._video.removeEventListener('seeking', this._timeupdate)
597
625
  this._video.removeEventListener('playing', this._timeupdate)
598
626
  this._video.removeEventListener('ratechange', this.setRate)
627
+ this._video.removeEventListener('resize', this.resize)
599
628
  }
600
629
  }
601
630
 
@@ -1,35 +0,0 @@
1
- <?xml version="1.0"?>
2
- <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
3
- <fontconfig>
4
- <dir>/fonts</dir>
5
- <match target="pattern">
6
- <test qual="any" name="family">
7
- <string>mono</string>
8
- </test>
9
- <edit name="family" mode="assign" binding="same">
10
- <string>monospace</string>
11
- </edit>
12
- </match>
13
- <match target="pattern">
14
- <test qual="any" name="family">
15
- <string>sans serif</string>
16
- </test>
17
- <edit name="family" mode="assign" binding="same">
18
- <string>sans-serif</string>
19
- </edit>
20
- </match>
21
- <match target="pattern">
22
- <test qual="any" name="family">
23
- <string>sans</string>
24
- </test>
25
- <edit name="family" mode="assign" binding="same">
26
- <string>sans-serif</string>
27
- </edit>
28
- </match>
29
- <cachedir>/fontconfig</cachedir>
30
- <config>
31
- <rescan>
32
- <int>30</int>
33
- </rescan>
34
- </config>
35
- </fontconfig>