jassub 1.6.1 → 1.6.4

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.
@@ -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(m){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(n,a)=>{const o=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(h>t){const l=this.mozFrameDelay||o.totalFrameDelay-e.totalFrameDelay||0,f=a-n;m(a,{presentationTime:a+l*1e3,expectedDisplayTime:a+f,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+f/1e3,presentedFrames:h,processingDuration:l}),delete this._rvfcpolyfillmap[i]}else this._rvfcpolyfillmap[i]=requestAnimationFrame(l=>s(a,l))},i=Date.now(),r=performance.now();return this._rvfcpolyfillmap[i]=requestAnimationFrame(n=>s(r,n)),i},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(m){cancelAnimationFrame(this._rvfcpolyfillmap[m]),delete this._rvfcpolyfillmap[m]});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 d extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),d._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._worker=new Worker(e.workerUrl||"jassub-worker.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,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,useLocalFonts:"queryLocalFonts"in self&&(e.useLocalFonts??!0)}),this._offscreenRender===!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._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),t())}})}_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 _supportsWebAssembly=null;static _hasAlphaBug=null;static _test(){if(d._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(o,h,l){const f=t.createImageData(h,l);return o&&f.data.set(o),f}}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 r=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;d._hasAlphaBug=r[1]!==n[1],d._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,r=this._video?.paused){if((!e||!t)&&this._video){const n=this._getVideoPosition();let a=null;if(this._videoWidth){const o=this._video.videoWidth/this._videoWidth,h=this._video.videoHeight/this._videoHeight;a=this._computeCanvasSize((n.width||0)/o,(n.height||0)/h)}else a=this._computeCanvasSize(n.width||0,n.height||0);e=a.width,t=a.height,this._canvasParent&&(s=n.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=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=i+"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:i,offsetHeight:r}=this._video,n=i/r;e=i,t=r,n>s?e=Math.floor(r*s):t=Math.floor(i/s);const a=(i-e)/2,o=(r-t)/2;return{width:e,height:t,x:a,y:o}}_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 r=s<1?-1:1;let n=t*i;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(i=>i.fullName.toLowerCase()===e);s&&s.blob().then(i=>{i.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: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})}_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:i,height:r,colorSpace:n}){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==r)&&(this._canvasctrl.width=i,this._canvasctrl.height=r,this._verifyColorSpace({subtitleColorSpace: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.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let a=0;const o=s.bitmaps||e.length;delete s.bitmaps;for(const h in s)a+=s[h];console.log("Bitmaps: "+o+" Total: "+(a|0)+"ms",s)}}_fixAlpha(e){if(d._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),r=({data:a})=>{a.target===s&&(t(null,a),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(i))},n=a=>{t(a),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(i)};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 d});
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "jassub",
3
- "version": "1.6.1",
3
+ "version": "1.6.4",
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
- "bundle": "npm run build-lib && npm run build-worker",
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: 'BT.709',
4
+ bt709: 'BT709',
5
5
  // these might not be exactly correct? oops?
