From 7b0e1d156b8e21e422200ee345afa63950f9d5a2 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 15:28:52 +0800 Subject: [PATCH] fix(jassub): [PKG-JASSUB-10] release bitmap batches on rendering errors --- docs/compiled/artplayer-plugin-jassub.js | 2 +- .../artplayer-plugin-jassub.legacy.js | 2 +- docs/compiled/artplayer-plugin-jassub.mjs | 10 +- .../artplayer-plugin-jassub/ARCHITECTURE.md | 10 + .../dist/artplayer-plugin-jassub.js | 2 +- .../dist/artplayer-plugin-jassub.legacy.js | 2 +- .../dist/artplayer-plugin-jassub.mjs | 10 +- .../artplayer-plugin-jassub/src/jassub.es.js | 10 +- refactor/baselines/jassub-render-patch.json | 11 + .../baselines/jassub-render-validation.json | 5441 +++++++++++++++++ refactor/baselines/jassub-render.patch | 21 + ...26-09-14-PKG-JASSUB-10-render-resources.md | 47 + refactor/plan.md | 10 +- refactor/progress.md | 15 + refactor/risk-table.md | 1 + refactor/risks.json | 27 +- refactor/scripts/jassub-vendor-patch.test.mjs | 2 +- refactor/tasks.json | 23 +- refactor/third-party.json | 5 +- test/browser/README.md | 7 + test/browser/jassub-render-failure.spec.js | 108 + test/jassub-offscreen.test.js | 51 + 22 files changed, 5795 insertions(+), 22 deletions(-) create mode 100644 refactor/baselines/jassub-render-patch.json create mode 100644 refactor/baselines/jassub-render-validation.json create mode 100644 refactor/baselines/jassub-render.patch create mode 100644 refactor/changes/2026-09-14-PKG-JASSUB-10-render-resources.md create mode 100644 test/browser/jassub-render-failure.spec.js diff --git a/docs/compiled/artplayer-plugin-jassub.js b/docs/compiled/artplayer-plugin-jassub.js index 01c1230b3..1c4ba59ef 100644 --- a/docs/compiled/artplayer-plugin-jassub.js +++ b/docs/compiled/artplayer-plugin-jassub.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=(t,s,i)=>((t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i)(t,"symbol"!==typeof s?s+"":s,i);typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const d=this.getVideoPlaybackQuality(),l=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames,c=l>r,_=0===r&&0===l&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===d.totalVideoFrames&&0===d.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||d.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:l,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const s={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},i={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"}},a=class e extends EventTarget{constructor(t){let s;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),s=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(t.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(t.offscreenRender??!0),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),s.then(()=>{this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:t.wasmUrl??"jassub-worker.wasm",legacyWasmUrl:t.legacyWasmUrl??"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(t.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(t.useLocalFonts??!0),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(i){s?.catch(()=>{});try{this.destroy()}catch{}throw i}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch{e._supportsSIMD=!1}}static async _testImageBugs(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)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(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(await createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()}static async _test(){e._testSIMD(),await e._testImageBugs()}resize(e=0,t=0,s=0,i=0,a=this._video?.paused){if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||this._video?.videoWidth||e,videoHeight:this._videoHeight||this._video?.videoHeight||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t?.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let i;this._colorCallback=null;try{i=new VideoFrame(e),this._videoColorSpace=s[i.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(a){console.warn(a)}finally{i?.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){if(this._ctx){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?(this._ctx.drawImage(s.image,s.x,s.y),s.image.close()):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)));if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const n of e)n.image?.close?.()}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}async sendMessage(e,t={},s){this._destroyed||(await this._loaded,this._destroyed||(s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})))}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(d)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),d=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(d),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(l){if(a)throw l;n(l)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){if(this._destroyed)for(const t of e.images||[])t.image?.close?.();else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._videoGeneration=(this._videoGeneration||0)+1;for(const e of[this._frameCallback,this._colorCallback])null!=e&&this._video?.cancelVideoFrameCallback?.(e);this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let t,s=!1;const i=e=>{try{e()}catch(i){s||(t=i,s=!0)}};e&&i(()=>{e=this._error(e)}),i(()=>this._removeListeners()),i(()=>this._ro?.disconnect()),i(()=>this._canvasParent?.remove()),this._init?.();const a=[];for(const r of[...this._requests||[]])i(()=>{const e=r();e&&a.push(e)});for(const r of a)i(r);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,i(()=>this._worker.terminate())),s)throw t;return e}};t(a,"_supportsSIMD",null),t(a,"_hasAlphaBug",null),t(a,"_hasBitmapBug",null);let r=a;return function(e){return t=>function(e,t){const s=new r({video:e.video,...t});let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch{}try{a()}catch{}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=(t,s,i)=>((t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i)(t,"symbol"!==typeof s?s+"":s,i);typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const d=this.getVideoPlaybackQuality(),l=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames,c=l>r,_=0===r&&0===l&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===d.totalVideoFrames&&0===d.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||d.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:l,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const s={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},i={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"}},a=class e extends EventTarget{constructor(t){let s;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),s=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(t.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(t.offscreenRender??!0),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),s.then(()=>{this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:t.wasmUrl??"jassub-worker.wasm",legacyWasmUrl:t.legacyWasmUrl??"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(t.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(t.useLocalFonts??!0),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(i){s?.catch(()=>{});try{this.destroy()}catch{}throw i}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch{e._supportsSIMD=!1}}static async _testImageBugs(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)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(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(await createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()}static async _test(){e._testSIMD(),await e._testImageBugs()}resize(e=0,t=0,s=0,i=0,a=this._video?.paused){if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||this._video?.videoWidth||e,videoHeight:this._videoHeight||this._video?.videoHeight||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t?.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let i;this._colorCallback=null;try{i=new VideoFrame(e),this._videoColorSpace=s[i.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(a){console.warn(a)}finally{i?.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){if(this._ctx){try{this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?this._ctx.drawImage(s.image,s.x,s.y):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)))}finally{if(t)for(const t of e)t.image?.close()}if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const n of e)n.image?.close?.()}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}async sendMessage(e,t={},s){this._destroyed||(await this._loaded,this._destroyed||(s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})))}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(d)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),d=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(d),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(l){if(a)throw l;n(l)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){if(this._destroyed)for(const t of e.images||[])t.image?.close?.();else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._videoGeneration=(this._videoGeneration||0)+1;for(const e of[this._frameCallback,this._colorCallback])null!=e&&this._video?.cancelVideoFrameCallback?.(e);this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let t,s=!1;const i=e=>{try{e()}catch(i){s||(t=i,s=!0)}};e&&i(()=>{e=this._error(e)}),i(()=>this._removeListeners()),i(()=>this._ro?.disconnect()),i(()=>this._canvasParent?.remove()),this._init?.();const a=[];for(const r of[...this._requests||[]])i(()=>{const e=r();e&&a.push(e)});for(const r of a)i(r);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,i(()=>this._worker.terminate())),s)throw t;return e}};t(a,"_supportsSIMD",null),t(a,"_hasAlphaBug",null),t(a,"_hasBitmapBug",null);let r=a;return function(e){return t=>function(e,t){const s=new r({video:e.video,...t});let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch{}try{a()}catch{}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); diff --git a/docs/compiled/artplayer-plugin-jassub.legacy.js b/docs/compiled/artplayer-plugin-jassub.legacy.js index 29cadc597..884304c77 100644 --- a/docs/compiled/artplayer-plugin-jassub.legacy.js +++ b/docs/compiled/artplayer-plugin-jassub.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,s=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i,r=(e,r)=>{for(var n in r||(r={}))s.call(r,n)&&a(e,n,r[n]);if(t)for(var n of t(r))i.call(r,n)&&a(e,n,r[n]);return e},n=(e,t,s)=>a(e,"symbol"!==typeof t?t+"":t,s),o=(e,t,s)=>new Promise((i,a)=>{var r=e=>{try{o(s.next(e))}catch(t){a(t)}},n=e=>{try{o(s.throw(e))}catch(t){a(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(r,n);o((s=s.apply(e,t)).next())});typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const d=this.getVideoPlaybackQuality(),l=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames,c=l>r,_=0===r&&0===l&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===d.totalVideoFrames&&0===d.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||d.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:l,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const h={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},d={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"}},l=class e extends EventTarget{constructor(t){var s,i;let a;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),a=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(null==(s=t.onDemandRender)||s),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(null==(i=t.offscreenRender)||i),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),a.then(()=>{var s,i,a,r;this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:null!=(s=t.wasmUrl)?s:"jassub-worker.wasm",legacyWasmUrl:null!=(i=t.legacyWasmUrl)?i:"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(null==(a=t.asyncRender)||a),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(null==(r=t.useLocalFonts)||r),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(r){null==a||a.catch(()=>{});try{this.destroy()}catch(n){}throw r}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch(t){e._supportsSIMD=!1}}static _testImageBugs(){return o(this,null,function*(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch(o){console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(yield createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()})}static _test(){return o(this,null,function*(){e._testSIMD(),yield e._testImageBugs()})}resize(e=0,t=0,s=0,i=0,a=(e=>null==(e=this._video)?void 0:e.paused)()){var r,n;if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||(null==(r=this._video)?void 0:r.videoWidth)||e,videoHeight:this._videoHeight||(null==(n=this._video)?void 0:n.videoHeight)||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=null==t?void 0:t.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t;try{(null==(t=null==navigator?void 0:navigator.permissions)?void 0:t.query)?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(s){console.warn("Local fonts API:",s)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let s;this._colorCallback=null;try{s=new VideoFrame(e),this._videoColorSpace=h[s.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(i){console.warn(i)}finally{null==s||s.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){var n,o;if(this._ctx){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?(this._ctx.drawImage(s.image,s.x,s.y),s.image.close()):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)));if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const h of e)null==(o=null==(n=h.image)?void 0:n.close)||o.call(n)}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}sendMessage(e){return o(this,arguments,function*(e,t={},s){this._destroyed||(yield this._loaded,this._destroyed||(s?this._worker.postMessage(r({target:e,transferable:s},t),[...s]):this._worker.postMessage(r({target:e},t))))})}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(d)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),d=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(d),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(l){if(a)throw l;n(l)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){var t,s;if(this._destroyed)for(const i of e.images||[])null==(s=null==(t=i.image)?void 0:t.close)||s.call(t);else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){var e,t;this._videoGeneration=(this._videoGeneration||0)+1;for(const s of[this._frameCallback,this._colorCallback])null!=s&&(null==(t=null==(e=this._video)?void 0:e.cancelVideoFrameCallback)||t.call(e,s));this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){var t;if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let s,i=!1;const a=e=>{try{e()}catch(t){i||(s=t,i=!0)}};e&&a(()=>{e=this._error(e)}),a(()=>this._removeListeners()),a(()=>{var e;return null==(e=this._ro)?void 0:e.disconnect()}),a(()=>{var e;return null==(e=this._canvasParent)?void 0:e.remove()}),null==(t=this._init)||t.call(this);const r=[];for(const n of[...this._requests||[]])a(()=>{const e=n();e&&r.push(e)});for(const n of r)a(n);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,a(()=>this._worker.terminate())),i)throw s;return e}};n(l,"_supportsSIMD",null),n(l,"_hasAlphaBug",null),n(l,"_hasBitmapBug",null);let c=l;return function(e){return t=>function(e,t){const s=new c(r({video:e.video},t));let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch(o){}try{a()}catch(o){}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,s=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i,r=(e,r)=>{for(var n in r||(r={}))s.call(r,n)&&a(e,n,r[n]);if(t)for(var n of t(r))i.call(r,n)&&a(e,n,r[n]);return e},n=(e,t,s)=>a(e,"symbol"!==typeof t?t+"":t,s),o=(e,t,s)=>new Promise((i,a)=>{var r=e=>{try{o(s.next(e))}catch(t){a(t)}},n=e=>{try{o(s.throw(e))}catch(t){a(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(r,n);o((s=s.apply(e,t)).next())});typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const l=this.getVideoPlaybackQuality(),d=this.mozPresentedFrames||this.mozPaintedFrames||l.totalVideoFrames-l.droppedVideoFrames,c=d>r,_=0===r&&0===d&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===l.totalVideoFrames&&0===l.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||l.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:d,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const h={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},l={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"}},d=class e extends EventTarget{constructor(t){var s,i;let a;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),a=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(null==(s=t.onDemandRender)||s),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(null==(i=t.offscreenRender)||i),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),a.then(()=>{var s,i,a,r;this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:null!=(s=t.wasmUrl)?s:"jassub-worker.wasm",legacyWasmUrl:null!=(i=t.legacyWasmUrl)?i:"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(null==(a=t.asyncRender)||a),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(null==(r=t.useLocalFonts)||r),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(r){null==a||a.catch(()=>{});try{this.destroy()}catch(n){}throw r}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch(t){e._supportsSIMD=!1}}static _testImageBugs(){return o(this,null,function*(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch(o){console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(yield createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()})}static _test(){return o(this,null,function*(){e._testSIMD(),yield e._testImageBugs()})}resize(e=0,t=0,s=0,i=0,a=(e=>null==(e=this._video)?void 0:e.paused)()){var r,n;if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||(null==(r=this._video)?void 0:r.videoWidth)||e,videoHeight:this._videoHeight||(null==(n=this._video)?void 0:n.videoHeight)||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=null==t?void 0:t.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t;try{(null==(t=null==navigator?void 0:navigator.permissions)?void 0:t.query)?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(s){console.warn("Local fonts API:",s)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let s;this._colorCallback=null;try{s=new VideoFrame(e),this._videoColorSpace=h[s.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(i){console.warn(i)}finally{null==s||s.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){var n,o,h;if(this._ctx){try{this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?this._ctx.drawImage(s.image,s.x,s.y):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)))}finally{if(t)for(const t of e)null==(h=t.image)||h.close()}if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const l of e)null==(o=null==(n=l.image)?void 0:n.close)||o.call(n)}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}sendMessage(e){return o(this,arguments,function*(e,t={},s){this._destroyed||(yield this._loaded,this._destroyed||(s?this._worker.postMessage(r({target:e,transferable:s},t),[...s]):this._worker.postMessage(r({target:e},t))))})}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(l)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),l=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(l),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(d){if(a)throw d;n(d)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){var t,s;if(this._destroyed)for(const i of e.images||[])null==(s=null==(t=i.image)?void 0:t.close)||s.call(t);else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){var e,t;this._videoGeneration=(this._videoGeneration||0)+1;for(const s of[this._frameCallback,this._colorCallback])null!=s&&(null==(t=null==(e=this._video)?void 0:e.cancelVideoFrameCallback)||t.call(e,s));this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){var t;if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let s,i=!1;const a=e=>{try{e()}catch(t){i||(s=t,i=!0)}};e&&a(()=>{e=this._error(e)}),a(()=>this._removeListeners()),a(()=>{var e;return null==(e=this._ro)?void 0:e.disconnect()}),a(()=>{var e;return null==(e=this._canvasParent)?void 0:e.remove()}),null==(t=this._init)||t.call(this);const r=[];for(const n of[...this._requests||[]])a(()=>{const e=n();e&&r.push(e)});for(const n of r)a(n);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,a(()=>this._worker.terminate())),i)throw s;return e}};n(d,"_supportsSIMD",null),n(d,"_hasAlphaBug",null),n(d,"_hasBitmapBug",null);let c=d;return function(e){return t=>function(e,t){const s=new c(r({video:e.video},t));let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch(o){}try{a()}catch(o){}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); diff --git a/docs/compiled/artplayer-plugin-jassub.mjs b/docs/compiled/artplayer-plugin-jassub.mjs index cc770e7eb..5cb773479 100644 --- a/docs/compiled/artplayer-plugin-jassub.mjs +++ b/docs/compiled/artplayer-plugin-jassub.mjs @@ -564,9 +564,13 @@ const _o = class _o extends EventTarget { for (const image of e) image.image?.close?.(); return; } - this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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))); + try { + this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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) : (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))); + } finally { + if (t) for (const i of e) i.image?.close(); + } if (this.debug) { s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime; let i = 0; diff --git a/packages/artplayer-plugin-jassub/ARCHITECTURE.md b/packages/artplayer-plugin-jassub/ARCHITECTURE.md index 915e22e1c..a35c986f6 100644 --- a/packages/artplayer-plugin-jassub/ARCHITECTURE.md +++ b/packages/artplayer-plugin-jassub/ARCHITECTURE.md @@ -42,6 +42,16 @@ protocol or change the historical matching of simultaneous same-target responses `src/jassub.es.js` owns rendering, media listeners, canvas state, capability checks and worker messages. Keep this third-party file separate from the adapter's TypeScript migration. +PKG-JASSUB-10 adds `refactor/baselines/jassub-render-patch.json` to the patch chain. +An asynchronous main-thread render owns the entire received ImageBitmap batch. +A finally block closes every bitmap even when resizing, clearing or drawing fails; +the original native rendering exception still escapes. Normal drawing releases the +batch before debug reporting. Synchronous ImageData buffers are not ImageBitmaps +and are not closed. This does not alter Worker messages, async/offscreen defaults, +or resolve the separate Firefox offscreen stall. `jassub-offscreen.test.js` covers +failure positions and the synchronous path; `jassub-render-failure.spec.js` uses +an actual Worker/WASM bitmap, native copies and an intentionally closed bitmap +to verify native draw failure, batch release and a subsequent visible subtitle. `worker/` contains worker/WASM files; the actual 1.0.0/1.1.0 npm packages do not ship those files. The local demo explicitly hosts its resources under `docs/assets/jassub/` and selects fonts in `docs/assets/example/jassub.js`. Do not move or rename those URLs as an internal cleanup. diff --git a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js index 01c1230b3..1c4ba59ef 100644 --- a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js +++ b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=(t,s,i)=>((t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i)(t,"symbol"!==typeof s?s+"":s,i);typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const d=this.getVideoPlaybackQuality(),l=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames,c=l>r,_=0===r&&0===l&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===d.totalVideoFrames&&0===d.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||d.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:l,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const s={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},i={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"}},a=class e extends EventTarget{constructor(t){let s;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),s=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(t.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(t.offscreenRender??!0),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),s.then(()=>{this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:t.wasmUrl??"jassub-worker.wasm",legacyWasmUrl:t.legacyWasmUrl??"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(t.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(t.useLocalFonts??!0),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(i){s?.catch(()=>{});try{this.destroy()}catch{}throw i}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch{e._supportsSIMD=!1}}static async _testImageBugs(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)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(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(await createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()}static async _test(){e._testSIMD(),await e._testImageBugs()}resize(e=0,t=0,s=0,i=0,a=this._video?.paused){if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||this._video?.videoWidth||e,videoHeight:this._videoHeight||this._video?.videoHeight||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t?.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let i;this._colorCallback=null;try{i=new VideoFrame(e),this._videoColorSpace=s[i.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(a){console.warn(a)}finally{i?.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){if(this._ctx){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?(this._ctx.drawImage(s.image,s.x,s.y),s.image.close()):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)));if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const n of e)n.image?.close?.()}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}async sendMessage(e,t={},s){this._destroyed||(await this._loaded,this._destroyed||(s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})))}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(d)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),d=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(d),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(l){if(a)throw l;n(l)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){if(this._destroyed)for(const t of e.images||[])t.image?.close?.();else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._videoGeneration=(this._videoGeneration||0)+1;for(const e of[this._frameCallback,this._colorCallback])null!=e&&this._video?.cancelVideoFrameCallback?.(e);this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let t,s=!1;const i=e=>{try{e()}catch(i){s||(t=i,s=!0)}};e&&i(()=>{e=this._error(e)}),i(()=>this._removeListeners()),i(()=>this._ro?.disconnect()),i(()=>this._canvasParent?.remove()),this._init?.();const a=[];for(const r of[...this._requests||[]])i(()=>{const e=r();e&&a.push(e)});for(const r of a)i(r);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,i(()=>this._worker.terminate())),s)throw t;return e}};t(a,"_supportsSIMD",null),t(a,"_hasAlphaBug",null),t(a,"_hasBitmapBug",null);let r=a;return function(e){return t=>function(e,t){const s=new r({video:e.video,...t});let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch{}try{a()}catch{}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=(t,s,i)=>((t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i)(t,"symbol"!==typeof s?s+"":s,i);typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const d=this.getVideoPlaybackQuality(),l=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames,c=l>r,_=0===r&&0===l&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===d.totalVideoFrames&&0===d.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||d.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:l,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const s={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},i={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"}},a=class e extends EventTarget{constructor(t){let s;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),s=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(t.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(t.offscreenRender??!0),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),s.then(()=>{this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:t.wasmUrl??"jassub-worker.wasm",legacyWasmUrl:t.legacyWasmUrl??"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(t.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(t.useLocalFonts??!0),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(i){s?.catch(()=>{});try{this.destroy()}catch{}throw i}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch{e._supportsSIMD=!1}}static async _testImageBugs(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)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(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(await createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()}static async _test(){e._testSIMD(),await e._testImageBugs()}resize(e=0,t=0,s=0,i=0,a=this._video?.paused){if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||this._video?.videoWidth||e,videoHeight:this._videoHeight||this._video?.videoHeight||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t?.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let i;this._colorCallback=null;try{i=new VideoFrame(e),this._videoColorSpace=s[i.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(a){console.warn(a)}finally{i?.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){if(this._ctx){try{this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?this._ctx.drawImage(s.image,s.x,s.y):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)))}finally{if(t)for(const t of e)t.image?.close()}if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const n of e)n.image?.close?.()}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}async sendMessage(e,t={},s){this._destroyed||(await this._loaded,this._destroyed||(s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})))}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(d)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),d=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(d),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(l){if(a)throw l;n(l)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){if(this._destroyed)for(const t of e.images||[])t.image?.close?.();else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._videoGeneration=(this._videoGeneration||0)+1;for(const e of[this._frameCallback,this._colorCallback])null!=e&&this._video?.cancelVideoFrameCallback?.(e);this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let t,s=!1;const i=e=>{try{e()}catch(i){s||(t=i,s=!0)}};e&&i(()=>{e=this._error(e)}),i(()=>this._removeListeners()),i(()=>this._ro?.disconnect()),i(()=>this._canvasParent?.remove()),this._init?.();const a=[];for(const r of[...this._requests||[]])i(()=>{const e=r();e&&a.push(e)});for(const r of a)i(r);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,i(()=>this._worker.terminate())),s)throw t;return e}};t(a,"_supportsSIMD",null),t(a,"_hasAlphaBug",null),t(a,"_hasBitmapBug",null);let r=a;return function(e){return t=>function(e,t){const s=new r({video:e.video,...t});let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch{}try{a()}catch{}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); diff --git a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js index 29cadc597..884304c77 100644 --- a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js +++ b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,s=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i,r=(e,r)=>{for(var n in r||(r={}))s.call(r,n)&&a(e,n,r[n]);if(t)for(var n of t(r))i.call(r,n)&&a(e,n,r[n]);return e},n=(e,t,s)=>a(e,"symbol"!==typeof t?t+"":t,s),o=(e,t,s)=>new Promise((i,a)=>{var r=e=>{try{o(s.next(e))}catch(t){a(t)}},n=e=>{try{o(s.throw(e))}catch(t){a(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(r,n);o((s=s.apply(e,t)).next())});typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const d=this.getVideoPlaybackQuality(),l=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames,c=l>r,_=0===r&&0===l&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===d.totalVideoFrames&&0===d.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||d.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:l,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const h={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},d={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"}},l=class e extends EventTarget{constructor(t){var s,i;let a;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),a=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(null==(s=t.onDemandRender)||s),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(null==(i=t.offscreenRender)||i),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),a.then(()=>{var s,i,a,r;this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:null!=(s=t.wasmUrl)?s:"jassub-worker.wasm",legacyWasmUrl:null!=(i=t.legacyWasmUrl)?i:"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(null==(a=t.asyncRender)||a),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(null==(r=t.useLocalFonts)||r),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(r){null==a||a.catch(()=>{});try{this.destroy()}catch(n){}throw r}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch(t){e._supportsSIMD=!1}}static _testImageBugs(){return o(this,null,function*(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch(o){console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(yield createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()})}static _test(){return o(this,null,function*(){e._testSIMD(),yield e._testImageBugs()})}resize(e=0,t=0,s=0,i=0,a=(e=>null==(e=this._video)?void 0:e.paused)()){var r,n;if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||(null==(r=this._video)?void 0:r.videoWidth)||e,videoHeight:this._videoHeight||(null==(n=this._video)?void 0:n.videoHeight)||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=null==t?void 0:t.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t;try{(null==(t=null==navigator?void 0:navigator.permissions)?void 0:t.query)?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(s){console.warn("Local fonts API:",s)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let s;this._colorCallback=null;try{s=new VideoFrame(e),this._videoColorSpace=h[s.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(i){console.warn(i)}finally{null==s||s.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){var n,o;if(this._ctx){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?(this._ctx.drawImage(s.image,s.x,s.y),s.image.close()):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)));if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const h of e)null==(o=null==(n=h.image)?void 0:n.close)||o.call(n)}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}sendMessage(e){return o(this,arguments,function*(e,t={},s){this._destroyed||(yield this._loaded,this._destroyed||(s?this._worker.postMessage(r({target:e,transferable:s},t),[...s]):this._worker.postMessage(r({target:e},t))))})}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(d)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),d=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(d),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(l){if(a)throw l;n(l)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){var t,s;if(this._destroyed)for(const i of e.images||[])null==(s=null==(t=i.image)?void 0:t.close)||s.call(t);else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){var e,t;this._videoGeneration=(this._videoGeneration||0)+1;for(const s of[this._frameCallback,this._colorCallback])null!=s&&(null==(t=null==(e=this._video)?void 0:e.cancelVideoFrameCallback)||t.call(e,s));this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){var t;if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let s,i=!1;const a=e=>{try{e()}catch(t){i||(s=t,i=!0)}};e&&a(()=>{e=this._error(e)}),a(()=>this._removeListeners()),a(()=>{var e;return null==(e=this._ro)?void 0:e.disconnect()}),a(()=>{var e;return null==(e=this._canvasParent)?void 0:e.remove()}),null==(t=this._init)||t.call(this);const r=[];for(const n of[...this._requests||[]])a(()=>{const e=n();e&&r.push(e)});for(const n of r)a(n);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,a(()=>this._worker.terminate())),i)throw s;return e}};n(l,"_supportsSIMD",null),n(l,"_hasAlphaBug",null),n(l,"_hasBitmapBug",null);let c=l;return function(e){return t=>function(e,t){const s=new c(r({video:e.video},t));let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch(o){}try{a()}catch(o){}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginJassub=t(),define(function(){return e.artplayerPluginJassub})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginJassub=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,s=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i,r=(e,r)=>{for(var n in r||(r={}))s.call(r,n)&&a(e,n,r[n]);if(t)for(var n of t(r))i.call(r,n)&&a(e,n,r[n]);return e},n=(e,t,s)=>a(e,"symbol"!==typeof t?t+"":t,s),o=(e,t,s)=>new Promise((i,a)=>{var r=e=>{try{o(s.next(e))}catch(t){a(t)}},n=e=>{try{o(s.throw(e))}catch(t){a(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(r,n);o((s=s.apply(e,t)).next())});typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){Object.prototype.hasOwnProperty.call(this,"_rvfcpolyfillmap")||(this._rvfcpolyfillmap={});const t=this._rvfcpolyfillid=(this._rvfcpolyfillid||0)+1,s=performance.now(),i=this.getVideoPlaybackQuality(),a=this.currentTime,r=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames,n=(o,h)=>{if(!(t in this._rvfcpolyfillmap))return;const l=this.getVideoPlaybackQuality(),d=this.mozPresentedFrames||this.mozPaintedFrames||l.totalVideoFrames-l.droppedVideoFrames,c=d>r,_=0===r&&0===d&&0===i.totalVideoFrames&&0===i.droppedVideoFrames&&0===l.totalVideoFrames&&0===l.droppedVideoFrames&&Number.isFinite(this.currentTime)&&this.currentTime!==a&&this.readyState>=2&&!this.seeking&&h-s>=1e3/30;if(c||_){const s=this.mozFrameDelay||l.totalFrameDelay-i.totalFrameDelay||0,a=h-o;delete this._rvfcpolyfillmap[t],e(h,{presentationTime:h+1e3*s,expectedDisplayTime:h+a,width:this.videoWidth,height:this.videoHeight,mediaTime:_&&!c?Math.max(0,this.currentTime):Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:d,processingDuration:s})}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(h,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>n(s,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){const t=this._rvfcpolyfillmap;t&&e in t&&(cancelAnimationFrame(t[e]),delete t[e])});const h={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},l={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"}},d=class e extends EventTarget{constructor(t){var s,i;let a;super(),this._requests=new Set;try{if(!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");if(this._loaded=new Promise(e=>{this._init=e}),a=e._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(null==(s=t.onDemandRender)||s),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(null==(i=t.offscreenRender)||i),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=()=>{this._destroyed||this.setRate(this._video.playbackRate)},this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),a.then(()=>{var s,i,a,r;this._destroyed||this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:null!=(s=t.wasmUrl)?s:"jassub-worker.wasm",legacyWasmUrl:null!=(i=t.legacyWasmUrl)?i:"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(null==(a=t.asyncRender)||a),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(null==(r=t.useLocalFonts)||r),hasBitmapBug:e._hasBitmapBug})}).catch(e=>{this._destroyed||this.destroy(e)})}catch(r){null==a||a.catch(()=>{});try{this.destroy()}catch(n){}throw r}}_createCanvas(){return 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._canvas}static _testSIMD(){if(null===e._supportsSIMD)try{e._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch(t){e._supportsSIMD=!1}}static _testImageBugs(){return o(this,null,function*(){if(null!==e._hasBitmapBug)return;const t=document.createElement("canvas"),s=t.getContext("2d",{willReadFrequently:!0});if(!s)throw new Error("Canvas rendering not supported");if("function"==typeof ImageData.prototype.constructor)try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch(o){console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(e,t,i){const a=s.createImageData(t,i);return e&&a.data.set(e),a}}const i=document.createElement("canvas"),a=i.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");t.width=i.width=1,t.height=i.height=1,s.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;s.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(t,0,0);const n=a.getImageData(0,0,1,1).data;if(e._hasAlphaBug=r[1]!==n[1],e._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const t=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(yield createImageBitmap(new ImageData(t,1)),0,0);const{data:s}=a.getImageData(0,0,1,1);e._hasBitmapBug=!1;for(const[i,a]of s.entries())if(Math.abs(t[i]-a)>15){e._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else e._hasBitmapBug=!1;t.remove(),i.remove()})}static _test(){return o(this,null,function*(){e._testSIMD(),yield e._testImageBugs()})}resize(e=0,t=0,s=0,i=0,a=(e=>null==(e=this._video)?void 0:e.paused)()){var r,n;if(!this._destroyed){if((!e||!t)&&this._video){const a=this._getVideoPosition();let r=null;if(this._videoWidth){const e=this._video.videoWidth/this._videoWidth,t=this._video.videoHeight/this._videoHeight;r=this._computeCanvasSize((a.width||0)/e,(a.height||0)/t)}else r=this._computeCanvasSize(a.width||0,a.height||0);e=r.width,t=r.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",a&&!1===this.busy?this.busy=!0:a=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||(null==(r=this._video)?void 0:r.videoWidth)||e,videoHeight:this._videoHeight||(null==(n=this._video)?void 0:n.videoHeight)||t,force:a})}}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:a}=this._video;e=i,t=a,i/a>s?e=Math.floor(a*s):t=Math.floor(i/s);return{width:e,height:t,x:(i-e)/2,y:(a-t)/2}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const a=s<1?-1:1;let r=t*i;a*r*s<=a*this.prescaleHeightLimit?r*=s:a*r0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){if(this._destroyed)return;const t={seeking:!0,waiting:!0,playing:!1}[e];null!=t&&(this._playstate=t),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){if(!this._destroyed)if(e instanceof HTMLVideoElement){if(this._removeListeners(),this._video=e,this._videoWidth=this._videoHeight=0,this._videoColorSpace=null,this._canvasParent&&this._canvasParent.parentNode!==e.parentNode&&e.insertAdjacentElement("afterend",this._canvasParent),this._onDemandRender)this._requestFrame();else{this._playstate=e.paused;for(const t of["timeupdate","progress","waiting","seeking","playing"])e.addEventListener(t,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))}else 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)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style: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})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=null==t?void 0:t.find(t=>t.fullName.toLowerCase()===e);s&&s.blob().then(e=>{e.arrayBuffer().then(e=>{this.addFont(new Uint8Array(e))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t;try{(null==(t=null==navigator?void 0:navigator.permissions)?void 0:t.query)?navigator.permissions.query({name:"local-fonts"}).then(t=>{"granted"===t.state&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(s){console.warn("Local fonts API:",s)}}_unbusy(){this._destroyed||(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._requestFrame()}_requestFrame(){if(this._destroyed||!this._video||null!=this._frameCallback)return;const e=this._video,t=this._videoGeneration;this._frameCallback=e.requestVideoFrameCallback((s,i)=>{this._destroyed||t!==this._videoGeneration||e!==this._video||(this._frameCallback=null,this._handleRVFC(s,i))})}_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._destroyed||!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.busy=!1,this._lastDemandTime=null,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){if(this._destroyed||null!=this._colorCallback)return;const e=this._video,t=this._videoGeneration;this._colorCallback=e.requestVideoFrameCallback(()=>{if(this._destroyed||t!==this._videoGeneration||e!==this._video)return;let s;this._colorCallback=null;try{s=new VideoFrame(e),this._videoColorSpace=h[s.colorSpace.matrix],this.sendMessage("getColorSpace")}catch(i){console.warn(i)}finally{null==s||s.close()}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){var n,o,h;if(this._ctx){try{this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==a)&&(this._canvasctrl.width=i,this._canvasctrl.height=a,this._verifyColorSpace({subtitleColorSpace:r})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const s of e)s.image&&(t?this._ctx.drawImage(s.image,s.x,s.y):(this._bufferCanvas.width=s.w,this._bufferCanvas.height=s.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(s.image)),s.w,s.h),0,0),this._ctx.drawImage(this._bufferCanvas,s.x,s.y)))}finally{if(t)for(const t of e)null==(h=t.image)||h.close()}if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let t=0;const i=s.bitmaps||e.length;delete s.bitmaps;for(const e in s)t+=s[e];console.log("Bitmaps: "+i+" Total: "+(0|t)+"ms",s)}}else for(const l of e)null==(o=null==(n=l.image)?void 0:n.close)||o.call(n)}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(!0!==this._offscreenRender||this._initialOffscreenSent||(this._initialOffscreenSent=!0,this._worker.postMessage({target:"offscreenCanvas",transferable:[this._canvasctrl]},[this._canvasctrl])),this._init(),this.dispatchEvent(new CustomEvent("ready")))}sendMessage(e){return o(this,arguments,function*(e,t={},s){this._destroyed||(yield this._loaded,this._destroyed||(s?this._worker.postMessage(r({target:e,transferable:s},t),[...s]):this._worker.postMessage(r({target:e},t))))})}_fetchFromWorker(e,t){if(this._destroyed)return void t(new Error("JASSUB instance destroyed"));const s=this._worker;let i,a=!1;const r=()=>{s.removeEventListener("message",o),s.removeEventListener("error",h),clearTimeout(i),this._requests.delete(l)},n=(e,s)=>{a||(a=!0,r(),t(e,s))},o=({data:t})=>{t.target===e.target&&n(null,t)},h=e=>n(e),l=()=>{if(!a)return a=!0,r(),()=>t(new Error("JASSUB instance destroyed"))};try{this._requests.add(l),s.addEventListener("message",o),s.addEventListener("error",h),i=setTimeout(()=>h(new Error("Error: Timeout while try to fetch "+e.target)),5e3),s.postMessage(e)}catch(d){if(a)throw d;n(d)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){var t,s;if(this._destroyed)for(const i of e.images||[])null==(s=null==(t=i.image)?void 0:t.close)||s.call(t);else 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});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){var e,t;this._videoGeneration=(this._videoGeneration||0)+1;for(const s of[this._frameCallback,this._colorCallback])null!=s&&(null==(t=null==(e=this._video)?void 0:e.cancelVideoFrameCallback)||t.call(e,s));this._frameCallback=this._colorCallback=null,this._lastDemandTime=null,this._onDemandRender&&(this.busy=!1),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){var t;if(this._destroyed)return e&&this._error(e);this._destroyed=!0;let s,i=!1;const a=e=>{try{e()}catch(t){i||(s=t,i=!0)}};e&&a(()=>{e=this._error(e)}),a(()=>this._removeListeners()),a(()=>{var e;return null==(e=this._ro)?void 0:e.disconnect()}),a(()=>{var e;return null==(e=this._canvasParent)?void 0:e.remove()}),null==(t=this._init)||t.call(this);const r=[];for(const n of[...this._requests||[]])a(()=>{const e=n();e&&r.push(e)});for(const n of r)a(n);if(this._worker&&(this._worker.onmessage=this._worker.onerror=null,a(()=>this._worker.terminate())),i)throw s;return e}};n(d,"_supportsSIMD",null),n(d,"_hasAlphaBug",null),n(d,"_hasBitmapBug",null);let c=d;return function(e){return t=>function(e,t){const s=new c(r({video:e.video},t));let i=!1;const a=()=>{if(!i&&!s._destroyed){i=!0;try{s.destroy()}catch(e){throw i=!1,e}}};try{s._canvasParent&&(s._canvasParent.style.zIndex=20),e.on("destroy",a)}catch(n){try{e.off("destroy",a)}catch(o){}try{a()}catch(o){}throw n}return{name:"artplayerPluginJassub",instance:s}}(t,e)}}); diff --git a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs index cc770e7eb..5cb773479 100644 --- a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs +++ b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs @@ -564,9 +564,13 @@ const _o = class _o extends EventTarget { for (const image of e) image.image?.close?.(); return; } - this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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))); + try { + this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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) : (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))); + } finally { + if (t) for (const i of e) i.image?.close(); + } if (this.debug) { s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime; let i = 0; diff --git a/packages/artplayer-plugin-jassub/src/jassub.es.js b/packages/artplayer-plugin-jassub/src/jassub.es.js index 88088b0ca..ea3c2d06f 100644 --- a/packages/artplayer-plugin-jassub/src/jassub.es.js +++ b/packages/artplayer-plugin-jassub/src/jassub.es.js @@ -563,9 +563,13 @@ class o extends EventTarget { for (const image of e) image.image?.close?.(); return; } - this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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))); + try { + this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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) : (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))); + } finally { + if (t) for (const i of e) i.image?.close(); + } if (this.debug) { s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime; let i = 0; diff --git a/refactor/baselines/jassub-render-patch.json b/refactor/baselines/jassub-render-patch.json new file mode 100644 index 000000000..148043aa0 --- /dev/null +++ b/refactor/baselines/jassub-render-patch.json @@ -0,0 +1,11 @@ +{ + "task": "PKG-JASSUB-10", + "baselineCommit": "04060aa8b6b5cf070bac0c6a9d50eb329fab9719", + "file": "packages/artplayer-plugin-jassub/src/jassub.es.js", + "originalSha256LF": "60590f2fca6ffded74f96f8a6001a86c32eb38f5e1fae26338cbbb43e93fb70a", + "patchedSha256LF": "ebabca473edfbe7f9768dba25b2c2403f2c9c5dec64292fa008d8eb758e32696", + "patchFile": "refactor/baselines/jassub-render.patch", + "patchSha256LF": "4de7af48eef2839e3b569592586132cca7c2a1ff0222a1fe14a06835ed11cb74", + "predecessor": "refactor/baselines/jassub-hybrid-patch.json", + "purpose": "Release the complete async bitmap batch on native canvas rendering failure while preserving the exception and synchronous ImageData path." +} diff --git a/refactor/baselines/jassub-render-validation.json b/refactor/baselines/jassub-render-validation.json new file mode 100644 index 000000000..f08b2e703 --- /dev/null +++ b/refactor/baselines/jassub-render-validation.json @@ -0,0 +1,5441 @@ +{ + "task": "PKG-JASSUB-10", + "status": "validated", + "baselineCommit": "04060aa8b6b5cf070bac0c6a9d50eb329fab9719", + "unitRed": { + "log": "refactor/.cache/jassub-render-red.log", + "passed": 12, + "failed": 4 + }, + "browserRed": { + "log": "refactor/.cache/jassub-render-browser-red-final.log", + "passed": 0, + "failed": 3 + }, + "limitations": [ + "Native fault injection, not proof of spontaneous bitmap corruption.", + "Firefox offscreen stall remains open." + ], + "date": "2026-09-14T07:27:35.052Z", + "environment": { + "node": "24.21.0", + "yarn": "1.22.22", + "platform": "win32", + "engines": [ + "Chromium 153.0.8010.12", + "Firefox 155.0", + "Windows WebKit 26.6" + ] + }, + "artifactOrigin": "Normal yarn build artplayer-plugin-jassub; no manual output edits", + "artifacts": [ + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995", + "docs": { + "path": "docs/compiled/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + } + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae", + "docs": { + "path": "docs/compiled/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + } + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs", + "sha256": "09dd8391827beafce39daa5ceb0bf098c69236c5abee4d2dbf6350354ea94098", + "docs": { + "path": "docs/compiled/artplayer-plugin-jassub.mjs", + "sha256": "09dd8391827beafce39daa5ceb0bf098c69236c5abee4d2dbf6350354ea94098" + } + } + ], + "baselineArtifact": { + "path": "refactor/.cache/jassub-render-prechange.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f" + }, + "inputs": [ + { + "path": "packages/artplayer-plugin-jassub/src/jassub.es.js", + "sha256": "ebabca473edfbe7f9768dba25b2c2403f2c9c5dec64292fa008d8eb758e32696" + }, + { + "path": "packages/artplayer-plugin-jassub/ARCHITECTURE.md", + "sha256": "b61040dc0d9cad6ec01c3cdc25115f025173a91871d8a3a17cf479523beaf1e4" + }, + { + "path": "test/jassub-offscreen.test.js", + "sha256": "e0708d5049cb4d209f185de21aee2326e8b8d956c9b4d3c764f416bb1f5ef24c" + }, + { + "path": "test/browser/jassub-render-failure.spec.js", + "sha256": "3eabcdaf2c76bfca3fa7bd64873dcb2f8bd491204e6d7859c4011f5a29389f40" + }, + { + "path": "test/browser/jassub-native.spec.js", + "sha256": "3198fb4314a8ac3d379beb027aed79103905f23ab615d7e54a368c7a6763e105" + }, + { + "path": "refactor/scripts/jassub-vendor-patch.test.mjs", + "sha256": "212ee2b2d61cd9a72ae90389e0439f98119d1a761105be86592d8a0b01c2e1cd" + }, + { + "path": "docs/assets/jassub/jassub-worker.js", + "sha256": "0fb9a83f96fc39426e733a94f3e7fe4e37099324c08328450ba032b653e85707" + }, + { + "path": "docs/assets/jassub/jassub-worker.wasm", + "sha256": "73880d99ba9260ac39fc4476491cf254ca784c76f3af2fb03f3f9be8065ad3c0" + }, + { + "path": "docs/assets/jassub/jassub-worker-modern.wasm", + "sha256": "8a8b37463713d1f2ddbc1fbb8a35889d9565e5a6670851758b46b2216d8e513c" + }, + { + "path": "docs/assets/jassub/default.woff2", + "sha256": "886929903707c5bb28b07cd2eed69921b3d5ef5c49c73a0dd1e187ebf6546a4c" + } + ], + "validation": [ + { + "command": "yarn test:jassub", + "passed": 201, + "failed": 0, + "log": { + "path": "refactor/.cache/jassub-render-suite-final.log", + "sha256": "5cc052851610a75450d3b91998c800f20654ba68c53a3fb68b89911616c0b025" + } + }, + { + "command": "ARTPLAYER_JASSUB_ARTIFACT=; node --test test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js", + "passedPerFormat": 69, + "failed": 0, + "logs": [ + { + "path": "refactor/.cache/jassub-render-main-unit.log", + "sha256": "0216eb4309c06934cf648b1087c2b1eb4de4a24c388976c750f40711454c2a09" + }, + { + "path": "refactor/.cache/jassub-render-legacy-unit.log", + "sha256": "0c41fcf0046d1f0f5d50ef1a2ed54f2771271064ee7107695958b217412a922d" + } + ] + }, + { + "command": "Final offscreen tests against exact Git prechange main", + "passed": 12, + "failed": 4, + "expectedFailure": true, + "log": { + "path": "refactor/.cache/jassub-render-red-exact.log", + "sha256": "b36df034d7bb1862551d420b559e4433208e4866106498569e067e8c59a1248b" + } + }, + { + "command": "Strict package tsc", + "exitCode": 0, + "log": { + "path": "refactor/.cache/jassub-render-types.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + { + "command": "Targeted read-only eslint", + "exitCode": 0, + "log": { + "path": "refactor/.cache/jassub-render-lint-clean.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + { + "command": "yarn check:toolchain --strict", + "exitCode": 0, + "log": { + "path": "refactor/.cache/jassub-render-toolchain.log", + "sha256": "73998cb4a2395aa495dd689a7f3677c253f077f6f381d94f13555fc3c059b4b6" + } + }, + { + "command": "yarn check:types", + "files": 37, + "changed": 0, + "log": { + "path": "refactor/.cache/jassub-render-declarations.log", + "sha256": "d10a5cd2101a35bd5383f1e1df591578405b14341e5cac786edd54436c8b1e73" + } + }, + { + "command": "Final formatted source node --test test/jassub-offscreen.test.js", + "passed": 16, + "failed": 0, + "log": { + "path": "refactor/.cache/jassub-render-source-final.log", + "sha256": "7334355be083a5c0324df8aa50bbfbe5590187cd533a93bd71a74b94033c966f" + } + }, + { + "command": "node --test refactor/scripts/risk-register.test.mjs refactor/scripts/jassub-vendor-patch.test.mjs", + "passed": 3, + "failed": 0, + "log": { + "path": "refactor/.cache/jassub-render-register-checks.log", + "sha256": "c36d095d2cefbed227f9ea465f27abf4389eddb6736367ed98f221b4cf4b7df6" + } + } + ], + "browserCommands": { + "fault": "ARTPLAYER_JASSUB_ARTIFACT=; yarn test:browser test/browser/jassub-render-failure.spec.js --workers=1", + "normal": "ARTPLAYER_JASSUB_ARTIFACT=; yarn test:browser test/browser/jassub-native.spec.js --workers=1", + "legacy": "ARTPLAYER_JASSUB_ARTIFACT=; yarn test:browser test/browser/jassub-render-failure.spec.js test/browser/jassub-native.spec.js --workers=1", + "options": "Default diagnostics unset; native regression explicit main-thread, asyncRender enabled. No retries or timeout increases." + }, + "runs": [ + { + "path": "refactor/.cache/jassub-render-harness-browser/report.json", + "sha256": "ab2a5bdceaa0a59273024bf471a49875b466b68a194d6c8de5086d33abde42e7", + "classification": "Invalid initial harness, not production defect evidence", + "cases": [ + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "chromium", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "eval at evaluate (:311:30), ", + "column": 16, + "line": 23 + }, + "message": "Error: page.evaluate: TypeError: Cannot read properties of undefined (reading 'addEventListener')\n at eval (eval at evaluate (:311:30), :23:16)\n at UtilityScript.evaluate (:313:16)\n at UtilityScript. (:1:44)\n at eval (eval at evaluate (:311:30), :23:16)\n at UtilityScript.evaluate (:313:16)\n at UtilityScript. (:1:44)\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:26:16" + } + ], + "attachments": { + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "firefox", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "@debugger eval code line 311 > eval", + "column": 7, + "line": 23 + }, + "message": "Error: page.evaluate: can't access property \"addEventListener\", instance is undefined\n@debugger eval code line 311 > eval:23:7\nevaluate@debugger eval code:313:16\n@debugger eval code:1:44\n@debugger eval code:1:62\n\n at @debugger eval code line 311 > eval:23:7\n at evaluate@debugger eval code:313:16\n at @debugger eval code:1:44\n at @debugger eval code:1:62\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:26:16" + } + ], + "attachments": { + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 16, + "line": 26 + }, + "message": "Error: page.evaluate: TypeError: undefined is not an object (evaluating 'instance.addEventListener')\n\n 24 | await page.locator('#play').click()\n 25 | try {\n> 26 | await page.evaluate((subtitle) => {\n | ^\n 27 | const register = window.artplayerPluginJassub.default || window.artplayerPluginJassub\n 28 | const { instance } = window.art.plugins.add(register({\n 29 | offscreenRender: false,\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:26:16" + } + ], + "attachments": { + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "resourceType": "xhr", + "failure": { + "errorText": "Load request cancelled" + } + } + ] + } + } + } + ] + }, + { + "path": "refactor/.cache/jassub-render-red-browser/report.json", + "sha256": "c11c2fc67a03467f261a04d97ae9d8479b109d4e52780fc8bbe9fb570a44c82a", + "classification": "Old main: native failure leaves the remaining bitmaps open", + "cases": [ + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "chromium", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 28, + "line": 77 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 2\u001b[39m\n\n\u001b[2m Array [\u001b[22m\n\u001b[2m 0,\u001b[22m\n\u001b[32m- 0,\u001b[39m\n\u001b[32m- 0,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[2m ]\u001b[22m\n\n 75 | expect(evidence.asyncRender).toBe(true)\n 76 | expect(evidence.failure?.name).toBe('InvalidStateError')\n> 77 | expect(evidence.after).toEqual([0, 0, 0])\n | ^\n 78 | await expect.poll(() => page.evaluate(() => {\n 79 | const { instance } = window.bitmapFailureProbe\n 80 | const pixels = instance._ctx.getImageData(0, 0, instance._canvas.width, instance._canvas.height).data\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:77:28" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 434, + 434 + ], + "failure": { + "name": "InvalidStateError", + "message": "Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image source is detached" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "firefox", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 28, + "line": 77 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 2\u001b[39m\n\n\u001b[2m Array [\u001b[22m\n\u001b[2m 0,\u001b[22m\n\u001b[32m- 0,\u001b[39m\n\u001b[32m- 0,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[2m ]\u001b[22m\n\n 75 | expect(evidence.asyncRender).toBe(true)\n 76 | expect(evidence.failure?.name).toBe('InvalidStateError')\n> 77 | expect(evidence.after).toEqual([0, 0, 0])\n | ^\n 78 | await expect.poll(() => page.evaluate(() => {\n 79 | const { instance } = window.bitmapFailureProbe\n 80 | const pixels = instance._ctx.getImageData(0, 0, instance._canvas.width, instance._canvas.height).data\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:77:28" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 434, + 434 + ], + "failure": { + "name": "InvalidStateError", + "message": "CanvasRenderingContext2D.drawImage: Passed-in ImageBitmap is closed" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 28, + "line": 77 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 2\u001b[39m\n\n\u001b[2m Array [\u001b[22m\n\u001b[2m 0,\u001b[22m\n\u001b[32m- 0,\u001b[39m\n\u001b[32m- 0,\u001b[39m\n\u001b[31m+ 868,\u001b[39m\n\u001b[31m+ 868,\u001b[39m\n\u001b[2m ]\u001b[22m\n\n 75 | expect(evidence.asyncRender).toBe(true)\n 76 | expect(evidence.failure?.name).toBe('InvalidStateError')\n> 77 | expect(evidence.after).toEqual([0, 0, 0])\n | ^\n 78 | await expect.poll(() => page.evaluate(() => {\n 79 | const { instance } = window.bitmapFailureProbe\n 80 | const pixels = instance._ctx.getImageData(0, 0, instance._canvas.width, instance._canvas.height).data\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:77:28" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f", + "evidence": { + "before": [ + 868, + 868, + 868 + ], + "after": [ + 0, + 868, + 868 + ], + "failure": { + "name": "InvalidStateError", + "message": "The object is in an invalid state." + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + } + ] + }, + { + "path": "refactor/.cache/jassub-render-force-busy-browser/report.json", + "sha256": "a1bfd4cb307e7cfe7f101aa90b6a8a328d2a089440038980145d530e359d7eb3", + "classification": "Intermediate candidate: all batch releases pass; two recovery failures from force while busy", + "cases": [ + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "chromium", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 9, + "line": 93 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m100\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 91 | }\n 92 | return green\n> 93 | })).toBeGreaterThan(100)\n | ^\n 94 | expect(await page.evaluate(() => window.bitmapFailureProbe.errors)).toEqual([])\n 95 | }\n 96 | finally {\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:93:9" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image source is detached" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "CanvasRenderingContext2D.drawImage: Passed-in ImageBitmap is closed" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 9, + "line": 93 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m100\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 91 | }\n 92 | return green\n> 93 | })).toBeGreaterThan(100)\n | ^\n 94 | expect(await page.evaluate(() => window.bitmapFailureProbe.errors)).toEqual([])\n 95 | }\n 96 | finally {\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:93:9" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995", + "evidence": { + "before": [ + 868, + 868, + 868 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "The object is in an invalid state." + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + } + ] + }, + { + "path": "refactor/.cache/jassub-render-main-browser/report.json", + "sha256": "0c94bf1d7effb1c8d9f694a90045d6ccd03616baa887be8d777bd756bf6384c8", + "classification": "Final main fault injection and recovery", + "cases": [ + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image source is detached" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "CanvasRenderingContext2D.drawImage: Passed-in ImageBitmap is closed" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995", + "evidence": { + "before": [ + 868, + 868, + 868 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "The object is in an invalid state." + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + } + ] + }, + { + "path": "refactor/.cache/jassub-render-main-native-browser/report.json", + "sha256": "55534ea4a5754d78b8368d2445c3a8907b366361afaeacb65c25d16d2b48e3fe", + "classification": "Final main: native main-thread playback across three cores", + "cases": [ + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false, defaultOffscreen=false", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 17, + "receivedCount": 16 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.50815, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "decodedFrames": 16 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.016988, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.111681, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 13, + "delivered": 12, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1333.8, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1317.7, + "presentedFrames": 20, + "processingDuration": 0.1064, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.111681, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false, defaultOffscreen=false", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 17, + "receivedCount": 16 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.506226, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "decodedFrames": 16 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.014403, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.119913, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 13, + "delivered": 12, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1300.6, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1284.6, + "presentedFrames": 20, + "processingDuration": 0.103, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.119913, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false, defaultOffscreen=false", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 18, + "receivedCount": 17 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.507059, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "decodedFrames": 16 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.0167, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.121131, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 146, + "droppedFrames": 0, + "decodedFrames": 146 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 14, + "delivered": 13, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1319.5, + "height": 720, + "mediaTime": 10.24, + "presentationTime": 1303, + "presentedFrames": 21, + "processingDuration": 0.132, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.121131, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 146, + "droppedFrames": 0, + "decodedFrames": 146 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false, defaultOffscreen=false", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 15, + "receivedCount": 14 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.572448, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 13, + "droppedFrames": 0, + "mozPresentedFrames": 13 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.000021, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040492, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "mozPresentedFrames": 16 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1843.26, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1843.26, + "presentedFrames": 45, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040492, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "mozPresentedFrames": 16 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false, defaultOffscreen=false", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 14, + "receivedCount": 13 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.577102, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 13, + "droppedFrames": 0, + "mozPresentedFrames": 13 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.000021, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040457, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "mozPresentedFrames": 16 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 10, + "delivered": 9, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1568.46, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1568.46, + "presentedFrames": 45, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040457, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "mozPresentedFrames": 16 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false, defaultOffscreen=false", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 14, + "receivedCount": 13 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.331069, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 7, + "droppedFrames": 0, + "mozPresentedFrames": 7 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.000021, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 8, + "droppedFrames": 0, + "mozPresentedFrames": 8 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040195, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 9, + "droppedFrames": 0, + "mozPresentedFrames": 9 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 10, + "delivered": 9, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1246, + "height": 720, + "mediaTime": 10.08, + "presentationTime": 1246, + "presentedFrames": 32, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040195, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 9, + "droppedFrames": 0, + "mozPresentedFrames": 9 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false, defaultOffscreen=false", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 19, + "receivedCount": 17 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.5820834, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.0713125, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3293542, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 15, + "delivered": 14, + "after": "function", + "lastMetadata": { + "presentationTime": 3210, + "expectedDisplayTime": 3552, + "width": 640, + "height": 360, + "mediaTime": 10.6897709, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": false, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3293542, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false, defaultOffscreen=false", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 22, + "receivedCount": 20 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.6690626, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.1203959, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.4891459, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 18, + "delivered": 17, + "after": "function", + "lastMetadata": { + "presentationTime": 3568, + "expectedDisplayTime": 3940, + "width": 640, + "height": 360, + "mediaTime": 10.8824377, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": false, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.4891459, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false, defaultOffscreen=false", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "6dc1e7a9e2172c4983c99b2e16c969a974039e6fe9a48b94817dd0a7a30fa995" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 19, + "receivedCount": 18 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.5862292, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.1024583, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3547709, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 15, + "delivered": 14, + "after": "function", + "lastMetadata": { + "presentationTime": 3428, + "expectedDisplayTime": 3787, + "width": 640, + "height": 360, + "mediaTime": 10.7305418, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": false, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3547709, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + } + ] + }, + { + "path": "refactor/.cache/jassub-render-legacy-browser/report.json", + "sha256": "cc96b5249214b750293d5db4953d6bcec5eba7c28c19722458d54058ebbb5e6d", + "classification": "Final legacy: native failure/recovery and main-thread playback", + "cases": [ + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false, defaultOffscreen=false", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 15, + "receivedCount": 14 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.504158, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "decodedFrames": 16 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.02, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.02, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1242.8, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1226.3, + "presentedFrames": 18, + "processingDuration": 0.001, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.02, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false, defaultOffscreen=false", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 17, + "receivedCount": 16 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.507876, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "decodedFrames": 16 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.011122, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.113968, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 13, + "delivered": 12, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1284.7, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1268.7, + "presentedFrames": 20, + "processingDuration": 0.1041, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.113968, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false, defaultOffscreen=false", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 18, + "receivedCount": 17 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.50656, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "decodedFrames": 16 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.016375, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 142, + "droppedFrames": 0, + "decodedFrames": 142 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.118281, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 14, + "delivered": 13, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1307, + "height": 720, + "mediaTime": 10.24, + "presentationTime": 1291.2, + "presentedFrames": 21, + "processingDuration": 0.1267, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.118281, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false, defaultOffscreen=false", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 19, + "receivedCount": 18 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.557524, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 13, + "droppedFrames": 0, + "mozPresentedFrames": 13 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.000022, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.161224, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 19, + "droppedFrames": 0, + "mozPresentedFrames": 19 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 15, + "delivered": 14, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1915.22, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1915.22, + "presentedFrames": 48, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.161224, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 19, + "droppedFrames": 0, + "mozPresentedFrames": 19 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false, defaultOffscreen=false", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 16, + "receivedCount": 15 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.564941, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 13, + "droppedFrames": 0, + "mozPresentedFrames": 13 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.000021, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 14, + "droppedFrames": 0, + "mozPresentedFrames": 14 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040466, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 12, + "delivered": 11, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1570.66, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1570.66, + "presentedFrames": 44, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040466, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false, defaultOffscreen=false", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 15, + "receivedCount": 14 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.333505, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 7, + "droppedFrames": 0, + "mozPresentedFrames": 7 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.000021, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 9, + "droppedFrames": 0, + "mozPresentedFrames": 9 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040411, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 10, + "droppedFrames": 0, + "mozPresentedFrames": 10 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1332.88, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1332.88, + "presentedFrames": 34, + "width": 1280 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": true, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.040411, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 10, + "droppedFrames": 0, + "mozPresentedFrames": 10 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false, defaultOffscreen=false", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 21, + "receivedCount": 20 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.6290835, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.1345834, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.5196876, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 17, + "delivered": 16, + "after": "function", + "lastMetadata": { + "presentationTime": 3722, + "expectedDisplayTime": 4097, + "width": 640, + "height": 360, + "mediaTime": 10.9120002, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": false, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.5196876, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false, defaultOffscreen=false", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 20, + "receivedCount": 19 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.5798542, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.0724167, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3716459, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 16, + "delivered": 15, + "after": "function", + "lastMetadata": { + "presentationTime": 3375, + "expectedDisplayTime": 3765, + "width": 640, + "height": 360, + "mediaTime": 10.6888543, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": false, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3716459, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false, defaultOffscreen=false", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "defaultOffscreen": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sentCount": 19, + "receivedCount": 17 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 0.5524376, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "clip": { + "x": 8, + "y": 8, + "width": 640, + "height": 360 + }, + "time": 10.1231875, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3799167, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 15, + "delivered": 14, + "after": "function", + "lastMetadata": { + "presentationTime": 3484, + "expectedDisplayTime": 3842, + "width": 640, + "height": 360, + "mediaTime": 10.7383334, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "renderCalls": [ + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 0 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + }, + { + "context": true, + "images": 2 + } + ], + "offscreen": { + "available": false, + "selected": false + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "clip": { + "x": 0, + "y": 0, + "width": 1280, + "height": 720 + }, + "time": 10.3799167, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + "readbackFrame": false, + "synchronousRender": false, + "screenshotReadback": false + }, + "limitation": "Actual worker, WASM and local font; offscreen option explicitly false. Canvas pixels are read from a separate canvas copying the native display bitmap. No full failure recovery, sustained GPU/memory or physical-device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image source is detached" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "CanvasRenderingContext2D.drawImage: Passed-in ImageBitmap is closed" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "attachments": { + "bitmap-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "373682081158e2f94a64b5917efcedfef3e65c8103c07554a3a482c81d911aae", + "evidence": { + "before": [ + 868, + 868, + 868 + ], + "after": [ + 0, + 0, + 0 + ], + "failure": { + "name": "InvalidStateError", + "message": "The object is in an invalid state." + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + } + ] + }, + { + "path": "refactor/.cache/jassub-render-red-exact-browser/report.json", + "sha256": "d234a22017bf8f362d680465c539f980fddc07d839e0385be30f8060580661f6", + "classification": "Final identical fault regression against exact prechange main", + "cases": [ + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "chromium", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 28, + "line": 83 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 2\u001b[39m\n\n\u001b[2m Array [\u001b[22m\n\u001b[2m 0,\u001b[22m\n\u001b[32m- 0,\u001b[39m\n\u001b[32m- 0,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[2m ]\u001b[22m\n\n 81 | expect(evidence.asyncRender).toBe(true)\n 82 | expect(evidence.failure?.name).toBe('InvalidStateError')\n> 83 | expect(evidence.after).toEqual([0, 0, 0])\n | ^\n 84 | // A busy renderer intentionally ignores resize(force); let its pending demand finish first.\n 85 | await expect.poll(() => page.evaluate(() => window.bitmapFailureProbe.instance.busy)).toBe(false)\n 86 | await page.evaluate(() => window.bitmapFailureProbe.instance.resize(0, 0, 0, 0, true))\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:83:28" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "refactor/.cache/jassub-render-prechange.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 434, + 434 + ], + "failure": { + "name": "InvalidStateError", + "message": "Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image source is detached" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "firefox", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 28, + "line": 83 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 2\u001b[39m\n\n\u001b[2m Array [\u001b[22m\n\u001b[2m 0,\u001b[22m\n\u001b[32m- 0,\u001b[39m\n\u001b[32m- 0,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[31m+ 434,\u001b[39m\n\u001b[2m ]\u001b[22m\n\n 81 | expect(evidence.asyncRender).toBe(true)\n 82 | expect(evidence.failure?.name).toBe('InvalidStateError')\n> 83 | expect(evidence.after).toEqual([0, 0, 0])\n | ^\n 84 | // A busy renderer intentionally ignores resize(force); let its pending demand finish first.\n 85 | await expect.poll(() => page.evaluate(() => window.bitmapFailureProbe.instance.busy)).toBe(false)\n 86 | await page.evaluate(() => window.bitmapFailureProbe.instance.resize(0, 0, 0, 0, true))\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:83:28" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "refactor/.cache/jassub-render-prechange.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f", + "evidence": { + "before": [ + 434, + 434, + 434 + ], + "after": [ + 0, + 434, + 434 + ], + "failure": { + "name": "InvalidStateError", + "message": "CanvasRenderingContext2D.drawImage: Passed-in ImageBitmap is closed" + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + }, + { + "title": "JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame", + "project": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js", + "column": 28, + "line": 83 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 2\u001b[39m\n\n\u001b[2m Array [\u001b[22m\n\u001b[2m 0,\u001b[22m\n\u001b[32m- 0,\u001b[39m\n\u001b[32m- 0,\u001b[39m\n\u001b[31m+ 868,\u001b[39m\n\u001b[31m+ 868,\u001b[39m\n\u001b[2m ]\u001b[22m\n\n 81 | expect(evidence.asyncRender).toBe(true)\n 82 | expect(evidence.failure?.name).toBe('InvalidStateError')\n> 83 | expect(evidence.after).toEqual([0, 0, 0])\n | ^\n 84 | // A busy renderer intentionally ignores resize(force); let its pending demand finish first.\n 85 | await expect.poll(() => page.evaluate(() => window.bitmapFailureProbe.instance.busy)).toBe(false)\n 86 | await page.evaluate(() => window.bitmapFailureProbe.instance.resize(0, 0, 0, 0, true))\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-render-failure.spec.js:83:28" + } + ], + "attachments": { + "bitmap-failure": { + "artifact": "refactor/.cache/jassub-render-prechange.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f", + "evidence": { + "before": [ + 868, + 868, + 868 + ], + "after": [ + 0, + 868, + 868 + ], + "failure": { + "name": "InvalidStateError", + "message": "The object is in an invalid state." + }, + "asyncRender": true + }, + "limitation": "Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Safari/605.1.15", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + } + } + ] + } + ], + "notes": [ + "Full suite and exact old-main red sequence precede a formatting-only if brace; the final formatted source offscreen group was re-run and passes all 16 tests. Browser test content was unchanged after final native/red-exact runs.", + "The two new bitmap copies are native resources; the fault is deliberate closure of the original Worker bitmap, not observed spontaneous corruption.", + "No public types, package version, Worker protocol, dependencies or default options changed.", + "Main/legacy each have 12 final native cases; ESM is built but not separately browser-executed.", + "JASSUB-09/05 and Firefox offscreen risk remain open. No package-wide release acceptance, remote CI, physical devices or publication is claimed." + ] +} diff --git a/refactor/baselines/jassub-render.patch b/refactor/baselines/jassub-render.patch new file mode 100644 index 000000000..be8f60312 --- /dev/null +++ b/refactor/baselines/jassub-render.patch @@ -0,0 +1,21 @@ +diff --git a/packages/artplayer-plugin-jassub/src/jassub.es.js b/packages/artplayer-plugin-jassub/src/jassub.es.js +index 88088b0ca..ea3c2d06f 100644 +--- a/packages/artplayer-plugin-jassub/src/jassub.es.js ++++ b/packages/artplayer-plugin-jassub/src/jassub.es.js +@@ -563,9 +563,13 @@ class o extends EventTarget { + for (const image of e) image.image?.close?.(); + return; + } +- this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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))); ++ try { ++ this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), 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) : (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))); ++ } finally { ++ if (t) for (const i of e) i.image?.close(); ++ } + if (this.debug) { + s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime; + let i = 0; diff --git a/refactor/changes/2026-09-14-PKG-JASSUB-10-render-resources.md b/refactor/changes/2026-09-14-PKG-JASSUB-10-render-resources.md new file mode 100644 index 000000000..19eb37860 --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-JASSUB-10-render-resources.md @@ -0,0 +1,47 @@ +# PKG-JASSUB-10 绘制异常后的位图释放 + +修复主线程 asyncRender 的整批资源归属。旧 wrapper 在 drawImage 成功之后才关闭 +当前 ImageBitmap;clearRect、画布尺寸更新或任意一次 drawImage 抛错,会跳过当前 +和剩余位图的释放。现在在 finally 释放本消息的整个异步位图批次,原生异常继续 +向调用方传播;正常绘制顺序、Worker 协议、公开类型和默认渲染模式保持不变。 +同步 ImageData/ArrayBuffer 路径不执行 bitmap.close。 + +受控测试注入四种失败位置并验证异常对象、绘制次数及每张位图只释放一次。另有 +同步缓冲区对照;既有正常 hybrid 绘制、迟到帧及销毁回归保留。原始源码16项中 +12通过4失败;这四项都是资源释放断言失败。 + +原生浏览器测试从真实 JASSUB Worker/WASM 接收 ImageBitmap,再创建两个原生 +副本作为同批后续位图。主动关闭第一张触发原生 InvalidStateError,检查其余位图 +width归零并强制下一帧绘制,绿色字幕像素必须恢复。它证明受控异常后的清理, +不声称 Worker 会自然产出损坏位图,也不把它当成 Firefox 默认 offscreen 停顿的 +根因或修复。初版测试误读 plugins.add 返回值导致的三项初始化失败单独保留; +修正后旧产物三浏览器都在剩余位图未释放的断言失败,不能混用两轮失败证据。 + +首轮候选的位图释放三浏览器都通过,但两项字幕恢复失败。原因是测试在仍有待处理 +demand 时调用 resize(force),现有实现有意忽略 busy 状态的强制重绘。测试现在暂停 +原生video、等待当前需求完成,再调用同一公开resize;没有修改生产时钟,也没有 +延长等待。最终main三项异常恢复通过;legacy与正常播放合跑12项通过。旧产物以 +最终浏览器测试重新执行,三项仍在位图释放断言失败,证明不是等待变化掩盖缺陷。 + +最终验证: + +- 固定 Node24.21.0 / Yarn1.22.22;严格工具链检查通过。 +- 全包201项通过;实际main/legacy的运行时、生命周期、帧回调、offscreen组各69项通过。 +- 最终受控旧main16项中12通过4失败;源码修复后的对应测试通过。仅在最后添加测试 + if分支的格式花括号,不改变条件或断言;lint最终通过。 +- 正常main/legacy/ESM均正常构建,分别与docs副本核对字节;严格包TS通过,核心37份 + 生成声明无漂移。没有修改消费者声明,未把已有安装验收报告当成此次重新安装。 +- main:3项真实位图异常/恢复,以及三核心×三浏览器9项真实WASM字幕播放、seek、 + 布局、资源销毁;legacy同样12项通过。无重试、无跳过。 +- 浏览器为Windows Chromium153.0.8010.12、Firefox155.0、WebKit26.6;明确主线程 + asyncRender。原生故障注入仅candidate核心,正常播放另含两个旧核心。不是物理 + Apple/Android设备、默认offscreen、持续GPU内存压力、完整组合或远端CI验收。 + +本次是独立任务,依赖已完成的初始化/生命周期基础。当前Git基线包含09尚未全部 +验收的修复检查点;其未关闭的默认模式问题继续由09/05跟踪,不能因10通过而关闭。 +第四段vendor补丁保留前三段原指纹与可逆diff,第三方清单记录最新实际源码指纹。 +没有更换Worker/WASM/字体、依赖、版本、公开声明或发行入口。 + +验证结果和具体候选/报告指纹见 +[机器记录](../baselines/jassub-render-validation.json)。回退本任务独立提交并正常 +重建包可恢复旧行为;不要只改来源清单或只回退一份分发格式。没有push/publish。 diff --git a/refactor/plan.md b/refactor/plan.md index 450aa036d..bc7701d44 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -2,9 +2,9 @@ > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 -基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 253 项,范围 22 个包及工作区/示例。 +基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 254 项,范围 22 个包及工作区/示例。 -状态:todo 54 / doing 17 / blocked 0 / done 182 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 54 / doing 17 / blocked 0 / done 183 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -27,7 +27,7 @@ | artplayer-plugin-dash-control | 1.1.0 | PKG-DASH-01, PKG-DASH-02, PKG-DASH-03, PKG-DASH-04, PKG-DASH-05, PKG-DASH-06 | | artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 | | artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 | -| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06, PKG-JASSUB-07, PKG-JASSUB-08, PKG-JASSUB-09 | +| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06, PKG-JASSUB-07, PKG-JASSUB-08, PKG-JASSUB-09, PKG-JASSUB-10 | | artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-07, PKG-MULTI-SUB-08, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06, PKG-MULTI-SUB-09, PKG-MULTI-SUB-10, PKG-MULTI-SUB-11 | | artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06, PKG-VAST-07 | | artplayer-plugin-vtt-thumbnail | 1.1.0 | PKG-VTT-THUMB-01, PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 | @@ -243,11 +243,12 @@ | PKG-JASSUB-02 | artplayer-plugin-jassub
建立特有行为与错误测试 | PKG-JASSUB-01, ENG-03, ENG-05 | ASS 字体、时钟/seek/倍率/resize、加载失败和销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-JASSUB-03 | artplayer-plugin-jassub
整理内部职责与资源 | PKG-JASSUB-02, CORE-15, CORE-16 | 仅整理自有 adapter/销毁;保留第三方文件及来源 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done | | PKG-JASSUB-04 | artplayer-plugin-jassub
迁移自有源码和公开类型 | PKG-JASSUB-03, ENG-04, ENG-06, CORE-07 | JASSUB option/instance 的兼容类型包装,vendor JS 例外记录 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done | -| PKG-JASSUB-05 | artplayer-plugin-jassub
验证新旧核心和组合 | PKG-JASSUB-04, CORE-22, PKG-JASSUB-07, PKG-JASSUB-08, PKG-JASSUB-09 | 真实 worker/WASM 字幕渲染与全屏、旧核心测试 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | +| PKG-JASSUB-05 | artplayer-plugin-jassub
验证新旧核心和组合 | PKG-JASSUB-04, CORE-22, PKG-JASSUB-07, PKG-JASSUB-08, PKG-JASSUB-09, PKG-JASSUB-10 | 真实 worker/WASM 字幕渲染与全屏、旧核心测试 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-JASSUB-06 | artplayer-plugin-jassub
验证分发并同步文档 | PKG-JASSUB-05, ENG-07 | jassub.js、外部资源路径、许可和离线失败记录;接续 VENDOR-04/05 完整组件/字体通知、LGPL 源码分发核对及六份未明确字体的分发处置 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录;来源身份已由01冻结,完整通知和未明确字体的发布处理必须闭环,不能以来源相同替代许可结论 | H | todo | | PKG-JASSUB-07 | artplayer-plugin-jassub
修复 vendor 生命周期与字幕时钟 | PKG-JASSUB-03, PKG-JASSUB-02 | 独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界;查询 timeout/Worker error 的缺失数据回调及每请求监听器/定时器清理(JASSUB-QUERY-01) | 保持公开真实 instance、同步方法与资源路径;保留原 vendor 来源指纹并记录每项实际补丁;候选修复正例和真实字幕/失败/时钟验证通过,未证实设备不得冒充通过 | H | done | | PKG-JASSUB-08 | artplayer-plugin-jassub
修复默认 offscreen 初始化消息顺序 | PKG-JASSUB-07 | 在释放排队绘制消息前转移初始画布;独立 vendor 补丁、默认模式原生回归和维护文档 | 保留默认能力选择、Worker/资源和公开同步 API;旧红新绿消息顺序测试及真实默认 offscreen 播放/seek/全屏通过,主线程绘制保持 | H | done | | PKG-JASSUB-09 | artplayer-plugin-jassub
修复 hybrid 迟到绘制与销毁后画布重建 | PKG-JASSUB-08 | 释放旧混合绘制 bitmap,不改变新模式的画布/时钟;销毁后切字幕不重新分配画布;来源补丁、原生排队消息及维护记录 | 旧红新绿验证旧帧不能重建画布/解除新绘制等待;正常 hybrid 绘制保持;真实 Worker/ImageBitmap 交错、主线程及默认模式回归通过,能力缺口明确 | H | doing | +| PKG-JASSUB-10 | artplayer-plugin-jassub
释放绘制异常后的整批 ImageBitmap | PKG-JASSUB-08 | 主线程异步绘制异常时释放整批位图;同步路径保持;原生异常/恢复测试和可逆vendor补丁记录 | 旧红新绿验证清屏、尺寸和首/后续绘制异常的释放及原异常传播;真实Worker位图和原生Canvas异常后恢复字幕;源码/main/legacy回归、类型、来源及构建通过 | M | done | ## 5 包迁移:artplayer-plugin-danmuku-mask @@ -658,3 +659,4 @@ - PKG-MULTI-SUB-11: [记录](changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md) [记录](baselines/multiple-subtitles-ass-validation.json) - PKG-AUTO-THUMB-11: [记录](changes/2026-09-14-PKG-AUTO-THUMB-11-handler-cleanup.md) [记录](baselines/auto-thumbnail-handler-validation.json) - PKG-VAST-07: [记录](changes/2026-09-14-PKG-VAST-07-default-alias.md) [记录](baselines/vast-alias-validation.json) +- PKG-JASSUB-10: [记录](changes/2026-09-14-PKG-JASSUB-10-render-resources.md) [记录](baselines/jassub-render-validation.json) [记录](baselines/jassub-render-patch.json) diff --git a/refactor/progress.md b/refactor/progress.md index a02495f61..bbe6ce230 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,20 @@ # 进度与证据 +## PKG-JASSUB-10 绘制异常后的位图释放完成 + +主线程asyncRender在清屏、调整尺寸或绘制抛错时,现在通过finally释放整批收到的 +ImageBitmap,保留原生异常及同步缓冲区路径。冻结旧main四项受控释放回归失败, +三浏览器以真实Worker/WASM位图及原生副本注入InvalidStateError也复现剩余位图 +未释放。候选全包201项通过,main/legacy运行时组各69项;两种产物各12项真实 +主线程字幕播放/seek/布局/销毁和异常恢复通过。修正测试的busy/force调用顺序后, +旧产物用最终测试仍三项失败;未修改生产调度或延长超时。 +见[变更](changes/2026-09-14-PKG-JASSUB-10-render-resources.md)及 +[证据](baselines/jassub-render-validation.json)。四段vendor补丁来源链、三种构建及 +docs副本、严格TS、37份核心声明无漂移、定向lint和工具链检查通过。 +254项:183 done、17 doing、54 todo。09/05及Firefox默认offscreen停顿保持未关闭; +此项不代表JASSUB整体、设备、远端CI或发布验收完成。下一步继续剩余包验收和 +原生绘制问题定位;不把本次主线程资源修复当作默认绘制停顿的解决方案。 + ## REL-08 逐包发布准入台账完成 建立22包历史分发、独立major目标、能力/回退和候选证据登记。新增纯模型及只读 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index c0bb737c6..b73745313 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -268,3 +268,4 @@ | MULTI-SUB-OFFSET-01 | resolved / 已复现 | Paused Firefox subtitle offset leaves out-of-time native cues active and captions visible | PKG-MULTI-SUB-05, CORE-SUBTITLE-OFFSET-01 | | MULTI-SUB-SWITCH-01 | open / 已复现 | Intermittent WebKit core5.3.0 caption loss after selected-track source switch | PKG-MULTI-SUB-05, PKG-MULTI-SUB-09 | | MULTI-SUB-ASS-01 | resolved / 已复现 | Published core5.1.2 ASS converter loses required VTT line breaks | PKG-MULTI-SUB-11 | +| JASSUB-RENDER-RESOURCES-01 | resolved / 已复现 | Main-thread async rendering leaves received ImageBitmaps open when native canvas operations throw | PKG-JASSUB-10 | diff --git a/refactor/risks.json b/refactor/risks.json index 828a76954..fa4ac7112 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "task": "BASE-07", "sourceCommit": "e01f39ff", - "updatedAt": "2026-09-14T06:00:35.506Z", + "updatedAt": "2026-09-14T07:23:15.393Z", "note": "Open means closure evidence is outstanding; source-observed and unverified are not reproduced runtime bugs. A source issue already mitigated in the workspace still requires candidate regression evidence. No accepted-with-scope item is implied by baseline task completion.", "items": [ { @@ -6102,6 +6102,31 @@ "refactor/baselines/multiple-subtitles-ass-validation.json" ], "resolutionRationale": "Frozen main output fails before repair while same-version legacy/source remain correct. Exact-output repair preserves valid/custom output and exception ownership.315 package checks, actual main/legacy unit cases and source/main/legacy each21 native desktop cases prove this specific conversion defect fixed, without claiming full ASS layout, device or source-seek acceptance." + }, + { + "id": "JASSUB-RENDER-RESOURCES-01", + "title": "Main-thread async rendering leaves received ImageBitmaps open when native canvas operations throw", + "confirmation": "reproduced", + "status": "resolved", + "owners": [ + "PKG-JASSUB-10" + ], + "evidence": [ + "test/jassub-offscreen.test.js", + "test/browser/jassub-render-failure.spec.js", + "refactor/changes/2026-09-14-PKG-JASSUB-10-render-resources.md", + "refactor/baselines/jassub-render-validation.json" + ], + "compatibleResolution": "Release all owned async bitmaps in finally, preserving the original native exception, ordinary draw order and synchronous pixel buffer behavior.", + "closureCriteria": "Controlled clear/resize/first/later draw failures and actual native Canvas failure release all bitmaps; normal rendering, subsequent subtitle recovery, source/built regressions, provenance and strict types pass.", + "workspaceState": "Final main/legacy release all owned bitmaps on native drawing failure; normal main-thread subtitle playback and explicit redraw recovery pass on three desktop engines. Firefox offscreen stall remains separate.", + "resolutionEvidence": [ + "refactor/baselines/jassub-render-validation.json", + "refactor/changes/2026-09-14-PKG-JASSUB-10-render-resources.md", + "test/jassub-offscreen.test.js", + "test/browser/jassub-render-failure.spec.js" + ], + "resolutionRationale": "Old exact main fails all four controlled disposal cases and all three native batch-disposal cases. Candidate full suite passes 201 cases; actual main/legacy runtime groups each pass 69 cases and each has 12 native failure/recovery/playback cases. The fix changes only async bitmap release on main-thread render and retains exceptions and synchronous buffers." } ] } diff --git a/refactor/scripts/jassub-vendor-patch.test.mjs b/refactor/scripts/jassub-vendor-patch.test.mjs index 0e629e0e1..fef469633 100644 --- a/refactor/scripts/jassub-vendor-patch.test.mjs +++ b/refactor/scripts/jassub-vendor-patch.test.mjs @@ -6,7 +6,7 @@ import test from 'node:test' import { hash } from './releases.mjs' test('JASSUB local vendor patch retains its exact original, changed source and reversible patch identity', () => { - const records = ['vendor', 'offscreen', 'hybrid'].map(name => JSON.parse(fs.readFileSync(`refactor/baselines/jassub-${name}-patch.json`, 'utf8'))) + const records = ['vendor', 'offscreen', 'hybrid', 'render'].map(name => JSON.parse(fs.readFileSync(`refactor/baselines/jassub-${name}-patch.json`, 'utf8'))) for (const [index, record] of records.entries()) { const next = records[index + 1] const original = execFileSync('git', ['show', `${record.baselineCommit}:${record.file}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n') diff --git a/refactor/tasks.json b/refactor/tasks.json index 4f86eefc8..c093fcc22 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -2548,7 +2548,8 @@ "CORE-22", "PKG-JASSUB-07", "PKG-JASSUB-08", - "PKG-JASSUB-09" + "PKG-JASSUB-09", + "PKG-JASSUB-10" ], "status": "todo", "risk": "H", @@ -5272,6 +5273,26 @@ "changes/2026-09-14-PKG-VAST-07-default-alias.md", "baselines/vast-alias-validation.json" ] + }, + { + "id": "PKG-JASSUB-10", + "phase": "5 包迁移:artplayer-plugin-jassub", + "title": "释放绘制异常后的整批 ImageBitmap", + "scope": [ + "artplayer-plugin-jassub" + ], + "dependsOn": [ + "PKG-JASSUB-08" + ], + "status": "done", + "risk": "M", + "deliverable": "主线程异步绘制异常时释放整批位图;同步路径保持;原生异常/恢复测试和可逆vendor补丁记录", + "acceptance": "旧红新绿验证清屏、尺寸和首/后续绘制异常的释放及原异常传播;真实Worker位图和原生Canvas异常后恢复字幕;源码/main/legacy回归、类型、来源及构建通过", + "evidence": [ + "changes/2026-09-14-PKG-JASSUB-10-render-resources.md", + "baselines/jassub-render-validation.json", + "baselines/jassub-render-patch.json" + ] } ] } diff --git a/refactor/third-party.json b/refactor/third-party.json index 9f68a9f23..a11585ae1 100644 --- a/refactor/third-party.json +++ b/refactor/third-party.json @@ -388,7 +388,7 @@ { "path": "packages/artplayer-plugin-jassub/src/jassub.es.js", "algorithm": "sha256-lf", - "sha256": "60590f2fca6ffded74f96f8a6001a86c32eb38f5e1fae26338cbbb43e93fb70a" + "sha256": "ebabca473edfbe7f9768dba25b2c2403f2c9c5dec64292fa008d8eb758e32696" }, { "path": "packages/artplayer-plugin-jassub/worker/jassub-worker-modern.wasm", @@ -414,7 +414,8 @@ "followUp": "refactor/baselines/jassub-offscreen-patch.json", "followUps": [ "refactor/baselines/jassub-offscreen-patch.json", - "refactor/baselines/jassub-hybrid-patch.json" + "refactor/baselines/jassub-hybrid-patch.json", + "refactor/baselines/jassub-render-patch.json" ] } }, diff --git a/test/browser/README.md b/test/browser/README.md index d5c053b3f..cd2b5dc1d 100644 --- a/test/browser/README.md +++ b/test/browser/README.md @@ -216,6 +216,13 @@ asserts it remains connected after destroy(false). The native report records the artifact hash and both mode choices. This does not change the default published baseline or establish ESM/offscreen/device acceptance. +`jassub-render-failure.spec.js` uses an actual Worker/WASM bitmap and native copies. +It intentionally closes one bitmap to provoke native Canvas InvalidStateError, +checks release of the entire batch, then pauses and waits for pending demand before +public resize(force) verifies a visible subtitle again. Select actual main/legacy +with ARTPLAYER_JASSUB_ARTIFACT. This main-thread fault injection does not reproduce +spontaneous Worker corruption or resolve the separate Firefox offscreen stall. + JASSUB Firefox diagnostics retain the canonical failing cases. Set `ARTPLAYER_JASSUB_OFFSCREEN=default` to exercise native capability selection. `ARTPLAYER_JASSUB_ASYNC_RENDER=false` explicitly selects synchronous rendering; diff --git a/test/browser/jassub-render-failure.spec.js b/test/browser/jassub-render-failure.spec.js new file mode 100644 index 000000000..8b5d7b441 --- /dev/null +++ b/test/browser/jassub-render-failure.spec.js @@ -0,0 +1,108 @@ +import fs from 'node:fs' +import process from 'node:process' +import { hash } from '../../refactor/scripts/releases.mjs' +import { expect, test } from './fixtures.js' + +const artifact = process.env.ARTPLAYER_JASSUB_ARTIFACT || 'packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js' +const code = fs.readFileSync(artifact, 'utf8') +const subtitle = `[Script Info] +ScriptType: v4.00+ +PlayResX: 640 +PlayResY: 360 +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Liberation Sans,40,&H0000FF00,&H00000000,&H00000000,0,0,1,0,0,2,10,10,10,1 +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.00,0:02:00.00,Default,,0,0,0,,Resource recovery subtitle +` + +test('JASSUB closes the entire bitmap batch after a native draw failure and renders the next frame', async ({ page }, testInfo) => { + await page.goto('/test/player.html?core=candidate') + await page.addScriptTag({ content: code }) + await page.evaluate(() => window.createPlayer('/assets/sample/steve-jobs.mp4')) + await page.locator('#play').click() + try { + await page.evaluate((subtitle) => { + const register = window.artplayerPluginJassub.default || window.artplayerPluginJassub + window.art.plugins.add(register({ + offscreenRender: false, + asyncRender: true, + workerUrl: '/assets/jassub/jassub-worker.js', + wasmUrl: '/assets/jassub/jassub-worker.wasm', + modernWasmUrl: '/assets/jassub/jassub-worker-modern.wasm', + availableFonts: { 'liberation sans': '/assets/jassub/default.woff2' }, + fonts: ['/assets/jassub/default.woff2'], + subContent: subtitle, + })) + const { instance } = window.art.plugins.artplayerPluginJassub + const probe = window.bitmapFailureProbe = { instance, held: [], holding: true, errors: [] } + instance.addEventListener('error', event => probe.errors.push(String(event.error))) + probe.dispatch = instance._worker.onmessage + instance._worker.onmessage = (event) => { + if (probe.holding && event.data.target === 'render' && event.data.images.some(item => item.image instanceof ImageBitmap)) + probe.held.push(event) + else + probe.dispatch(event) + } + }, subtitle) + await expect.poll(() => page.evaluate(() => window.bitmapFailureProbe.held.length)).toBeGreaterThan(0) + const evidence = await page.evaluate(async () => { + const probe = window.bitmapFailureProbe + window.art.video.pause() + const event = probe.held.shift() + const first = event.data.images.find(item => item.image instanceof ImageBitmap) + const copies = await Promise.all([createImageBitmap(first.image), createImageBitmap(first.image)]) + for (const item of event.data.images) { + if (item !== first) + item.image?.close?.() + } + event.data.images = [first, ...copies.map(image => ({ image, x: 0, y: 0 }))] + const before = event.data.images.map(item => item.image.width) + first.image.close() + let failure + try { + probe.dispatch(event) + } + catch (error) { failure = { name: error.name, message: error.message } } + const after = event.data.images.map(item => item.image.width) + // Clean the failing historical candidate too, after recording its resource state. + for (const item of event.data.images) + item.image.close() + for (const held of probe.held.splice(0)) { + for (const item of held.data.images) + item.image?.close?.() + } + probe.holding = false + return { before, after, failure, asyncRender: event.data.asyncRender } + }) + await testInfo.attach('bitmap-failure', { contentType: 'application/json', body: JSON.stringify({ artifact, sha256: hash(code), evidence, limitation: 'Actual JASSUB Worker/WASM bitmap, plus two native bitmap copies. Deliberately closing the first bitmap injects a native draw error; this does not claim spontaneous Worker corruption or Firefox offscreen recovery.' }) }) + expect(evidence.before.every(width => width > 0)).toBe(true) + expect(evidence.asyncRender).toBe(true) + expect(evidence.failure?.name).toBe('InvalidStateError') + expect(evidence.after).toEqual([0, 0, 0]) + // A busy renderer intentionally ignores resize(force); let its pending demand finish first. + await expect.poll(() => page.evaluate(() => window.bitmapFailureProbe.instance.busy)).toBe(false) + await page.evaluate(() => window.bitmapFailureProbe.instance.resize(0, 0, 0, 0, true)) + await expect.poll(() => page.evaluate(() => { + const { instance } = window.bitmapFailureProbe + const pixels = instance._ctx.getImageData(0, 0, instance._canvas.width, instance._canvas.height).data + let green = 0 + for (let i = 0; i < pixels.length; i += 4) { + if (pixels[i + 1] > 150 && pixels[i] < 100 && pixels[i + 2] < 100 && pixels[i + 3] > 100) + green++ + } + return green + })).toBeGreaterThan(100) + expect(await page.evaluate(() => window.bitmapFailureProbe.errors)).toEqual([]) + } + finally { + await page.evaluate(() => { + for (const event of window.bitmapFailureProbe?.held || []) { + for (const item of event.data.images) + item.image?.close?.() + } + window.art?.destroy(false) + }) + } +}) diff --git a/test/jassub-offscreen.test.js b/test/jassub-offscreen.test.js index e989c12c3..4dddd6dac 100644 --- a/test/jassub-offscreen.test.js +++ b/test/jassub-offscreen.test.js @@ -144,3 +144,54 @@ for (const method of ['setTrack', 'setTrackByUrl']) { assert.equal(env.workers[0].terminated, 1) }) } + +for (const phase of ['clear', 'first-image', 'second-image', 'resize']) { + test(`JASSUB releases every received bitmap when rendering fails during ${phase}`, async () => { + const env = environment(false) + const { instance } = env.factory({ offscreenRender: false })(env.art) + await env.ready() + const failure = new Error(`render ${phase}`) + const closed = [0, 0, 0] + const images = closed.map((_, index) => ({ + image: { close() { closed[index]++ } }, + x: index, + y: 0, + })) + let draws = 0 + instance._ctx.drawImage = () => { + draws++ + if (draws === (phase === 'first-image' ? 1 : phase === 'second-image' ? 2 : 0)) + throw failure + } + if (phase === 'clear') + instance._ctx.clearRect = () => { throw failure } + if (phase === 'resize') { + Object.defineProperty(instance._canvasctrl, 'width', { + configurable: true, + get() { return 300 }, + set() { throw failure }, + }) + } + try { + assert.throws(() => env.workers[0].onmessage({ data: { target: 'render', images, asyncRender: true, width: 640, height: 360 } }), error => error === failure) + assert.deepEqual(closed, [1, 1, 1]) + assert.equal(draws, phase === 'first-image' ? 1 : phase === 'second-image' ? 2 : 0) + } + finally { + instance.destroy() + } + }) +} + +test('JASSUB synchronous pixel buffers are drawn without bitmap disposal', async () => { + const env = environment(false) + const { instance } = env.factory({ offscreenRender: false })(env.art) + await env.ready() + const image = new Uint8ClampedArray([0, 255, 0, 255]).buffer + image.close = () => assert.fail('Synchronous buffers are not owned ImageBitmaps') + const draws = [] + instance._ctx.drawImage = (...args) => draws.push(args) + env.workers[0].onmessage({ data: { target: 'render', images: [{ image, x: 2, y: 3, w: 1, h: 1 }], asyncRender: false, width: 640, height: 360 } }) + assert.deepEqual(draws, [[instance._bufferCanvas, 2, 3]]) + instance.destroy() +})