jassub 1.6.2 → 1.7.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/README.md +9 -4
- package/dist/jassub-worker-modern.wasm +0 -0
- package/dist/jassub-worker.js +16 -18
- package/dist/jassub-worker.wasm +0 -0
- package/dist/jassub-worker.wasm.js +1 -0
- package/dist/jassub.es.js +488 -475
- package/dist/jassub.umd.js +1 -2
- package/index.d.ts +4 -1
- package/package.json +5 -5
- package/src/jassub.js +140 -92
- package/dist/jassub-worker-legacy.js +0 -18
package/dist/jassub.umd.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
(function(d,l){typeof exports=="object"&&typeof module<"u"?module.exports=l():typeof define=="function"&&define.amd?define(l):(d=typeof globalThis<"u"?globalThis:d||self,d.JASSUB=l())})(this,function(){"use strict";var g=Object.defineProperty;var p=(d,l,h)=>l in d?g(d,l,{enumerable:!0,configurable:!0,writable:!0,value:h}):d[l]=h;var u=(d,l,h)=>(p(d,typeof l!="symbol"?l+"":l,h),h);!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(_){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(n,r)=>{const a=this.getVideoPlaybackQuality(),m=this.mozPresentedFrames||this.mozPaintedFrames||a.totalVideoFrames-a.droppedVideoFrames;if(m>t){const v=this.mozFrameDelay||a.totalFrameDelay-e.totalFrameDelay||0,f=r-n;_(r,{presentationTime:r+v*1e3,expectedDisplayTime:r+f,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+f/1e3,presentedFrames:m,processingDuration:v}),delete this._rvfcpolyfillmap[i]}else this._rvfcpolyfillmap[i]=requestAnimationFrame(v=>s(r,v))},i=Date.now(),o=performance.now();return this._rvfcpolyfillmap[i]=requestAnimationFrame(n=>s(o,n)),i},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(_){cancelAnimationFrame(this._rvfcpolyfillmap[_]),delete this._rvfcpolyfillmap[_]});const d={bt709:"BT.709",bt470bg:"BT.601",smpte170m:"BT.601"},l={"BT.601":{"BT.709":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418 0 0 0 0 0 1 0'/></filter></svg>#f")`},"BT.709":{"BT.601":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582 0 0 0 0 0 1 0'/></filter></svg>#f")`},FCC:{"BT.709":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251
|
|
2
|
-
1.0378 0 0 0 0 0 1 0'/></filter></svg>#f")`,"BT.601":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996 0 0 0 0 0 1 0'/></filter></svg>#f")`},SMPTE240M:{"BT.709":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148 0 0 0 0 0 1 0'/></filter></svg>#f")`,"BT.601":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973 0 0 0 0 0 1 0'/></filter></svg>#f")`}},c=class extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),c._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e.onDemandRender??!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=e.canvas,this._video&&!this._canvas?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._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._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._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),this._canvasctrl=this._canvas,this._ctx=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(c._supportsWebAssembly?e.workerUrl||"jassub-worker.js":e.legacyWorkerUrl||"jassub-worker-legacy.js"),this._worker.onmessage=t=>this._onmessage(t),this._worker.onerror=t=>this._error(t),this._loaded=new Promise(t=>{this._init=()=>{this._destroyed||(this._worker.postMessage({target:"init",asyncRender:typeof createImageBitmap<"u"&&(e.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,preMain:!0,blendMode:e.blendMode||"js",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:c._hasAlphaBug,offscreenRender:typeof OffscreenCanvas<"u"&&(e.offscreenRender??!0),useLocalFonts:"queryLocalFonts"in self&&(e.useLocalFonts??!0)}),this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),t())}})}static _test(){if(c._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"),self.ImageData=function(a,m,v){const f=t.createImageData(m,v);return a&&f.data.set(a),f}}try{if(typeof WebAssembly=="object"&&typeof WebAssembly.instantiate=="function"){const r=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));r instanceof WebAssembly.Module&&(c._supportsWebAssembly=new WebAssembly.Instance(r)instanceof WebAssembly.Instance)}}catch{c._supportsWebAssembly=!1}const s=document.createElement("canvas"),i=s.getContext("2d",{willReadFrequently:!0});e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),i.clearRect(0,0,1,1);const o=i.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),i.drawImage(e,0,0);const n=i.getImageData(0,0,1,1).data;c._hasAlphaBug=o[1]!==n[1],c._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),e.remove(),s.remove()}resize(e=0,t=0,s=0,i=0,o=(n=>(n=this._video)==null?void 0:n.paused)()){if((!e||!t)&&this._video){const r=this._getVideoPosition();let a=null;if(this._videoWidth){const m=this._video.videoWidth/this._videoWidth,v=this._video.videoHeight/this._videoHeight;a=this._computeCanvasSize((r.width||0)/m,(r.height||0)/v)}else a=this._computeCanvasSize(r.width||0,r.height||0);e=a.width,t=a.height,this._canvasParent&&(s=r.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=r.x),this._canvas.style.width=r.width+"px",this._canvas.style.height=r.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",this.sendMessage("canvas",{width:e,height:t,force:o&&this.busy===!1})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:o}=this._video,n=i/o;e=i,t=o,n>s?e=Math.floor(o*s):t=Math.floor(i/s);const r=(i-e)/2,a=(o-t)/2;return{width:e,height:t,x:r,y:a}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(e=e*i,t=t*i,t<=0||e<=0)e=0,t=0;else{const o=s<1?-1:1;let n=t*i;o*n*s<=o*this.prescaleHeightLimit?n*=s:o*n<o*this.prescaleHeightLimit&&(n=this.prescaleHeightLimit),this.maxRenderHeight>0&&n>this.maxRenderHeight&&(n=this.maxRenderHeight),e*=n/t,t=n}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)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{const e=new VideoFrame(this._video);this._videoColorSpace=d[e.colorSpace.matrix],e.close()})}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.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(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==null?void 0:t.find(i=>i.fullName.toLowerCase()===e);s&&s.blob().then(i=>{i.arrayBuffer().then(o=>{this.addFont(new Uint8Array(o))})})})}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,width:s,height:i}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:i}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:i})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}verifyColorSpace(e,t=this._videoColorSpace){!e||!t||e!==t&&(this._ctx.filter=l[e][t])}_render({images:e,async:t,times:s,width:i,height:o,colorSpace:n}){this._unbusy();const r=Date.now();(this._canvasctrl.width!==i||this._canvasctrl.height!==o)&&(this._canvasctrl.width=i,this._canvasctrl.height=o,this.verifyColorSpace(n)),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const a of e)a.image&&(t?(this._ctx.drawImage(a.image,a.x,a.y),a.image.close()):(this._bufferCanvas.width=a.w,this._bufferCanvas.height=a.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(a.image)),a.w,a.h),0,0),this._ctx.drawImage(this._bufferCanvas,a.x,a.y)));if(this.debug){s.drawTime=Date.now()-r;let a=0;for(const m in s)a+=s[m];console.log("Bitmaps: "+e.length+" Total: "+Math.round(a)+"ms",s)}}_fixAlpha(e){if(c._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,i=setTimeout(()=>{n(new Error("Error: Timeout while try to fetch "+s))},5e3),o=({data:r})=>{r.target===s&&(t(null,r),this._worker.removeEventListener("message",o),this._worker.removeEventListener("error",n),clearTimeout(i))},n=r=>{t(r),this._worker.removeEventListener("message",o),this._worker.removeEventListener("error",n),clearTimeout(i)};this._worker.addEventListener("message",o),this._worker.addEventListener("error",n),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){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});this.dispatchEvent(s),console.error(t)}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx.filter="none",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),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){e&&this._error(e),this._video&&this._canvasParent&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}};let h=c;return u(h,"_supportsWebAssembly",null),u(h,"_hasAlphaBug",null),h});
|
|
1
|
+
(function(c,_){typeof exports=="object"&&typeof module<"u"?module.exports=_():typeof define=="function"&&define.amd?define(_):(c=typeof globalThis<"u"?globalThis:c||self,c.JASSUB=_())})(this,function(){"use strict";!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(f){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(n,i)=>{const o=this.getVideoPlaybackQuality(),d=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(d>t){const l=this.mozFrameDelay||o.totalFrameDelay-e.totalFrameDelay||0,m=i-n;f(i,{presentationTime:i+l*1e3,expectedDisplayTime:i+m,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+m/1e3,presentedFrames:d,processingDuration:l}),delete this._rvfcpolyfillmap[a]}else this._rvfcpolyfillmap[a]=requestAnimationFrame(l=>s(i,l))},a=Date.now(),r=performance.now();return this._rvfcpolyfillmap[a]=requestAnimationFrame(n=>s(r,n)),a},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(f){cancelAnimationFrame(this._rvfcpolyfillmap[f]),delete this._rvfcpolyfillmap[f]});const c={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},_={BT601:{BT709:"1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"},BT709:{BT601:"0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582"},FCC:{BT709:"1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378",BT601:"1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996"},SMPTE240M:{BT709:"0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148",BT601:"0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"}};class h extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),this._loaded=new Promise(t=>{this._init=t}),h._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!e.canvas&&(e.offscreenRender??!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=e.canvas,this._video&&!this._canvas?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._createCanvas(),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._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&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._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(e.workerUrl||"jassub-worker.js"),this._worker.onmessage=t=>this._onmessage(t),this._worker.onerror=t=>this._error(t),this._worker.postMessage({target:"init",wasmUrl:h._supportsSIMD&&e.modernWasmUrl?e.modernWasmUrl:e.wasmUrl||"jassub-worker.wasm",legacyWasmUrl:e.legacyWasmUrl||"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(e.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:e.blendMode||"js",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,dropAllBlur:e.dropAllBlur,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(e.useLocalFonts??!0)}),this._offscreenRender===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])}_createCanvas(){this._canvas=document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas)}static _supportsSIMD=null;static _hasAlphaBug=null;static _test(){if(h._supportsSIMD!==null)return null;try{h._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch{h._supportsSIMD=!1}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"),self.ImageData=function(o,d,l){const m=t.createImageData(d,l);return o&&m.data.set(o),m}}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 r=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 n=a.getImageData(0,0,1,1).data;h._hasAlphaBug=r[1]!==n[1],h._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),e.remove(),s.remove()}resize(e=0,t=0,s=0,a=0,r=this._video?.paused){if((!e||!t)&&this._video){const n=this._getVideoPosition();let i=null;if(this._videoWidth){const o=this._video.videoWidth/this._videoWidth,d=this._video.videoHeight/this._videoHeight;i=this._computeCanvasSize((n.width||0)/o,(n.height||0)/d)}else i=this._computeCanvasSize(n.width||0,n.height||0);e=i.width,t=i.height,this._canvasParent&&(s=n.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),a=n.x),this._canvas.style.width=n.width+"px",this._canvas.style.height=n.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=a+"px",r&&this.busy===!1?this.busy=!0:r=!1,this.sendMessage("canvas",{width:e,height:t,force:r})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:a,offsetHeight:r}=this._video,n=a/r;e=a,t=r,n>s?e=Math.floor(r*s):t=Math.floor(a/s);const i=(a-e)/2,o=(r-t)/2;return{width:e,height:t,x:i,y:o}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,a=self.devicePixelRatio||1;if(e=e*a,t=t*a,t<=0||e<=0)e=0,t=0;else{const r=s<1?-1:1;let n=t*a;r*n*s<=r*this.prescaleHeightLimit?n*=s:r*n<r*this.prescaleHeightLimit&&(n=this.prescaleHeightLimit),this.maxRenderHeight>0&&n>this.maxRenderHeight&&(n=this.maxRenderHeight),e*=n/t,t=n}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.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(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}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}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,colorSpace:this._videoColorSpace})}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?.find(a=>a.fullName.toLowerCase()===e);s&&s.blob().then(a=>{a.arrayBuffer().then(r=>{this.addFont(new Uint8Array(r))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{t.state==="granted"&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:a}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:a}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:a})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=c[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='${_[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`)}_render({images:e,asyncRender:t,times:s,width:a,height:r,colorSpace:n}){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==a||this._canvasctrl.height!==r)&&(this._canvasctrl.width=a,this._canvasctrl.height=r,this._verifyColorSpace({subtitleColorSpace:n})),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.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let i=0;const o=s.bitmaps||e.length;delete s.bitmaps;for(const d in s)i+=s[d];console.log("Bitmaps: "+o+" Total: "+(i|0)+"ms",s)}}_fixAlpha(e){if(h._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,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(()=>{n(new Error("Error: Timeout while try to fetch "+s))},5e3),r=({data:i})=>{i.target===s&&(t(null,i),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(a))},n=i=>{t(i),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(a)};this._worker.addEventListener("message",r),this._worker.addEventListener("error",n),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){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});this.dispatchEvent(s),console.error(t)}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),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),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){e&&this._error(e),this._video&&this._canvasParent&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}}return h});
|
package/index.d.ts
CHANGED
|
@@ -60,9 +60,12 @@ interface JassubOptions {
|
|
|
60
60
|
prescaleHeightLimit?: number;
|
|
61
61
|
maxRenderHeight?: number;
|
|
62
62
|
dropAllAnimations?: boolean;
|
|
63
|
+
dropAllBlur?: boolean
|
|
63
64
|
|
|
64
65
|
workerUrl?: string;
|
|
65
|
-
|
|
66
|
+
wasmUrl?: string;
|
|
67
|
+
legacyWasmUrl?: string;
|
|
68
|
+
modernWasmUrl?: string;
|
|
66
69
|
|
|
67
70
|
subUrl?: string;
|
|
68
71
|
subContent?: string;
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jassub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "libass Subtitle Renderer and Parser library for browsers",
|
|
5
5
|
"main": "src/jassub.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"files": [
|
|
7
8
|
"dist/*",
|
|
8
9
|
"src/jassub.js",
|
|
@@ -32,11 +33,10 @@
|
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"vite": "^4.1.4",
|
|
35
|
-
"vite-plugin-static-copy": "^0.13.1"
|
|
36
|
+
"vite-plugin-static-copy": "^0.13.1",
|
|
37
|
+
"terser": "^5.17.3"
|
|
36
38
|
},
|
|
37
39
|
"scripts": {
|
|
38
|
-
"
|
|
39
|
-
"build-lib": "vite build --config vite.config.js",
|
|
40
|
-
"build-worker": "vite build --config vite-worker.config.js"
|
|
40
|
+
"build": "node vite.build.js"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/jassub.js
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
import 'rvfc-polyfill'
|
|
2
2
|
|
|
3
3
|
const webYCbCrMap = {
|
|
4
|
-
bt709: '
|
|
4
|
+
bt709: 'BT709',
|
|
5
5
|
// these might not be exactly correct? oops?
|
|
6
|
-
bt470bg: '
|
|
7
|
-
smpte170m: '
|
|
6
|
+
bt470bg: 'BT601', // alias BT.601 PAL... whats the difference?
|
|
7
|
+
smpte170m: 'BT601'// alias BT.601 NTSC... whats the difference?
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const colorMatrixConversionMap = {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
BT601: {
|
|
12
|
+
BT709: '1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418'
|
|
13
13
|
},
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
BT709: {
|
|
15
|
+
BT601: '0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582'
|
|
16
16
|
},
|
|
17
17
|
FCC: {
|
|
18
|
-
|
|
19
|
-
1.
|
|
20
|
-
'BT.601': 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'f\'><feColorMatrix type=\'matrix\' values=\'1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996 0 0 0 0 0 1 0\'/></filter></svg>#f")'
|
|
18
|
+
BT709: '1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378',
|
|
19
|
+
BT601: '1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996'
|
|
21
20
|
},
|
|
22
21
|
SMPTE240M: {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
BT709: '0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148',
|
|
23
|
+
BT601: '0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973'
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -45,8 +44,11 @@ export default class JASSUB extends EventTarget {
|
|
|
45
44
|
* @param {Number} [options.prescaleHeightLimit=1080] The height in pixels beyond which the subtitles canvas won't be prescaled.
|
|
46
45
|
* @param {Number} [options.maxRenderHeight=0] The maximum rendering height in pixels of the subtitles canvas. Beyond this subtitles will be upscaled by the browser.
|
|
47
46
|
* @param {Boolean} [options.dropAllAnimations=false] Attempt to discard all animated tags. Enabling this may severly mangle complex subtitles and should only be considered as an last ditch effort of uncertain success for hardware otherwise incapable of displaing anything. Will not reliably work with manually edited or allocated events.
|
|
47
|
+
* @param {Boolean} [options.dropAllBlur=false] The holy grail of performance gains. If heavy TS lags a lot, disabling this will make it ~x10 faster. This drops blur from all added subtitle tracks making most text and backgrounds look sharper, this is way less intrusive than dropping all animations, while still offering major performance gains.
|
|
48
48
|
* @param {String} [options.workerUrl='jassub-worker.js'] The URL of the worker.
|
|
49
|
-
* @param {String} [options.
|
|
49
|
+
* @param {String} [options.wasmUrl='jassub-worker.wasm'] The URL of the worker WASM.
|
|
50
|
+
* @param {String} [options.legacyWasmUrl='jassub-worker.wasm.js'] The URL of the worker WASM. Only loaded if the browser doesn't support WASM.
|
|
51
|
+
* @param {String} options.modernWasmUrl The URL of the modern worker WASM. This includes faster ASM instructions, but is only supported by newer browsers, disabled if the URL isn't defined.
|
|
50
52
|
* @param {String} [options.subUrl=options.subContent] The URL of the subtitle file to play.
|
|
51
53
|
* @param {String} [options.subContent=options.subUrl] The content of the subtitle file to play.
|
|
52
54
|
* @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.
|
|
@@ -61,9 +63,17 @@ export default class JASSUB extends EventTarget {
|
|
|
61
63
|
if (!globalThis.Worker) {
|
|
62
64
|
this.destroy('Worker not supported')
|
|
63
65
|
}
|
|
66
|
+
|
|
67
|
+
this._loaded = new Promise(resolve => {
|
|
68
|
+
this._init = resolve
|
|
69
|
+
})
|
|
70
|
+
|
|
64
71
|
JASSUB._test()
|
|
65
72
|
this._onDemandRender = 'requestVideoFrameCallback' in HTMLVideoElement.prototype && (options.onDemandRender ?? true)
|
|
66
73
|
|
|
74
|
+
// don't support offscreen rendering on custom canvases, as we can't replace it if colorSpace doesn't match
|
|
75
|
+
this._offscreenRender = 'transferControlToOffscreen' in HTMLCanvasElement.prototype && !options.canvas && (options.offscreenRender ?? true)
|
|
76
|
+
|
|
67
77
|
this.timeOffset = options.timeOffset || 0
|
|
68
78
|
this._video = options.video
|
|
69
79
|
this._videoHeight = 0
|
|
@@ -75,11 +85,7 @@ export default class JASSUB extends EventTarget {
|
|
|
75
85
|
this._canvasParent.className = 'JASSUB'
|
|
76
86
|
this._canvasParent.style.position = 'relative'
|
|
77
87
|
|
|
78
|
-
this.
|
|
79
|
-
this._canvas.style.display = 'block'
|
|
80
|
-
this._canvas.style.position = 'absolute'
|
|
81
|
-
this._canvas.style.pointerEvents = 'none'
|
|
82
|
-
this._canvasParent.appendChild(this._canvas)
|
|
88
|
+
this._createCanvas()
|
|
83
89
|
|
|
84
90
|
if (this._video.nextSibling) {
|
|
85
91
|
this._video.parentNode.insertBefore(this._canvasParent, this._video.nextSibling)
|
|
@@ -93,8 +99,8 @@ export default class JASSUB extends EventTarget {
|
|
|
93
99
|
this._bufferCanvas = document.createElement('canvas')
|
|
94
100
|
this._bufferCtx = this._bufferCanvas.getContext('2d')
|
|
95
101
|
|
|
96
|
-
this._canvasctrl = this._canvas
|
|
97
|
-
this._ctx = this._canvasctrl.getContext('2d')
|
|
102
|
+
this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas
|
|
103
|
+
this._ctx = !this._offscreenRender && this._canvasctrl.getContext('2d')
|
|
98
104
|
|
|
99
105
|
this._lastRenderTime = 0
|
|
100
106
|
this.debug = !!options.debug
|
|
@@ -103,58 +109,67 @@ export default class JASSUB extends EventTarget {
|
|
|
103
109
|
this.prescaleHeightLimit = options.prescaleHeightLimit || 1080
|
|
104
110
|
this.maxRenderHeight = options.maxRenderHeight || 0 // 0 - no limit.
|
|
105
111
|
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
108
|
-
this.
|
|
112
|
+
this._boundResize = this.resize.bind(this)
|
|
113
|
+
this._boundTimeUpdate = this._timeupdate.bind(this)
|
|
114
|
+
this._boundSetRate = this.setRate.bind(this)
|
|
115
|
+
this._boundUpdateColorSpace = this._updateColorSpace.bind(this)
|
|
116
|
+
if (this._video) this.setVideo(options.video)
|
|
109
117
|
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
target: 'init',
|
|
115
|
-
asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
|
|
116
|
-
onDemandRender: this._onDemandRender,
|
|
117
|
-
width: this._canvasctrl.width || 0,
|
|
118
|
-
height: this._canvasctrl.height || 0,
|
|
119
|
-
preMain: true,
|
|
120
|
-
blendMode: options.blendMode || 'js',
|
|
121
|
-
subUrl: options.subUrl,
|
|
122
|
-
subContent: options.subContent || null,
|
|
123
|
-
fonts: options.fonts || [],
|
|
124
|
-
availableFonts: options.availableFonts || { 'liberation sans': './default.woff2' },
|
|
125
|
-
fallbackFont: options.fallbackFont || 'liberation sans',
|
|
126
|
-
debug: this.debug,
|
|
127
|
-
targetFps: options.targetFps || 24,
|
|
128
|
-
dropAllAnimations: options.dropAllAnimations,
|
|
129
|
-
libassMemoryLimit: options.libassMemoryLimit || 0,
|
|
130
|
-
libassGlyphLimit: options.libassGlyphLimit || 0,
|
|
131
|
-
hasAlphaBug: JASSUB._hasAlphaBug,
|
|
132
|
-
offscreenRender: typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true),
|
|
133
|
-
useLocalFonts: ('queryLocalFonts' in self) && (options.useLocalFonts ?? true)
|
|
134
|
-
})
|
|
118
|
+
if (this._onDemandRender) {
|
|
119
|
+
this.busy = false
|
|
120
|
+
this._lastDemandTime = null
|
|
121
|
+
}
|
|
135
122
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this._boundUpdateColorSpace = this._updateColorSpace.bind(this)
|
|
140
|
-
if (this._video) this.setVideo(options.video)
|
|
123
|
+
this._worker = new Worker(options.workerUrl || 'jassub-worker.js')
|
|
124
|
+
this._worker.onmessage = e => this._onmessage(e)
|
|
125
|
+
this._worker.onerror = e => this._error(e)
|
|
141
126
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
127
|
+
this._worker.postMessage({
|
|
128
|
+
target: 'init',
|
|
129
|
+
wasmUrl: JASSUB._supportsSIMD && options.modernWasmUrl ? options.modernWasmUrl : options.wasmUrl || 'jassub-worker.wasm',
|
|
130
|
+
legacyWasmUrl: options.legacyWasmUrl || 'jassub-worker.wasm.js',
|
|
131
|
+
asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
|
|
132
|
+
onDemandRender: this._onDemandRender,
|
|
133
|
+
width: this._canvasctrl.width || 0,
|
|
134
|
+
height: this._canvasctrl.height || 0,
|
|
135
|
+
blendMode: options.blendMode || 'js',
|
|
136
|
+
subUrl: options.subUrl,
|
|
137
|
+
subContent: options.subContent || null,
|
|
138
|
+
fonts: options.fonts || [],
|
|
139
|
+
availableFonts: options.availableFonts || { 'liberation sans': './default.woff2' },
|
|
140
|
+
fallbackFont: options.fallbackFont || 'liberation sans',
|
|
141
|
+
debug: this.debug,
|
|
142
|
+
targetFps: options.targetFps || 24,
|
|
143
|
+
dropAllAnimations: options.dropAllAnimations,
|
|
144
|
+
dropAllBlur: options.dropAllBlur,
|
|
145
|
+
libassMemoryLimit: options.libassMemoryLimit || 0,
|
|
146
|
+
libassGlyphLimit: options.libassGlyphLimit || 0,
|
|
147
|
+
useLocalFonts: typeof queryLocalFonts !== 'undefined' && (options.useLocalFonts ?? true)
|
|
148
148
|
})
|
|
149
|
+
if (this._offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_createCanvas () {
|
|
153
|
+
this._canvas = document.createElement('canvas')
|
|
154
|
+
this._canvas.style.display = 'block'
|
|
155
|
+
this._canvas.style.position = 'absolute'
|
|
156
|
+
this._canvas.style.pointerEvents = 'none'
|
|
157
|
+
this._canvasParent.appendChild(this._canvas)
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
// test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page
|
|
152
|
-
static
|
|
161
|
+
static _supportsSIMD = null
|
|
153
162
|
static _hasAlphaBug = null
|
|
154
163
|
|
|
155
164
|
static _test () {
|
|
156
165
|
// check if ran previously
|
|
157
|
-
if (JASSUB.
|
|
166
|
+
if (JASSUB._supportsSIMD !== null) return null
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
JASSUB._supportsSIMD = WebAssembly.validate(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11))
|
|
170
|
+
} catch (e) {
|
|
171
|
+
JASSUB._supportsSIMD = false
|
|
172
|
+
}
|
|
158
173
|
|
|
159
174
|
const canvas1 = document.createElement('canvas')
|
|
160
175
|
const ctx1 = canvas1.getContext('2d', { willReadFrequently: true })
|
|
@@ -176,15 +191,6 @@ export default class JASSUB extends EventTarget {
|
|
|
176
191
|
}
|
|
177
192
|
}
|
|
178
193
|
|
|
179
|
-
try {
|
|
180
|
-
if (typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function') {
|
|
181
|
-
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00))
|
|
182
|
-
if (module instanceof WebAssembly.Module) JASSUB._supportsWebAssembly = (new WebAssembly.Instance(module) instanceof WebAssembly.Instance)
|
|
183
|
-
}
|
|
184
|
-
} catch (e) {
|
|
185
|
-
JASSUB._supportsWebAssembly = false
|
|
186
|
-
}
|
|
187
|
-
|
|
188
194
|
// Test for alpha bug, where e.g. WebKit can render a transparent pixel
|
|
189
195
|
// (with alpha == 0) as non-black which then leads to visual artifacts.
|
|
190
196
|
const canvas2 = document.createElement('canvas')
|
|
@@ -236,7 +242,12 @@ export default class JASSUB extends EventTarget {
|
|
|
236
242
|
|
|
237
243
|
this._canvas.style.top = top + 'px'
|
|
238
244
|
this._canvas.style.left = left + 'px'
|
|
239
|
-
|
|
245
|
+
if (force && this.busy === false) {
|
|
246
|
+
this.busy = true
|
|
247
|
+
} else {
|
|
248
|
+
force = false
|
|
249
|
+
}
|
|
250
|
+
this.sendMessage('canvas', { width, height, force })
|
|
240
251
|
}
|
|
241
252
|
|
|
242
253
|
_getVideoPosition (width = this._video.videoWidth, height = this._video.videoHeight) {
|
|
@@ -295,15 +306,6 @@ export default class JASSUB extends EventTarget {
|
|
|
295
306
|
this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset)
|
|
296
307
|
}
|
|
297
308
|
|
|
298
|
-
_updateColorSpace () {
|
|
299
|
-
this._video.requestVideoFrameCallback(() => {
|
|
300
|
-
// eslint-disable-next-line no-undef
|
|
301
|
-
const frame = new VideoFrame(this._video)
|
|
302
|
-
this._videoColorSpace = webYCbCrMap[frame.colorSpace.matrix]
|
|
303
|
-
frame.close()
|
|
304
|
-
})
|
|
305
|
-
}
|
|
306
|
-
|
|
307
309
|
/**
|
|
308
310
|
* Change the video to use as target for event listeners.
|
|
309
311
|
* @param {HTMLVideoElement} video
|
|
@@ -351,6 +353,8 @@ export default class JASSUB extends EventTarget {
|
|
|
351
353
|
*/
|
|
352
354
|
setTrackByUrl (url) {
|
|
353
355
|
this.sendMessage('setTrackByUrl', { url })
|
|
356
|
+
this._reAttachOffscreen()
|
|
357
|
+
if (this._ctx) this._ctx.filter = 'none'
|
|
354
358
|
}
|
|
355
359
|
|
|
356
360
|
/**
|
|
@@ -359,6 +363,8 @@ export default class JASSUB extends EventTarget {
|
|
|
359
363
|
*/
|
|
360
364
|
setTrack (content) {
|
|
361
365
|
this.sendMessage('setTrack', { content })
|
|
366
|
+
this._reAttachOffscreen()
|
|
367
|
+
if (this._ctx) this._ctx.filter = 'none'
|
|
362
368
|
}
|
|
363
369
|
|
|
364
370
|
/**
|
|
@@ -391,7 +397,7 @@ export default class JASSUB extends EventTarget {
|
|
|
391
397
|
* @param {Number} [rate] Playback rate.
|
|
392
398
|
*/
|
|
393
399
|
setCurrentTime (isPaused, currentTime, rate) {
|
|
394
|
-
this.sendMessage('video', { isPaused, currentTime, rate })
|
|
400
|
+
this.sendMessage('video', { isPaused, currentTime, rate, colorSpace: this._videoColorSpace })
|
|
395
401
|
}
|
|
396
402
|
|
|
397
403
|
/**
|
|
@@ -587,29 +593,69 @@ export default class JASSUB extends EventTarget {
|
|
|
587
593
|
this.sendMessage('demand', { time: mediaTime + this.timeOffset })
|
|
588
594
|
}
|
|
589
595
|
|
|
596
|
+
// if we're using offscreen render, we can't use ctx filters, so we can't use a transfered canvas
|
|
597
|
+
_detachOffscreen () {
|
|
598
|
+
if (!this._offscreenRender || this._ctx) return null
|
|
599
|
+
this._canvas.remove()
|
|
600
|
+
this._createCanvas()
|
|
601
|
+
this._canvasctrl = this._canvas
|
|
602
|
+
this._ctx = this._canvasctrl.getContext('2d')
|
|
603
|
+
this.sendMessage('detachOffscreen')
|
|
604
|
+
// force a render after resize
|
|
605
|
+
this.busy = false
|
|
606
|
+
this.resize(0, 0, 0, 0, true)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// if the video or track changed, we need to re-attach the offscreen canvas
|
|
610
|
+
_reAttachOffscreen () {
|
|
611
|
+
if (!this._offscreenRender || !this._ctx) return null
|
|
612
|
+
this._canvas.remove()
|
|
613
|
+
this._createCanvas()
|
|
614
|
+
this._canvasctrl = this._canvas.transferControlToOffscreen()
|
|
615
|
+
this._ctx = false
|
|
616
|
+
this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
|
|
617
|
+
this.resize(0, 0, 0, 0, true)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
_updateColorSpace () {
|
|
621
|
+
this._video.requestVideoFrameCallback(() => {
|
|
622
|
+
try {
|
|
623
|
+
// eslint-disable-next-line no-undef
|
|
624
|
+
const frame = new VideoFrame(this._video)
|
|
625
|
+
this._videoColorSpace = webYCbCrMap[frame.colorSpace.matrix]
|
|
626
|
+
frame.close()
|
|
627
|
+
this.sendMessage('getColorSpace')
|
|
628
|
+
} catch (e) {
|
|
629
|
+
// sources can be tainted
|
|
630
|
+
console.warn(e)
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
}
|
|
634
|
+
|
|
590
635
|
/**
|
|
591
636
|
* Veryify the color spaces for subtitles and videos, then apply filters to correct the color of subtitles.
|
|
592
|
-
* @param {String} subtitleColorSpace Subtitle color space. One of:
|
|
593
|
-
* @param {String} videoColorSpace Video color space. One of:
|
|
637
|
+
* @param {String} subtitleColorSpace Subtitle color space. One of: BT601 BT709 SMPTE240M FCC
|
|
638
|
+
* @param {String} videoColorSpace Video color space. One of: BT601 BT709
|
|
594
639
|
*/
|
|
595
|
-
|
|
640
|
+
_verifyColorSpace ({ subtitleColorSpace, videoColorSpace = this._videoColorSpace }) {
|
|
596
641
|
if (!subtitleColorSpace || !videoColorSpace) return
|
|
597
642
|
if (subtitleColorSpace === videoColorSpace) return
|
|
598
|
-
this.
|
|
643
|
+
this._detachOffscreen()
|
|
644
|
+
this._ctx.filter = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='${colorMatrixConversionMap[subtitleColorSpace][videoColorSpace]} 0 0 0 0 0 1 0'/></filter></svg>#f")`
|
|
599
645
|
}
|
|
600
646
|
|
|
601
|
-
_render ({ images,
|
|
647
|
+
_render ({ images, asyncRender, times, width, height, colorSpace }) {
|
|
602
648
|
this._unbusy()
|
|
603
|
-
|
|
649
|
+
if (this.debug) times.IPCTime = Date.now() - times.JSRenderTime
|
|
604
650
|
if (this._canvasctrl.width !== width || this._canvasctrl.height !== height) {
|
|
605
651
|
this._canvasctrl.width = width
|
|
606
652
|
this._canvasctrl.height = height
|
|
607
|
-
this.
|
|
653
|
+
this._verifyColorSpace({ subtitleColorSpace: colorSpace })
|
|
608
654
|
}
|
|
609
655
|
this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height)
|
|
610
656
|
for (const image of images) {
|
|
611
657
|
if (image.image) {
|
|
612
|
-
if (
|
|
658
|
+
if (asyncRender) {
|
|
613
659
|
this._ctx.drawImage(image.image, image.x, image.y)
|
|
614
660
|
image.image.close()
|
|
615
661
|
} else {
|
|
@@ -621,10 +667,12 @@ export default class JASSUB extends EventTarget {
|
|
|
621
667
|
}
|
|
622
668
|
}
|
|
623
669
|
if (this.debug) {
|
|
624
|
-
times.
|
|
670
|
+
times.JSRenderTime = Date.now() - times.JSRenderTime - times.IPCTime
|
|
625
671
|
let total = 0
|
|
672
|
+
const count = times.bitmaps || images.length
|
|
673
|
+
delete times.bitmaps
|
|
626
674
|
for (const key in times) total += times[key]
|
|
627
|
-
console.log('Bitmaps: ' +
|
|
675
|
+
console.log('Bitmaps: ' + count + ' Total: ' + (total | 0) + 'ms', times)
|
|
628
676
|
}
|
|
629
677
|
}
|
|
630
678
|
|
|
@@ -724,7 +772,7 @@ export default class JASSUB extends EventTarget {
|
|
|
724
772
|
_removeListeners () {
|
|
725
773
|
if (this._video) {
|
|
726
774
|
if (this._ro) this._ro.unobserve(this._video)
|
|
727
|
-
this._ctx.filter = 'none'
|
|
775
|
+
if (this._ctx) this._ctx.filter = 'none'
|
|
728
776
|
this._video.removeEventListener('timeupdate', this._boundTimeUpdate)
|
|
729
777
|
this._video.removeEventListener('progress', this._boundTimeUpdate)
|
|
730
778
|
this._video.removeEventListener('waiting', this._boundTimeUpdate)
|