6
- bt470bg: 'BT.601', // alias BT.601 PAL... whats the difference?
7
- smpte170m: 'BT.601'// alias BT.601 NTSC... whats the difference?
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
- 'BT.601': {
12
- '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")'
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
- 'BT.709': {
15
- '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")'
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
- '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
19
- 1.0378 0 0 0 0 0 1 0'/></filter></svg>#f")`,
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
- '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")',
24
- '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")'
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
 
@@ -64,6 +63,9 @@ export default class JASSUB extends EventTarget {
64
63
  JASSUB._test()
65
64
  this._onDemandRender = 'requestVideoFrameCallback' in HTMLVideoElement.prototype && (options.onDemandRender ?? true)
66
65
 
66
+ // don't support offscreen rendering on custom canvases, as we can't replace it if colorSpace doesn't match
67
+ this._offscreenRender = 'transferControlToOffscreen' in HTMLCanvasElement.prototype && !options.canvas && (options.offscreenRender ?? true)
68
+
67
69
  this.timeOffset = options.timeOffset || 0
68
70
  this._video = options.video
69
71
  this._videoHeight = 0
@@ -75,11 +77,7 @@ export default class JASSUB extends EventTarget {
75
77
  this._canvasParent.className = 'JASSUB'
76
78
  this._canvasParent.style.position = 'relative'
77
79
 
78
- this._canvas = document.createElement('canvas')
79
- this._canvas.style.display = 'block'
80
- this._canvas.style.position = 'absolute'
81
- this._canvas.style.pointerEvents = 'none'
82
- this._canvasParent.appendChild(this._canvas)
80
+ this._createCanvas()
83
81
 
84
82
  if (this._video.nextSibling) {
85
83
  this._video.parentNode.insertBefore(this._canvasParent, this._video.nextSibling)
@@ -93,8 +91,8 @@ export default class JASSUB extends EventTarget {
93
91
  this._bufferCanvas = document.createElement('canvas')
94
92
  this._bufferCtx = this._bufferCanvas.getContext('2d')
95
93
 
96
- this._canvasctrl = this._canvas
97
- this._ctx = this._canvasctrl.getContext('2d')
94
+ this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas
95
+ this._ctx = !this._offscreenRender && this._canvasctrl.getContext('2d')
98
96
 
99
97
  this._lastRenderTime = 0
100
98
  this.debug = !!options.debug
@@ -103,7 +101,7 @@ export default class JASSUB extends EventTarget {
103
101
  this.prescaleHeightLimit = options.prescaleHeightLimit || 1080
104
102
  this.maxRenderHeight = options.maxRenderHeight || 0 // 0 - no limit.
105
103
 
106
- this._worker = new Worker(JASSUB._supportsWebAssembly ? options.workerUrl || 'jassub-worker.js' : options.legacyWorkerUrl || 'jassub-worker-legacy.js')
104
+ this._worker = new Worker(options.workerUrl || 'jassub-worker.js')
107
105
  this._worker.onmessage = e => this._onmessage(e)
108
106
  this._worker.onerror = e => this._error(e)
109
107
 
@@ -116,7 +114,6 @@ export default class JASSUB extends EventTarget {
116
114
  onDemandRender: this._onDemandRender,
117
115
  width: this._canvasctrl.width || 0,
118
116
  height: this._canvasctrl.height || 0,
119
- preMain: true,
120
117
  blendMode: options.blendMode || 'js',
121
118
  subUrl: options.subUrl,
122
119
  subContent: options.subContent || null,
@@ -126,12 +123,12 @@ export default class JASSUB extends EventTarget {
126
123
  debug: this.debug,
127
124
  targetFps: options.targetFps || 24,
128
125
  dropAllAnimations: options.dropAllAnimations,
126
+ dropAllBlur: options.dropAllBlur,
129
127
  libassMemoryLimit: options.libassMemoryLimit || 0,
130
128
  libassGlyphLimit: options.libassGlyphLimit || 0,
131
- hasAlphaBug: JASSUB._hasAlphaBug,
132
- offscreenRender: typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true),
133
129
  useLocalFonts: ('queryLocalFonts' in self) && (options.useLocalFonts ?? true)
134
130
  })
131
+ if (this._offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
135
132
 
136
133
  this._boundResize = this.resize.bind(this)
137
134
  this._boundTimeUpdate = this._timeupdate.bind(this)
@@ -148,6 +145,14 @@ export default class JASSUB extends EventTarget {
148
145
  })
149
146
  }
150
147
 
148
+ _createCanvas () {
149
+ this._canvas = document.createElement('canvas')
150
+ this._canvas.style.display = 'block'
151
+ this._canvas.style.position = 'absolute'
152
+ this._canvas.style.pointerEvents = 'none'
153
+ this._canvasParent.appendChild(this._canvas)
154
+ }
155
+
151
156
  // test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page
152
157
  static _supportsWebAssembly = null
153
158
  static _hasAlphaBug = null
@@ -176,15 +181,6 @@ export default class JASSUB extends EventTarget {
176
181
  }
177
182
  }
178
183
 
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
184
  // Test for alpha bug, where e.g. WebKit can render a transparent pixel
189
185
  // (with alpha == 0) as non-black which then leads to visual artifacts.
190
186
  const canvas2 = document.createElement('canvas')
@@ -236,7 +232,12 @@ export default class JASSUB extends EventTarget {
236
232
 
237
233
  this._canvas.style.top = top + 'px'
238
234
  this._canvas.style.left = left + 'px'
239
- this.sendMessage('canvas', { width, height, force: force && this.busy === false })
235
+ if (force && this.busy === false) {
236
+ this.busy = true
237
+ } else {
238
+ force = false
239
+ }
240
+ this.sendMessage('canvas', { width, height, force })
240
241
  }
241
242
 
242
243
  _getVideoPosition (width = this._video.videoWidth, height = this._video.videoHeight) {
@@ -295,15 +296,6 @@ export default class JASSUB extends EventTarget {
295
296
  this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset)
296
297
  }
297
298
 
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
299
  /**
308
300
  * Change the video to use as target for event listeners.
309
301
  * @param {HTMLVideoElement} video
@@ -351,6 +343,8 @@ export default class JASSUB extends EventTarget {
351
343
  */
352
344
  setTrackByUrl (url) {
353
345
  this.sendMessage('setTrackByUrl', { url })
346
+ this._reAttachOffscreen()
347
+ if (this._ctx) this._ctx.filter = 'none'
354
348
  }
355
349
 
356
350
  /**
@@ -359,6 +353,8 @@ export default class JASSUB extends EventTarget {
359
353
  */
360
354
  setTrack (content) {
361
355
  this.sendMessage('setTrack', { content })
356
+ this._reAttachOffscreen()
357
+ if (this._ctx) this._ctx.filter = 'none'
362
358
  }
363
359
 
364
360
  /**
@@ -391,7 +387,7 @@ export default class JASSUB extends EventTarget {
391
387
  * @param {Number} [rate] Playback rate.
392
388
  */
393
389
  setCurrentTime (isPaused, currentTime, rate) {
394
- this.sendMessage('video', { isPaused, currentTime, rate })
390
+ this.sendMessage('video', { isPaused, currentTime, rate, colorSpace: this._videoColorSpace })
395
391
  }
396
392
 
397
393
  /**
@@ -587,29 +583,69 @@ export default class JASSUB extends EventTarget {
587
583
  this.sendMessage('demand', { time: mediaTime + this.timeOffset })
588
584
  }
589
585
 
586
+ // if we're using offscreen render, we can't use ctx filters, so we can't use a transfered canvas
587
+ _detachOffscreen () {
588
+ if (!this._offscreenRender || this._ctx) return null
589
+ this._canvas.remove()
590
+ this._createCanvas()
591
+ this._canvasctrl = this._canvas
592
+ this._ctx = this._canvasctrl.getContext('2d')
593
+ this.sendMessage('detachOffscreen')
594
+ // force a render after resize
595
+ this.busy = false
596
+ this.resize(0, 0, 0, 0, true)
597
+ }
598
+
599
+ // if the video or track changed, we need to re-attach the offscreen canvas
600
+ _reAttachOffscreen () {
601
+ if (!this._offscreenRender || !this._ctx) return null
602
+ this._canvas.remove()
603
+ this._createCanvas()
604
+ this._canvasctrl = this._canvas.transferControlToOffscreen()
605
+ this._ctx = false
606
+ this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
607
+ this.resize(0, 0, 0, 0, true)
608
+ }
609
+
610
+ _updateColorSpace () {
611
+ this._video.requestVideoFrameCallback(() => {
612
+ try {
613
+ // eslint-disable-next-line no-undef
614
+ const frame = new VideoFrame(this._video)
615
+ this._videoColorSpace = webYCbCrMap[frame.colorSpace.matrix]
616
+ frame.close()
617
+ this.sendMessage('getColorSpace')
618
+ } catch (e) {
619
+ // sources can be tainted
620
+ console.warn(e)
621
+ }
622
+ })
623
+ }
624
+
590
625
  /**
591
626
  * 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: BT.601 BT.709 SMPTE240M FCC
593
- * @param {String} videoColorSpace Video color space. One of: BT.601 BT.709
627
+ * @param {String} subtitleColorSpace Subtitle color space. One of: BT601 BT709 SMPTE240M FCC
628
+ * @param {String} videoColorSpace Video color space. One of: BT601 BT709
594
629
  */
595
- verifyColorSpace (subtitleColorSpace, videoColorSpace = this._videoColorSpace) {
630
+ _verifyColorSpace ({ subtitleColorSpace, videoColorSpace = this._videoColorSpace }) {
596
631
  if (!subtitleColorSpace || !videoColorSpace) return
597
632
  if (subtitleColorSpace === videoColorSpace) return
598
- this._ctx.filter = colorMatrixConversionMap[subtitleColorSpace][videoColorSpace]
633
+ this._detachOffscreen()
634
+ 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
635
  }
600
636
 
601
- _render ({ images, async, times, width, height, colorSpace }) {
637
+ _render ({ images, asyncRender, times, width, height, colorSpace }) {
602
638
  this._unbusy()
603
- const drawStartTime = Date.now()
639
+ if (this.debug) times.IPCTime = Date.now() - times.JSRenderTime
604
640
  if (this._canvasctrl.width !== width || this._canvasctrl.height !== height) {
605
641
  this._canvasctrl.width = width
606
642
  this._canvasctrl.height = height
607
- this.verifyColorSpace(colorSpace)
643
+ this._verifyColorSpace({ subtitleColorSpace: colorSpace })
608
644
  }
609
645
  this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height)
610
646
  for (const image of images) {
611
647
  if (image.image) {
612
- if (async) {
648
+ if (asyncRender) {
613
649
  this._ctx.drawImage(image.image, image.x, image.y)
614
650
  image.image.close()
615
651
  } else {
@@ -621,10 +657,12 @@ export default class JASSUB extends EventTarget {
621
657
  }
622
658
  }
623
659
  if (this.debug) {
624
- times.drawTime = Date.now() - drawStartTime
660
+ times.JSRenderTime = Date.now() - times.JSRenderTime - times.IPCTime
625
661
  let total = 0
662
+ const count = times.bitmaps || images.length
663
+ delete times.bitmaps
626
664
  for (const key in times) total += times[key]
627
- console.log('Bitmaps: ' + images.length + ' Total: ' + Math.round(total) + 'ms', times)
665
+ console.log('Bitmaps: ' + count + ' Total: ' + (total | 0) + 'ms', times)
628
666
  }
629
667
  }
630
668
 
@@ -724,7 +762,7 @@ export default class JASSUB extends EventTarget {
724
762
  _removeListeners () {
725
763
  if (this._video) {
726
764
  if (this._ro) this._ro.unobserve(this._video)
727
- this._ctx.filter = 'none'
765
+ if (this._ctx) this._ctx.filter = 'none'
728
766
  this._video.removeEventListener('timeupdate', this._boundTimeUpdate)
729
767
  this._video.removeEventListener('progress', this._boundTimeUpdate)
730
768
  this._video.removeEventListener('waiting', this._boundTimeUpdate)