jassub 1.5.12 → 1.6.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 +1 -0
- package/dist/jassub-worker-legacy.js +10 -10
- package/dist/jassub-worker.js +11 -11
- package/dist/jassub-worker.wasm +0 -0
- package/dist/jassub.es.js +119 -84
- package/dist/jassub.umd.js +2 -1
- package/package.json +1 -1
- package/src/jassub.js +64 -13
package/dist/jassub.umd.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
(function(h,o){typeof exports=="object"&&typeof module<"u"?module.exports=o():typeof define=="function"&&define.amd?define(o):(h=typeof globalThis<"u"?globalThis:h||self,h.JASSUB=o())})(this,function(){"use strict";var u=Object.defineProperty;var f=(h,o,l)=>o in h?u(h,o,{enumerable:!0,configurable:!0,writable:!0,value:l}):h[o]=l;var v=(h,o,l)=>(f(h,typeof o!="symbol"?o+"":o,l),l);!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(l){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(r,i)=>{const d=this.getVideoPlaybackQuality(),m=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames;if(m>t){const c=this.mozFrameDelay||d.totalFrameDelay-e.totalFrameDelay||0,_=i-r;l(i,{presentationTime:i+c*1e3,expectedDisplayTime:i+_,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+_/1e3,presentedFrames:m,processingDuration:c}),delete this._rvfcpolyfillmap[a]}else this._rvfcpolyfillmap[a]=requestAnimationFrame(c=>s(i,c))},a=Date.now(),n=performance.now();return this._rvfcpolyfillmap[a]=requestAnimationFrame(r=>s(n,r)),a},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(l){cancelAnimationFrame(this._rvfcpolyfillmap[l]),delete this._rvfcpolyfillmap[l]});const o=class extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),o._test();const t=e.blendMode||"js",s=typeof createImageBitmap<"u"&&(e.asyncRender??!0),a=typeof OffscreenCanvas<"u"&&(e.offscreenRender??!0);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._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",{desynchronized:!0,willReadFrequently:!0}),this._canvasctrl=a?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!a&&this._canvasctrl.getContext("2d",{desynchronized:!0}),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(o._supportsWebAssembly?e.workerUrl||"jassub-worker.js":e.legacyWorkerUrl||"jassub-worker-legacy.js"),this._worker.onmessage=n=>this._onmessage(n),this._worker.onerror=n=>this._error(n),this._loaded=new Promise(n=>{this._init=()=>{var r;this._destroyed||(this._worker.postMessage({target:"init",asyncRender:s,onDemandRender:this._onDemandRender,width:this._canvasctrl.width,height:this._canvasctrl.height,preMain:!0,blendMode:t,subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,hasAlphaBug:o._hasAlphaBug,useLocalFonts:"queryLocalFonts"in self&&(e.useLocalFonts??!0)}),a===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null,(r=this._video)==null||r.requestVideoFrameCallback(this._handleRVFC.bind(this))),n())}})}static _test(){if(o._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(d,m,c){const _=t.createImageData(m,c);return d&&_.data.set(d),_}}try{if(typeof WebAssembly=="object"&&typeof WebAssembly.instantiate=="function"){const i=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));i instanceof WebAssembly.Module&&(o._supportsWebAssembly=new WebAssembly.Instance(i)instanceof WebAssembly.Instance)}}catch{o._supportsWebAssembly=!1}const s=document.createElement("canvas"),a=s.getContext("2d",{willReadFrequently:!0});e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const n=a.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(e,0,0);const r=a.getImageData(0,0,1,1).data;o._hasAlphaBug=n[1]!==r[1],o._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,n=(r=>(r=this._video)==null?void 0:r.paused)()){if((!e||!t)&&this._video){const i=this._getVideoPosition();let d=null;if(this._videoWidth){const m=this._video.videoWidth/this._videoWidth,c=this._video.videoHeight/this._videoHeight;d=this._computeCanvasSize((i.width||0)/m,(i.height||0)/c)}else d=this._computeCanvasSize(i.width||0,i.height||0);e=d.width,t=d.height,this._canvasParent&&(s=i.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),a=i.x),this._canvas.style.width=i.width+"px",this._canvas.style.height=i.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=a+"px",this.sendMessage("canvas",{width:e,height:t,force:n&&this.busy===!1})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:a,offsetHeight:n}=this._video,r=a/n;e=a,t=n,r>s?e=Math.floor(n*s):t=Math.floor(a/s);const i=(a-e)/2,d=(n-t)/2;return{width:e,height:t,x:i,y:d}}_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 n=s<1?-1:1;let r=t*a;n*r*s<=n*this.prescaleHeightLimit?r*=s:n*r<n*this.prescaleHeightLimit&&(r=this.prescaleHeightLimit),this.maxRenderHeight>0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}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)),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(a=>a.fullName.toLowerCase()===e);s&&s.blob().then(a=>{a.arrayBuffer().then(n=>{this.addFont(new Uint8Array(n))})})})}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: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})}_render({images:e,async:t,times:s,width:a,height:n}){this._unbusy();const r=Date.now();(this._canvasctrl.width!==a||this._canvasctrl.height!==n)&&(this._canvasctrl.width=a,this._canvasctrl.height=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.drawTime=Date.now()-r;let i=0;for(const d in s)i+=s[d];console.log("Bitmaps: "+e.length+" Total: "+Math.round(i)+"ms",s)}}_fixAlpha(e){if(o._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(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),n=({data:i})=>{i.target===s&&(t(null,i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",r),clearTimeout(a))},r=i=>{t(i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",r),clearTimeout(a)};this._worker.addEventListener("message",n),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){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._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize))}destroy(e){e&&this._error(e),this._video&&this._canvasParent&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}};let h=o;return v(h,"_supportsWebAssembly",null),v(h,"_hasAlphaBug",null),h});
|
|
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",{desynchronized:!0,willReadFrequently:!0}),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d",{desynchronized:!0}),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});
|
package/package.json
CHANGED
package/src/jassub.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import 'rvfc-polyfill'
|
|
2
2
|
|
|
3
|
+
const webYCbCrMap = {
|
|
4
|
+
bt709: 'BT.709',
|
|
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?
|
|
8
|
+
}
|
|
9
|
+
|
|
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")'
|
|
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")'
|
|
16
|
+
},
|
|
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")'
|
|
21
|
+
},
|
|
22
|
+
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")'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
/**
|
|
4
29
|
* New JASSUB instance.
|
|
5
30
|
* @class
|
|
@@ -37,15 +62,13 @@ export default class JASSUB extends EventTarget {
|
|
|
37
62
|
this.destroy('Worker not supported')
|
|
38
63
|
}
|
|
39
64
|
JASSUB._test()
|
|
40
|
-
const blendMode = options.blendMode || 'js'
|
|
41
|
-
const asyncRender = typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true)
|
|
42
|
-
const offscreenRender = typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true)
|
|
43
65
|
this._onDemandRender = 'requestVideoFrameCallback' in HTMLVideoElement.prototype && (options.onDemandRender ?? true)
|
|
44
66
|
|
|
45
67
|
this.timeOffset = options.timeOffset || 0
|
|
46
68
|
this._video = options.video
|
|
47
69
|
this._videoHeight = 0
|
|
48
70
|
this._videoWidth = 0
|
|
71
|
+
this._videoColorSpace = null
|
|
49
72
|
this._canvas = options.canvas
|
|
50
73
|
if (this._video && !this._canvas) {
|
|
51
74
|
this._canvasParent = document.createElement('div')
|
|
@@ -70,8 +93,8 @@ export default class JASSUB extends EventTarget {
|
|
|
70
93
|
this._bufferCanvas = document.createElement('canvas')
|
|
71
94
|
this._bufferCtx = this._bufferCanvas.getContext('2d', { desynchronized: true, willReadFrequently: true })
|
|
72
95
|
|
|
73
|
-
this._canvasctrl =
|
|
74
|
-
this._ctx =
|
|
96
|
+
this._canvasctrl = this._canvas
|
|
97
|
+
this._ctx = this._canvasctrl.getContext('2d', { desynchronized: true })
|
|
75
98
|
|
|
76
99
|
this._lastRenderTime = 0
|
|
77
100
|
this.debug = !!options.debug
|
|
@@ -89,12 +112,12 @@ export default class JASSUB extends EventTarget {
|
|
|
89
112
|
if (this._destroyed) return
|
|
90
113
|
this._worker.postMessage({
|
|
91
114
|
target: 'init',
|
|
92
|
-
asyncRender,
|
|
115
|
+
asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
|
|
93
116
|
onDemandRender: this._onDemandRender,
|
|
94
|
-
width: this._canvasctrl.width,
|
|
95
|
-
height: this._canvasctrl.height,
|
|
117
|
+
width: this._canvasctrl.width || 0,
|
|
118
|
+
height: this._canvasctrl.height || 0,
|
|
96
119
|
preMain: true,
|
|
97
|
-
blendMode,
|
|
120
|
+
blendMode: options.blendMode || 'js',
|
|
98
121
|
subUrl: options.subUrl,
|
|
99
122
|
subContent: options.subContent || null,
|
|
100
123
|
fonts: options.fonts || [],
|
|
@@ -106,19 +129,19 @@ export default class JASSUB extends EventTarget {
|
|
|
106
129
|
libassMemoryLimit: options.libassMemoryLimit || 0,
|
|
107
130
|
libassGlyphLimit: options.libassGlyphLimit || 0,
|
|
108
131
|
hasAlphaBug: JASSUB._hasAlphaBug,
|
|
132
|
+
offscreenRender: typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true),
|
|
109
133
|
useLocalFonts: ('queryLocalFonts' in self) && (options.useLocalFonts ?? true)
|
|
110
134
|
})
|
|
111
|
-
if (offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
|
|
112
135
|
|
|
113
136
|
this._boundResize = this.resize.bind(this)
|
|
114
137
|
this._boundTimeUpdate = this._timeupdate.bind(this)
|
|
115
138
|
this._boundSetRate = this.setRate.bind(this)
|
|
139
|
+
this._boundUpdateColorSpace = this._updateColorSpace.bind(this)
|
|
116
140
|
if (this._video) this.setVideo(options.video)
|
|
117
141
|
|
|
118
142
|
if (this._onDemandRender) {
|
|
119
143
|
this.busy = false
|
|
120
144
|
this._lastDemandTime = null
|
|
121
|
-
this._video?.requestVideoFrameCallback(this._handleRVFC.bind(this))
|
|
122
145
|
}
|
|
123
146
|
resolve()
|
|
124
147
|
}
|
|
@@ -272,6 +295,15 @@ export default class JASSUB extends EventTarget {
|
|
|
272
295
|
this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset)
|
|
273
296
|
}
|
|
274
297
|
|
|
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
|
+
|
|
275
307
|
/**
|
|
276
308
|
* Change the video to use as target for event listeners.
|
|
277
309
|
* @param {HTMLVideoElement} video
|
|
@@ -291,7 +323,12 @@ export default class JASSUB extends EventTarget {
|
|
|
291
323
|
video.addEventListener('seeking', this._boundTimeUpdate, false)
|
|
292
324
|
video.addEventListener('playing', this._boundTimeUpdate, false)
|
|
293
325
|
video.addEventListener('ratechange', this._boundSetRate, false)
|
|
294
|
-
video.addEventListener('resize', this._boundResize)
|
|
326
|
+
video.addEventListener('resize', this._boundResize, false)
|
|
327
|
+
}
|
|
328
|
+
// everything else is unreliable for this, loadedmetadata and loadeddata included.
|
|
329
|
+
if ('VideoFrame' in window) {
|
|
330
|
+
video.addEventListener('loadedmetadata', this._boundUpdateColorSpace, false)
|
|
331
|
+
if (video.readyState > 2) this._updateColorSpace()
|
|
295
332
|
}
|
|
296
333
|
if (video.videoWidth > 0) this.resize()
|
|
297
334
|
// Support Element Resize Observer
|
|
@@ -550,12 +587,24 @@ export default class JASSUB extends EventTarget {
|
|
|
550
587
|
this.sendMessage('demand', { time: mediaTime + this.timeOffset })
|
|
551
588
|
}
|
|
552
589
|
|
|
553
|
-
|
|
590
|
+
/**
|
|
591
|
+
* 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
|
|
594
|
+
*/
|
|
595
|
+
verifyColorSpace (subtitleColorSpace, videoColorSpace = this._videoColorSpace) {
|
|
596
|
+
if (!subtitleColorSpace || !videoColorSpace) return
|
|
597
|
+
if (subtitleColorSpace === videoColorSpace) return
|
|
598
|
+
this._ctx.filter = colorMatrixConversionMap[subtitleColorSpace][videoColorSpace]
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
_render ({ images, async, times, width, height, colorSpace }) {
|
|
554
602
|
this._unbusy()
|
|
555
603
|
const drawStartTime = Date.now()
|
|
556
604
|
if (this._canvasctrl.width !== width || this._canvasctrl.height !== height) {
|
|
557
605
|
this._canvasctrl.width = width
|
|
558
606
|
this._canvasctrl.height = height
|
|
607
|
+
this.verifyColorSpace(colorSpace)
|
|
559
608
|
}
|
|
560
609
|
this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height)
|
|
561
610
|
for (const image of images) {
|
|
@@ -675,6 +724,7 @@ export default class JASSUB extends EventTarget {
|
|
|
675
724
|
_removeListeners () {
|
|
676
725
|
if (this._video) {
|
|
677
726
|
if (this._ro) this._ro.unobserve(this._video)
|
|
727
|
+
this._ctx.filter = 'none'
|
|
678
728
|
this._video.removeEventListener('timeupdate', this._boundTimeUpdate)
|
|
679
729
|
this._video.removeEventListener('progress', this._boundTimeUpdate)
|
|
680
730
|
this._video.removeEventListener('waiting', this._boundTimeUpdate)
|
|
@@ -682,6 +732,7 @@ export default class JASSUB extends EventTarget {
|
|
|
682
732
|
this._video.removeEventListener('playing', this._boundTimeUpdate)
|
|
683
733
|
this._video.removeEventListener('ratechange', this._boundSetRate)
|
|
684
734
|
this._video.removeEventListener('resize', this._boundResize)
|
|
735
|
+
this._video.removeEventListener('loadedmetadata', this._boundUpdateColorSpace)
|
|
685
736
|
}
|
|
686
737
|
}
|
|
687
738
|
|