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.
- package/dist/jassub-worker.js +13 -16
- package/dist/jassub-worker.wasm +0 -0
- package/dist/jassub-worker.wasm.js +1 -0
- package/dist/jassub.es.js +479 -475
- package/dist/jassub.umd.js +1 -2
- package/package.json +5 -5
- package/src/jassub.js +92 -54
- 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(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.
|
|
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
|
-
"
|
|
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
|
|
|
@@ -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.
|
|
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(
|
|
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
|
-
|
|
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:
|
|
593
|
-
* @param {String} videoColorSpace Video color space. One of:
|
|
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
|
-
|
|
630
|
+
_verifyColorSpace ({ subtitleColorSpace, videoColorSpace = this._videoColorSpace }) {
|
|
596
631
|
if (!subtitleColorSpace || !videoColorSpace) return
|
|
597
632
|
if (subtitleColorSpace === videoColorSpace) return
|
|
598
|
-
this.
|
|
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,
|
|
637
|
+
_render ({ images, asyncRender, times, width, height, colorSpace }) {
|
|
602
638
|
this._unbusy()
|
|
603
|
-
|
|
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.
|
|
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 (
|
|
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.
|
|
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: ' +
|
|
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)
|