From 0f3c068aae1415c2d40ede8708f02f4c5864fe7a Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 02:17:24 +0800 Subject: [PATCH] fix(jassub): [PKG-JASSUB-07] repair vendor lifecycle and subtitle frame clock --- docs/compiled/artplayer-plugin-jassub.js | 2 +- .../artplayer-plugin-jassub.legacy.js | 2 +- docs/compiled/artplayer-plugin-jassub.mjs | 315 +- package.json | 4 +- .../artplayer-plugin-jassub/ARCHITECTURE.md | 62 +- .../dist/artplayer-plugin-jassub.js | 2 +- .../dist/artplayer-plugin-jassub.legacy.js | 2 +- .../dist/artplayer-plugin-jassub.mjs | 315 +- .../artplayer-plugin-jassub/src/jassub.es.js | 228 +- .../artplayer-plugin-jassub/types/README.md | 19 +- .../types/runtime-api.d.ts | 11 +- refactor/baselines/jassub-vendor-patch.json | 19 + .../baselines/jassub-vendor-validation.json | 11831 ++++++++++++++++ refactor/baselines/jassub-vendor.patch | 373 + .../2026-09-14-PKG-JASSUB-07-vendor.md | 115 + refactor/plan.md | 5 +- refactor/progress.md | 12 + refactor/risk-table.md | 12 +- refactor/risks.json | 72 +- refactor/scripts/jassub-vendor-patch.test.mjs | 22 + refactor/tasks.json | 8 +- refactor/third-party.json | 15 +- test/browser/jassub-lifecycle.spec.js | 190 + test/browser/jassub-native.spec.js | 22 +- test/helpers/jassub.js | 1 + test/jassub-frame-polyfill.test.js | 364 + test/jassub-vendor-lifecycle.test.js | 302 + test/jassub.test.js | 4 +- test/types/jassub-runtime.ts | 2 +- 29 files changed, 14064 insertions(+), 267 deletions(-) create mode 100644 refactor/baselines/jassub-vendor-patch.json create mode 100644 refactor/baselines/jassub-vendor-validation.json create mode 100644 refactor/baselines/jassub-vendor.patch create mode 100644 refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md create mode 100644 refactor/scripts/jassub-vendor-patch.test.mjs create mode 100644 test/browser/jassub-lifecycle.spec.js create mode 100644 test/jassub-frame-polyfill.test.js create mode 100644 test/jassub-vendor-lifecycle.test.js diff --git a/docs/compiled/artplayer-plugin-jassub.js b/docs/compiled/artplayer-plugin-jassub.js index c882d2acb..a8c1975b5 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._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){const t=performance.now(),s=this.getVideoPlaybackQuality(),i=this.mozPresentedFrames||this.mozPaintedFrames||s.totalVideoFrames-s.droppedVideoFrames,a=(r,n)=>{const o=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(h>i){const i=this.mozFrameDelay||o.totalFrameDelay-s.totalFrameDelay||0,a=n-r;e(n,{presentationTime:n+1e3*i,expectedDisplayTime:n+a,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:h,processingDuration:i}),delete this._rvfcpolyfillmap[t]}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(n,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(t,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){cancelAnimationFrame(this._rvfcpolyfillmap[e]),delete this._rvfcpolyfillmap[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){if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");this._loaded=new Promise(e=>{this._init=e});const s=e._test();if(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.setRate.bind(this),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._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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_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((!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,videoHeight:this._videoHeight||this._video.videoHeight,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}){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){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s,colorSpace:this._videoColorSpace})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}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._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:i}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:i}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:i})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=s[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,i=setTimeout(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),a=({data:e})=>{e.target===s&&(t(null,e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i))},r=e=>{t(e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i)};this._worker.addEventListener("message",a),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){return e&&(e=this._error(e)),this._video&&this._canvasParent&&this._video.parentNode?.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker?.terminate(),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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]))}).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._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){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}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(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 f5c40ceed..b5145e496 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._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){const t=performance.now(),s=this.getVideoPlaybackQuality(),i=this.mozPresentedFrames||this.mozPaintedFrames||s.totalVideoFrames-s.droppedVideoFrames,a=(r,n)=>{const o=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(h>i){const i=this.mozFrameDelay||o.totalFrameDelay-s.totalFrameDelay||0,a=n-r;e(n,{presentationTime:n+1e3*i,expectedDisplayTime:n+a,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:h,processingDuration:i}),delete this._rvfcpolyfillmap[t]}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(n,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(t,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){cancelAnimationFrame(this._rvfcpolyfillmap[e]),delete this._rvfcpolyfillmap[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;if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");this._loaded=new Promise(e=>{this._init=e});const a=e._test();if(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.setRate.bind(this),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._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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_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)()){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,videoHeight:this._videoHeight||this._video.videoHeight,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}){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){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s,colorSpace:this._videoColorSpace})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}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._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:i}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:i}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:i})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=h[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}sendMessage(e){return o(this,arguments,function*(e,t={},s){yield this._loaded,s?this._worker.postMessage(r({target:e,transferable:s},t),[...s]):this._worker.postMessage(r({target:e},t))})}_fetchFromWorker(e,t){try{const s=e.target,i=setTimeout(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),a=({data:e})=>{e.target===s&&(t(null,e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i))},r=e=>{t(e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i)};this._worker.addEventListener("message",a),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){var t,s;return e&&(e=this._error(e)),this._video&&this._canvasParent&&(null==(t=this._video.parentNode)||t.removeChild(this._canvasParent)),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),null==(s=this._worker)||s.terminate(),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 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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]))}).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._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){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}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(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)}}); diff --git a/docs/compiled/artplayer-plugin-jassub.mjs b/docs/compiled/artplayer-plugin-jassub.mjs index 24386bec2..e7f96d74a 100644 --- a/docs/compiled/artplayer-plugin-jassub.mjs +++ b/docs/compiled/artplayer-plugin-jassub.mjs @@ -7,27 +7,46 @@ var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); -typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function(c) { - const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames, r = (n, a) => { - const i = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || i.totalVideoFrames - i.droppedVideoFrames; - if (h > s) { - const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l = a - n; - c(a, { - presentationTime: a + d * 1e3, - expectedDisplayTime: a + l, - width: this.videoWidth, - height: this.videoHeight, - mediaTime: Math.max(0, this.currentTime || 0) + l / 1e3, - presentedFrames: h, - processingDuration: d - }), delete this._rvfcpolyfillmap[e]; - } else - this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => r(a, d)); +if (typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype) { + HTMLVideoElement.prototype.requestVideoFrameCallback = function(callback) { + if (!Object.prototype.hasOwnProperty.call(this, "_rvfcpolyfillmap")) + this._rvfcpolyfillmap = {}; + const id = this._rvfcpolyfillid = (this._rvfcpolyfillid || 0) + 1; + const started = performance.now(), initial = this.getVideoPlaybackQuality(), initialTime = this.currentTime; + const initialFrames = this.mozPresentedFrames || this.mozPaintedFrames || initial.totalVideoFrames - initial.droppedVideoFrames; + const poll = (previous, now) => { + if (!(id in this._rvfcpolyfillmap)) return; + const quality = this.getVideoPlaybackQuality(); + const frames = this.mozPresentedFrames || this.mozPaintedFrames || quality.totalVideoFrames - quality.droppedVideoFrames; + const advanced = frames > initialFrames; + const mediaFallback = initialFrames === 0 && frames === 0 && initial.totalVideoFrames === 0 && initial.droppedVideoFrames === 0 && quality.totalVideoFrames === 0 && quality.droppedVideoFrames === 0 && Number.isFinite(this.currentTime) && this.currentTime !== initialTime && this.readyState >= 2 && !this.seeking && now - started >= 1e3 / 30; + if (advanced || mediaFallback) { + const delay = this.mozFrameDelay || quality.totalFrameDelay - initial.totalFrameDelay || 0; + const elapsed = now - previous; + delete this._rvfcpolyfillmap[id]; + callback(now, { + presentationTime: now + delay * 1e3, + expectedDisplayTime: now + elapsed, + width: this.videoWidth, + height: this.videoHeight, + mediaTime: mediaFallback && !advanced ? Math.max(0, this.currentTime) : Math.max(0, this.currentTime || 0) + elapsed / 1e3, + presentedFrames: frames, + processingDuration: delay + }); + } else { + this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(now, time)); + } + }; + this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(started, time)); + return id; }; - return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) => r(e, n)), e; -}, HTMLVideoElement.prototype.cancelVideoFrameCallback = function(c) { - cancelAnimationFrame(this._rvfcpolyfillmap[c]), delete this._rvfcpolyfillmap[c]; -}); + HTMLVideoElement.prototype.cancelVideoFrameCallback = function(id) { + const callbacks = this._rvfcpolyfillmap; + if (!callbacks || !(id in callbacks)) return; + cancelAnimationFrame(callbacks[id]); + delete callbacks[id]; + }; +} const _ = { bt709: "BT709", // these might not be exactly correct? oops? @@ -82,47 +101,65 @@ const _o = class _o extends EventTarget { * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate). */ constructor(e) { - if (super(), !globalThis.Worker) - throw this.destroy("Worker not supported"); - if (!e) - throw this.destroy("No options provided"); - this._loaded = /** @type {Promise} */ - new Promise((s) => { - this._init = s; - }); - const t = _o._test(); - if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? true), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? true), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas) - this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._canvas = 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 = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = false, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { - this._worker.postMessage({ - target: "init", - wasmUrl: _o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm", - legacyWasmUrl: e.legacyWasmUrl ?? "jassub-worker.wasm.js", - asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? true), - onDemandRender: this._onDemandRender, - width: this._canvasctrl.width || 0, - height: this._canvasctrl.height || 0, - blendMode: e.blendMode || "js", - subUrl: e.subUrl, - subContent: e.subContent || null, - fonts: e.fonts || [], - availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" }, - fallbackFont: e.fallbackFont || "liberation sans", - debug: this.debug, - targetFps: e.targetFps || 24, - dropAllAnimations: e.dropAllAnimations, - dropAllBlur: e.dropAllBlur, - libassMemoryLimit: e.libassMemoryLimit || 0, - libassGlyphLimit: e.libassGlyphLimit || 0, - // @ts-ignore - useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? true), - hasBitmapBug: _o._hasBitmapBug - }), this._offscreenRender === true && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]); - }); + super(); + this._requests = /* @__PURE__ */ new Set(); + let t; + try { + if (!globalThis.Worker) + throw this.destroy("Worker not supported"); + if (!e) + throw this.destroy("No options provided"); + this._loaded = /** @type {Promise} */ + new Promise((s) => { + this._init = s; + }); + t = _o._test(); + if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? true), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? true), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas) + this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._canvas = 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 = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = () => { + if (!this._destroyed) this.setRate(this._video.playbackRate); + }, this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = false, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { + if (this._destroyed) return; + this._worker.postMessage({ + target: "init", + wasmUrl: _o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm", + legacyWasmUrl: e.legacyWasmUrl ?? "jassub-worker.wasm.js", + asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? true), + onDemandRender: this._onDemandRender, + width: this._canvasctrl.width || 0, + height: this._canvasctrl.height || 0, + blendMode: e.blendMode || "js", + subUrl: e.subUrl, + subContent: e.subContent || null, + fonts: e.fonts || [], + availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" }, + fallbackFont: e.fallbackFont || "liberation sans", + debug: this.debug, + targetFps: e.targetFps || 24, + dropAllAnimations: e.dropAllAnimations, + dropAllBlur: e.dropAllBlur, + libassMemoryLimit: e.libassMemoryLimit || 0, + libassGlyphLimit: e.libassGlyphLimit || 0, + // @ts-ignore + useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? true), + hasBitmapBug: _o._hasBitmapBug + }), this._offscreenRender === true && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]); + }).catch((error) => { + if (!this._destroyed) this.destroy(error); + }); + } catch (error) { + t?.catch(() => { + }); + try { + this.destroy(); + } catch { + } + throw error; + } } _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; @@ -183,6 +220,7 @@ const _o = class _o extends EventTarget { * @param {Boolean} [force=false] */ resize(e = 0, t = 0, s = 0, r = 0, n = this._video?.paused) { + if (this._destroyed) return; if ((!e || !t) && this._video) { const a = this._getVideoPosition(); let i = null; @@ -193,7 +231,7 @@ const _o = class _o extends EventTarget { i = this._computeCanvasSize(a.width || 0, a.height || 0); e = i.width, t = i.height, this._canvasParent && (s = a.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), r = 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 = r + "px", n && this.busy === false ? this.busy = true : n = false, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video.videoWidth, videoHeight: this._videoHeight || this._video.videoHeight, force: n }); + this._canvas.style.top = s + "px", this._canvas.style.left = r + "px", n && this.busy === false ? this.busy = true : n = false, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video?.videoWidth || e, videoHeight: this._videoHeight || this._video?.videoHeight || t, force: n }); } _getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) { const s = e / t, { offsetWidth: r, offsetHeight: n } = this._video, a = r / n; @@ -213,6 +251,7 @@ const _o = class _o extends EventTarget { return { width: e, height: t }; } _timeupdate({ type: e }) { + if (this._destroyed) return; const s = { seeking: true, waiting: true, @@ -225,7 +264,34 @@ const _o = class _o extends EventTarget { * @param {HTMLVideoElement} video */ setVideo(e) { - e instanceof HTMLVideoElement ? (this._removeListeners(), this._video = e, this._onDemandRender ? this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)) : (this._playstate = e.paused, e.addEventListener("timeupdate", this._boundTimeUpdate, false), e.addEventListener("progress", this._boundTimeUpdate, false), e.addEventListener("waiting", this._boundTimeUpdate, false), e.addEventListener("seeking", this._boundTimeUpdate, false), e.addEventListener("playing", this._boundTimeUpdate, false), e.addEventListener("ratechange", this._boundSetRate, false), e.addEventListener("resize", this._boundResize, false)), "VideoFrame" in window && (e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, false), e.readyState > 2 && this._updateColorSpace()), e.videoWidth > 0 && this.resize(), typeof ResizeObserver < "u" && (this._ro || (this._ro = new ResizeObserver(() => this.resize())), this._ro.observe(e))) : this._error("Video element invalid!"); + if (this._destroyed) return; + if (!(e instanceof HTMLVideoElement)) { + this._error("Video element invalid!"); + return; + } + this._removeListeners(); + this._video = e; + this._videoWidth = this._videoHeight = 0; + this._videoColorSpace = null; + if (this._canvasParent && this._canvasParent.parentNode !== e.parentNode) + e.insertAdjacentElement("afterend", this._canvasParent); + if (this._onDemandRender) this._requestFrame(); + else { + this._playstate = e.paused; + for (const name of ["timeupdate", "progress", "waiting", "seeking", "playing"]) + e.addEventListener(name, this._boundTimeUpdate, false); + e.addEventListener("ratechange", this._boundSetRate, false); + e.addEventListener("resize", this._boundResize, false); + } + if ("VideoFrame" in window) { + e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, false); + if (e.readyState > 2) this._updateColorSpace(); + } + if (e.videoWidth > 0) this.resize(); + if (typeof ResizeObserver < "u") { + this._ro || (this._ro = new ResizeObserver(() => this.resize())); + this._ro.observe(e); + } } runBenchmark() { this.sendMessage("runBenchmark"); @@ -317,7 +383,7 @@ const _o = class _o extends EventTarget { getEvents(e) { this._fetchFromWorker({ target: "getEvents" - }, (t, { events: s }) => { + }, (t, { events: s } = {}) => { e(t, s); }); } @@ -392,7 +458,7 @@ const _o = class _o extends EventTarget { getStyles(e) { this._fetchFromWorker({ target: "getStyles" - }, (t, { styles: s }) => { + }, (t, { styles: s } = {}) => { e(t, s); }); } @@ -434,12 +500,22 @@ const _o = class _o extends EventTarget { } } _unbusy() { + if (this._destroyed) return; this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = false; } _handleRVFC(e, { mediaTime: t, width: s, height: r }) { if (this._destroyed) return null; - this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = true, this._demandRender({ mediaTime: t, width: s, height: r })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)); + this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = true, this._demandRender({ mediaTime: t, width: s, height: r })), this._requestFrame(); + } + _requestFrame() { + if (this._destroyed || !this._video || this._frameCallback != null) return; + const video = this._video, generation = this._videoGeneration; + this._frameCallback = video.requestVideoFrameCallback((time, metadata) => { + if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; + this._frameCallback = null; + this._handleRVFC(time, metadata); + }); } _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 }); @@ -457,12 +533,20 @@ const _o = class _o extends EventTarget { this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas.transferControlToOffscreen(), this._ctx = false, this.sendMessage("offscreenCanvas", null, [this._canvasctrl]), this.resize(0, 0, 0, 0, true); } _updateColorSpace() { - this._video.requestVideoFrameCallback(() => { + if (this._destroyed || this._colorCallback != null) return; + const video = this._video, generation = this._videoGeneration; + this._colorCallback = video.requestVideoFrameCallback(() => { + if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; + this._colorCallback = null; + let frame; try { - const e = new VideoFrame(this._video); - this._videoColorSpace = _[e.colorSpace.matrix], e.close(), this.sendMessage("getColorSpace"); + frame = new VideoFrame(video); + this._videoColorSpace = _[frame.colorSpace.matrix]; + this.sendMessage("getColorSpace"); } catch (e) { console.warn(e); + } finally { + frame?.close(); } }); } @@ -496,6 +580,7 @@ const _o = class _o extends EventTarget { return e; } _ready() { + if (this._destroyed) return; this._init(), this.dispatchEvent(new CustomEvent("ready")); } /** @@ -505,7 +590,10 @@ const _o = class _o extends EventTarget { * @param {Transferable[]} [transferable] Array of transferables. */ async sendMessage(e, t = {}, s) { - await this._loaded, s ? this._worker.postMessage({ + if (this._destroyed) return; + await this._loaded; + if (this._destroyed) return; + s ? this._worker.postMessage({ target: e, transferable: s, ...t @@ -515,23 +603,53 @@ const _o = class _o extends EventTarget { }); } _fetchFromWorker(e, t) { + if (this._destroyed) { + t(new Error("JASSUB instance destroyed")); + return; + } + const worker = this._worker; + let timer, settled = false; + const cleanup = () => { + worker.removeEventListener("message", onMessage); + worker.removeEventListener("error", onError); + clearTimeout(timer); + this._requests.delete(cancel); + }; + const finish = (error, data) => { + if (settled) return; + settled = true; + cleanup(); + t(error, data); + }; + const onMessage = ({ data }) => { + if (data.target === e.target) finish(null, data); + }; + const onError = (error) => finish(error); + const cancel = () => { + if (settled) return; + settled = true; + cleanup(); + return () => t(new Error("JASSUB instance destroyed")); + }; try { - const s = e.target, r = setTimeout(() => { - a(new Error("Error: Timeout while try to fetch " + s)); - }, 5e3), n = ({ data: i }) => { - i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r)); - }, a = (i) => { - t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r); - }; - this._worker.addEventListener("message", n), this._worker.addEventListener("error", a), this._worker.postMessage(e); + this._requests.add(cancel); + worker.addEventListener("message", onMessage); + worker.addEventListener("error", onError); + timer = setTimeout(() => onError(new Error("Error: Timeout while try to fetch " + e.target)), 5e3); + worker.postMessage(e); } catch (s) { - this._error(s); + if (settled) throw s; + finish(s); } } _console({ content: e, command: t }) { console[t].apply(console, JSON.parse(e)); } _onmessage({ data: e }) { + if (this._destroyed) { + for (const image of e.images || []) image.image?.close?.(); + return; + } this["_" + e.target] && this["_" + e.target](e); } _error(e) { @@ -539,6 +657,12 @@ const _o = class _o extends EventTarget { return this.dispatchEvent(s), console.error(t), t; } _removeListeners() { + this._videoGeneration = (this._videoGeneration || 0) + 1; + for (const id of [this._frameCallback, this._colorCallback]) + if (id != null) this._video?.cancelVideoFrameCallback?.(id); + this._frameCallback = this._colorCallback = null; + this._lastDemandTime = null; + if (this._onDemandRender) this.busy = false; 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)); } /** @@ -546,7 +670,38 @@ const _o = class _o extends EventTarget { * @param {String|Error} [err] Error to throw when destroying. */ destroy(e) { - return e && (e = this._error(e)), this._video && this._canvasParent && this._video.parentNode?.removeChild(this._canvasParent), this._destroyed = true, this._removeListeners(), this.sendMessage("destroy"), this._worker?.terminate(), e; + if (this._destroyed) return e && this._error(e); + this._destroyed = true; + let failure, failed = false; + const attempt = (callback) => { + try { + callback(); + } catch (error) { + if (!failed) { + failure = error; + failed = true; + } + } + }; + if (e) attempt(() => { + e = this._error(e); + }); + attempt(() => this._removeListeners()); + attempt(() => this._ro?.disconnect()); + attempt(() => this._canvasParent?.remove()); + this._init?.(); + const callbacks = []; + for (const cancel of [...this._requests || []]) attempt(() => { + const notify = cancel(); + if (notify) callbacks.push(notify); + }); + for (const notify of callbacks) attempt(notify); + if (this._worker) { + this._worker.onmessage = this._worker.onerror = null; + attempt(() => this._worker.terminate()); + } + if (failed) throw failure; + return e; } }; // test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page diff --git a/package.json b/package.json index d74d6774f..19840d3ad 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports", "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js", "typecheck": "node scripts/typecheck.mjs", - "test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js", + "test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js", "test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs", "test": "yarn test:node && yarn test:baseline", "test:browser": "playwright test", @@ -90,7 +90,7 @@ "test:vtt-thumbnail-types-package": "node refactor/scripts/vtt-thumbnail-package-types.mjs", "test:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs", "test:multiple-subtitles-types-package": "node refactor/scripts/multiple-subtitles-package-types.mjs", - "test:jassub": "node --test test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js refactor/scripts/jassub-contract.test.mjs refactor/scripts/jassub-provenance.test.mjs refactor/scripts/jassub-failures.test.mjs refactor/scripts/jassub-types.test.mjs", + "test:jassub": "node --test test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js refactor/scripts/jassub-contract.test.mjs refactor/scripts/jassub-provenance.test.mjs refactor/scripts/jassub-failures.test.mjs refactor/scripts/jassub-types.test.mjs refactor/scripts/jassub-vendor-patch.test.mjs", "test:jassub-types": "node --test refactor/scripts/jassub-types.test.mjs", "test:jassub-types-package": "node refactor/scripts/jassub-package-types.mjs", "check:ci": "node refactor/scripts/ci-workflow.mjs", diff --git a/packages/artplayer-plugin-jassub/ARCHITECTURE.md b/packages/artplayer-plugin-jassub/ARCHITECTURE.md index cb9156934..a763edfeb 100644 --- a/packages/artplayer-plugin-jassub/ARCHITECTURE.md +++ b/packages/artplayer-plugin-jassub/ARCHITECTURE.md @@ -12,14 +12,33 @@ Caller-supplied canvas nodes remain caller-owned. The adapter does not invent a or delete those nodes; vendor-created containers still get z-index 20. Host cleanup dynamically calls the exposed instance's current destroy method, ignores already destroyed instances and blocks synchronous reentry. A thrown disposal error is preserved -and the guard resets for a later attempt. Direct repeated vendor destroy is still a -separate PKG-JASSUB-07 problem; the adapter does not replace public instance methods. +and the guard resets for a later attempt. The local vendor patch also makes direct +destroy idempotent; the adapter does not replace public instance methods. If styling or host subscription throws after construction, registration independently attempts off with the exact callback and instance cleanup, then rethrows the original error. Secondary rollback errors do not replace it. An invalid/custom host whose off throws may retain an inert callback; failed vendor cleanup is not magically repaired. -Failures inside the vendor constructor before it returns an instance remain 07 work. +The vendor constructor rolls back its partially created DOM/listeners/Worker and +rethrows the original construction error. Asynchronous Worker errors keep their +existing event channel; callers can explicitly destroy after a loading failure. + +The local vendor lifecycle patch gives each video generation ownership of its main +and color-space frame callbacks. setVideo cancels the previous callbacks, unobserves +the old element, clears old demand/dimensions and moves its own container to a new +parent when needed. Already queued callbacks check generation before reading frames +or scheduling another callback. Color probes close VideoFrame even if reading fails. +Destroy invalidates the generation, disconnects the observer, removes the actual owned +container and terminates the Worker once. Caller-owned canvas/video elements remain. +Capability/init messages and pending sendMessage calls cannot post after destruction. + +Each query owns a timer and two Worker listeners. Success, timeout and Worker error +settle once after cleanup; callbacks receive the original error (including a plain +native Event for CSP failures). Destroy cleans every request before notifying any +callback with an Error and undefined data. A throwing callback does not prevent other +request cleanup or Worker termination; the first exception is rethrown afterward. +Queries should be issued after ready; this patch does not invent a Worker request-ID +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. @@ -41,15 +60,19 @@ There is no second implementation and no new factory.default property. Root/lega types remain unchanged; exact typesVersions mappings support old Node module resolution. See [type migration notes](types/README.md) before changing either surface. -`src/jassub.es.d.ts` is a private bridge for the frozen vendor JS, not a public export. +`src/jassub.es.d.ts` is a private bridge for the locally patched vendor JS, not a public export. It adds only the adapter's `_destroyed` read and `_canvasParent.style.zIndex` write to the accurate instance. The minimal style contract permits the historical numeric 20, which the native DOM setter converts to a string. The owned TS modules depend on RuntimeOption and RuntimeResult, while JassubHost needs only video/on/off. The implementation fixture checks actual Artplayer assignability. No broad any index, allowJs or ts-nocheck hides -owned code; the isolated third-party JS remains an explicit provenance/07 exception. +owned code; the isolated third-party JS remains an explicit provenance exception. -The wrapper matches upstream jassub 1.8.8 apart from formatting and the ESLint header; +The original wrapper matched upstream jassub 1.8.8 apart from formatting and the ESLint header; +PKG-JASSUB-07 records its local lifecycle/clock patch separately in +`../../refactor/baselines/jassub-vendor-patch.json` and the accompanying patch file. +Keep original and patched fingerprints distinct; do not call the changed wrapper an +unmodified upstream file. The worker JS and default font match that archive byte-for-byte. Local WASM instead matches the exact Pages nightly blobs associated with source 6b19a04ddfbad8f9bfd3237395788dd76218841b. Its build workflow and seven submodule revisions are pinned in the separate @@ -78,17 +101,28 @@ Provenance tests are offline by default; explicit --network refreshes pinned sou asset comparisons and fails on any request error, without substituting cached evidence. Set ARTPLAYER_JASSUB_CANDIDATE=1 for current source behavior; ARTPLAYER_JASSUB_ARTIFACT can point at a main/legacy artifact. Tests use controlled DOM, Worker and SIMD detection, with actual -vendor JavaScript. They do not render ASS or execute worker WASM. Real browser, failures, -repeated destroy and resource cleanup remain later steps. Frozen failure tests now +vendor JavaScript. The runtime query test also executes complete Worker JS and real WASM +through controlled transport; it is not a browser Worker. Frozen failure tests cover repeated teardown, cross-parent setVideo, custom canvas, Worker construction failure, stale video frames and incorrect fallback ratechange payloads. They assert -the old failures, not candidate fixes. PKG-JASSUB-03 owns adapter cleanup; 07 owns -separately tracked vendor lifecycle/clock changes and 05 verifies native behavior. +the old failures, not candidate fixes. Candidate lifecycle and polyfill tests separately +verify the local patch. PKG-JASSUB-05 retains complete combination/device acceptance. `yarn test:browser test/browser/jassub-native.spec.js --workers=1` loads the actual published wrapper, Worker, WASM and default font with offscreenRender=false. -Chromium/Firefox render and seek correctly; Windows WebKit's default frame-clock -path remains a recorded failure. The onDemandRender=false diagnostic does not -waive the default behavior gate. Native failure recovery, default offscreen mode, -full devices and sustained resource release remain unverified. See +Without ARTPLAYER_JASSUB_ARTIFACT this remains a historical test: the published wrapper +still fails Windows WebKit when its quality counters stay zero. Set that variable to +the normal candidate build to test the fix. Native RVFC and increasing quality counters +retain their existing behavior. Only the polyfill's observed all-zero counters can use +finite changed media time with readyState >= 2 and no active seek; this approximate path +limits each continuous callback chain to 30 Hz and still reports zero presented frames. +Paused seek completion is supported; a static paused video does not keep firing. +Per-video maps and monotonic handles prevent equal-clock callback collisions. + +The candidate's default onDemandRender path is tested through playback/seek/layout in +Chromium/Firefox/WebKit. jassub-lifecycle.spec.js additionally checks actual replacement +video pixels, numerical fallback playbackRate, repeated teardown, invalid-URL constructor +rollback and a genuine asynchronous CSP Worker error reaching a pending query. These +are distinct from default offscreen mode, full devices and sustained memory/GPU release, +which remain unverified. See `../../refactor/baselines/jassub-contract.md` for precise contract and provenance evidence. diff --git a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js index c882d2acb..a8c1975b5 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._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){const t=performance.now(),s=this.getVideoPlaybackQuality(),i=this.mozPresentedFrames||this.mozPaintedFrames||s.totalVideoFrames-s.droppedVideoFrames,a=(r,n)=>{const o=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(h>i){const i=this.mozFrameDelay||o.totalFrameDelay-s.totalFrameDelay||0,a=n-r;e(n,{presentationTime:n+1e3*i,expectedDisplayTime:n+a,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:h,processingDuration:i}),delete this._rvfcpolyfillmap[t]}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(n,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(t,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){cancelAnimationFrame(this._rvfcpolyfillmap[e]),delete this._rvfcpolyfillmap[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){if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");this._loaded=new Promise(e=>{this._init=e});const s=e._test();if(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.setRate.bind(this),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._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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_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((!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,videoHeight:this._videoHeight||this._video.videoHeight,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}){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){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s,colorSpace:this._videoColorSpace})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}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._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:i}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:i}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:i})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=s[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,i=setTimeout(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),a=({data:e})=>{e.target===s&&(t(null,e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i))},r=e=>{t(e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i)};this._worker.addEventListener("message",a),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){return e&&(e=this._error(e)),this._video&&this._canvasParent&&this._video.parentNode?.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker?.terminate(),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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]))}).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._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){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}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(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 f5c40ceed..b5145e496 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._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){const t=performance.now(),s=this.getVideoPlaybackQuality(),i=this.mozPresentedFrames||this.mozPaintedFrames||s.totalVideoFrames-s.droppedVideoFrames,a=(r,n)=>{const o=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(h>i){const i=this.mozFrameDelay||o.totalFrameDelay-s.totalFrameDelay||0,a=n-r;e(n,{presentationTime:n+1e3*i,expectedDisplayTime:n+a,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:h,processingDuration:i}),delete this._rvfcpolyfillmap[t]}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(n,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(t,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){cancelAnimationFrame(this._rvfcpolyfillmap[e]),delete this._rvfcpolyfillmap[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;if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");this._loaded=new Promise(e=>{this._init=e});const a=e._test();if(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.setRate.bind(this),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._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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_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)()){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,videoHeight:this._videoHeight||this._video.videoHeight,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}){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){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s,colorSpace:this._videoColorSpace})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}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._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:i}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:i}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:i})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=h[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:a,colorSpace:r}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}sendMessage(e){return o(this,arguments,function*(e,t={},s){yield this._loaded,s?this._worker.postMessage(r({target:e,transferable:s},t),[...s]):this._worker.postMessage(r({target:e},t))})}_fetchFromWorker(e,t){try{const s=e.target,i=setTimeout(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),a=({data:e})=>{e.target===s&&(t(null,e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i))},r=e=>{t(e),this._worker.removeEventListener("message",a),this._worker.removeEventListener("error",r),clearTimeout(i)};this._worker.addEventListener("message",a),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){var t,s;return e&&(e=this._error(e)),this._video&&this._canvasParent&&(null==(t=this._video.parentNode)||t.removeChild(this._canvasParent)),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),null==(s=this._worker)||s.terminate(),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 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}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]))}).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._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){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}){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 n of e)n.image&&(t?(this._ctx.drawImage(n.image,n.x,n.y),n.image.close()):(this._bufferCanvas.width=n.w,this._bufferCanvas.height=n.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(n.image)),n.w,n.h),0,0),this._ctx.drawImage(this._bufferCanvas,n.x,n.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)}}_fixAlpha(t){if(e._hasAlphaBug)for(let e=3;e1?t[e]:1;return t}_ready(){this._destroyed||(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)}}); diff --git a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs index 24386bec2..e7f96d74a 100644 --- a/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs +++ b/packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs @@ -7,27 +7,46 @@ var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); -typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function(c) { - const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames, r = (n, a) => { - const i = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || i.totalVideoFrames - i.droppedVideoFrames; - if (h > s) { - const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l = a - n; - c(a, { - presentationTime: a + d * 1e3, - expectedDisplayTime: a + l, - width: this.videoWidth, - height: this.videoHeight, - mediaTime: Math.max(0, this.currentTime || 0) + l / 1e3, - presentedFrames: h, - processingDuration: d - }), delete this._rvfcpolyfillmap[e]; - } else - this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => r(a, d)); +if (typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype) { + HTMLVideoElement.prototype.requestVideoFrameCallback = function(callback) { + if (!Object.prototype.hasOwnProperty.call(this, "_rvfcpolyfillmap")) + this._rvfcpolyfillmap = {}; + const id = this._rvfcpolyfillid = (this._rvfcpolyfillid || 0) + 1; + const started = performance.now(), initial = this.getVideoPlaybackQuality(), initialTime = this.currentTime; + const initialFrames = this.mozPresentedFrames || this.mozPaintedFrames || initial.totalVideoFrames - initial.droppedVideoFrames; + const poll = (previous, now) => { + if (!(id in this._rvfcpolyfillmap)) return; + const quality = this.getVideoPlaybackQuality(); + const frames = this.mozPresentedFrames || this.mozPaintedFrames || quality.totalVideoFrames - quality.droppedVideoFrames; + const advanced = frames > initialFrames; + const mediaFallback = initialFrames === 0 && frames === 0 && initial.totalVideoFrames === 0 && initial.droppedVideoFrames === 0 && quality.totalVideoFrames === 0 && quality.droppedVideoFrames === 0 && Number.isFinite(this.currentTime) && this.currentTime !== initialTime && this.readyState >= 2 && !this.seeking && now - started >= 1e3 / 30; + if (advanced || mediaFallback) { + const delay = this.mozFrameDelay || quality.totalFrameDelay - initial.totalFrameDelay || 0; + const elapsed = now - previous; + delete this._rvfcpolyfillmap[id]; + callback(now, { + presentationTime: now + delay * 1e3, + expectedDisplayTime: now + elapsed, + width: this.videoWidth, + height: this.videoHeight, + mediaTime: mediaFallback && !advanced ? Math.max(0, this.currentTime) : Math.max(0, this.currentTime || 0) + elapsed / 1e3, + presentedFrames: frames, + processingDuration: delay + }); + } else { + this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(now, time)); + } + }; + this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(started, time)); + return id; }; - return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) => r(e, n)), e; -}, HTMLVideoElement.prototype.cancelVideoFrameCallback = function(c) { - cancelAnimationFrame(this._rvfcpolyfillmap[c]), delete this._rvfcpolyfillmap[c]; -}); + HTMLVideoElement.prototype.cancelVideoFrameCallback = function(id) { + const callbacks = this._rvfcpolyfillmap; + if (!callbacks || !(id in callbacks)) return; + cancelAnimationFrame(callbacks[id]); + delete callbacks[id]; + }; +} const _ = { bt709: "BT709", // these might not be exactly correct? oops? @@ -82,47 +101,65 @@ const _o = class _o extends EventTarget { * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate). */ constructor(e) { - if (super(), !globalThis.Worker) - throw this.destroy("Worker not supported"); - if (!e) - throw this.destroy("No options provided"); - this._loaded = /** @type {Promise} */ - new Promise((s) => { - this._init = s; - }); - const t = _o._test(); - if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? true), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? true), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas) - this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._canvas = 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 = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = false, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { - this._worker.postMessage({ - target: "init", - wasmUrl: _o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm", - legacyWasmUrl: e.legacyWasmUrl ?? "jassub-worker.wasm.js", - asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? true), - onDemandRender: this._onDemandRender, - width: this._canvasctrl.width || 0, - height: this._canvasctrl.height || 0, - blendMode: e.blendMode || "js", - subUrl: e.subUrl, - subContent: e.subContent || null, - fonts: e.fonts || [], - availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" }, - fallbackFont: e.fallbackFont || "liberation sans", - debug: this.debug, - targetFps: e.targetFps || 24, - dropAllAnimations: e.dropAllAnimations, - dropAllBlur: e.dropAllBlur, - libassMemoryLimit: e.libassMemoryLimit || 0, - libassGlyphLimit: e.libassGlyphLimit || 0, - // @ts-ignore - useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? true), - hasBitmapBug: _o._hasBitmapBug - }), this._offscreenRender === true && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]); - }); + super(); + this._requests = /* @__PURE__ */ new Set(); + let t; + try { + if (!globalThis.Worker) + throw this.destroy("Worker not supported"); + if (!e) + throw this.destroy("No options provided"); + this._loaded = /** @type {Promise} */ + new Promise((s) => { + this._init = s; + }); + t = _o._test(); + if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? true), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? true), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas) + this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._canvas = 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 = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = () => { + if (!this._destroyed) this.setRate(this._video.playbackRate); + }, this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = false, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { + if (this._destroyed) return; + this._worker.postMessage({ + target: "init", + wasmUrl: _o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm", + legacyWasmUrl: e.legacyWasmUrl ?? "jassub-worker.wasm.js", + asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? true), + onDemandRender: this._onDemandRender, + width: this._canvasctrl.width || 0, + height: this._canvasctrl.height || 0, + blendMode: e.blendMode || "js", + subUrl: e.subUrl, + subContent: e.subContent || null, + fonts: e.fonts || [], + availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" }, + fallbackFont: e.fallbackFont || "liberation sans", + debug: this.debug, + targetFps: e.targetFps || 24, + dropAllAnimations: e.dropAllAnimations, + dropAllBlur: e.dropAllBlur, + libassMemoryLimit: e.libassMemoryLimit || 0, + libassGlyphLimit: e.libassGlyphLimit || 0, + // @ts-ignore + useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? true), + hasBitmapBug: _o._hasBitmapBug + }), this._offscreenRender === true && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]); + }).catch((error) => { + if (!this._destroyed) this.destroy(error); + }); + } catch (error) { + t?.catch(() => { + }); + try { + this.destroy(); + } catch { + } + throw error; + } } _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; @@ -183,6 +220,7 @@ const _o = class _o extends EventTarget { * @param {Boolean} [force=false] */ resize(e = 0, t = 0, s = 0, r = 0, n = this._video?.paused) { + if (this._destroyed) return; if ((!e || !t) && this._video) { const a = this._getVideoPosition(); let i = null; @@ -193,7 +231,7 @@ const _o = class _o extends EventTarget { i = this._computeCanvasSize(a.width || 0, a.height || 0); e = i.width, t = i.height, this._canvasParent && (s = a.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), r = 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 = r + "px", n && this.busy === false ? this.busy = true : n = false, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video.videoWidth, videoHeight: this._videoHeight || this._video.videoHeight, force: n }); + this._canvas.style.top = s + "px", this._canvas.style.left = r + "px", n && this.busy === false ? this.busy = true : n = false, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video?.videoWidth || e, videoHeight: this._videoHeight || this._video?.videoHeight || t, force: n }); } _getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) { const s = e / t, { offsetWidth: r, offsetHeight: n } = this._video, a = r / n; @@ -213,6 +251,7 @@ const _o = class _o extends EventTarget { return { width: e, height: t }; } _timeupdate({ type: e }) { + if (this._destroyed) return; const s = { seeking: true, waiting: true, @@ -225,7 +264,34 @@ const _o = class _o extends EventTarget { * @param {HTMLVideoElement} video */ setVideo(e) { - e instanceof HTMLVideoElement ? (this._removeListeners(), this._video = e, this._onDemandRender ? this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)) : (this._playstate = e.paused, e.addEventListener("timeupdate", this._boundTimeUpdate, false), e.addEventListener("progress", this._boundTimeUpdate, false), e.addEventListener("waiting", this._boundTimeUpdate, false), e.addEventListener("seeking", this._boundTimeUpdate, false), e.addEventListener("playing", this._boundTimeUpdate, false), e.addEventListener("ratechange", this._boundSetRate, false), e.addEventListener("resize", this._boundResize, false)), "VideoFrame" in window && (e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, false), e.readyState > 2 && this._updateColorSpace()), e.videoWidth > 0 && this.resize(), typeof ResizeObserver < "u" && (this._ro || (this._ro = new ResizeObserver(() => this.resize())), this._ro.observe(e))) : this._error("Video element invalid!"); + if (this._destroyed) return; + if (!(e instanceof HTMLVideoElement)) { + this._error("Video element invalid!"); + return; + } + this._removeListeners(); + this._video = e; + this._videoWidth = this._videoHeight = 0; + this._videoColorSpace = null; + if (this._canvasParent && this._canvasParent.parentNode !== e.parentNode) + e.insertAdjacentElement("afterend", this._canvasParent); + if (this._onDemandRender) this._requestFrame(); + else { + this._playstate = e.paused; + for (const name of ["timeupdate", "progress", "waiting", "seeking", "playing"]) + e.addEventListener(name, this._boundTimeUpdate, false); + e.addEventListener("ratechange", this._boundSetRate, false); + e.addEventListener("resize", this._boundResize, false); + } + if ("VideoFrame" in window) { + e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, false); + if (e.readyState > 2) this._updateColorSpace(); + } + if (e.videoWidth > 0) this.resize(); + if (typeof ResizeObserver < "u") { + this._ro || (this._ro = new ResizeObserver(() => this.resize())); + this._ro.observe(e); + } } runBenchmark() { this.sendMessage("runBenchmark"); @@ -317,7 +383,7 @@ const _o = class _o extends EventTarget { getEvents(e) { this._fetchFromWorker({ target: "getEvents" - }, (t, { events: s }) => { + }, (t, { events: s } = {}) => { e(t, s); }); } @@ -392,7 +458,7 @@ const _o = class _o extends EventTarget { getStyles(e) { this._fetchFromWorker({ target: "getStyles" - }, (t, { styles: s }) => { + }, (t, { styles: s } = {}) => { e(t, s); }); } @@ -434,12 +500,22 @@ const _o = class _o extends EventTarget { } } _unbusy() { + if (this._destroyed) return; this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = false; } _handleRVFC(e, { mediaTime: t, width: s, height: r }) { if (this._destroyed) return null; - this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = true, this._demandRender({ mediaTime: t, width: s, height: r })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)); + this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = true, this._demandRender({ mediaTime: t, width: s, height: r })), this._requestFrame(); + } + _requestFrame() { + if (this._destroyed || !this._video || this._frameCallback != null) return; + const video = this._video, generation = this._videoGeneration; + this._frameCallback = video.requestVideoFrameCallback((time, metadata) => { + if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; + this._frameCallback = null; + this._handleRVFC(time, metadata); + }); } _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 }); @@ -457,12 +533,20 @@ const _o = class _o extends EventTarget { this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas.transferControlToOffscreen(), this._ctx = false, this.sendMessage("offscreenCanvas", null, [this._canvasctrl]), this.resize(0, 0, 0, 0, true); } _updateColorSpace() { - this._video.requestVideoFrameCallback(() => { + if (this._destroyed || this._colorCallback != null) return; + const video = this._video, generation = this._videoGeneration; + this._colorCallback = video.requestVideoFrameCallback(() => { + if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; + this._colorCallback = null; + let frame; try { - const e = new VideoFrame(this._video); - this._videoColorSpace = _[e.colorSpace.matrix], e.close(), this.sendMessage("getColorSpace"); + frame = new VideoFrame(video); + this._videoColorSpace = _[frame.colorSpace.matrix]; + this.sendMessage("getColorSpace"); } catch (e) { console.warn(e); + } finally { + frame?.close(); } }); } @@ -496,6 +580,7 @@ const _o = class _o extends EventTarget { return e; } _ready() { + if (this._destroyed) return; this._init(), this.dispatchEvent(new CustomEvent("ready")); } /** @@ -505,7 +590,10 @@ const _o = class _o extends EventTarget { * @param {Transferable[]} [transferable] Array of transferables. */ async sendMessage(e, t = {}, s) { - await this._loaded, s ? this._worker.postMessage({ + if (this._destroyed) return; + await this._loaded; + if (this._destroyed) return; + s ? this._worker.postMessage({ target: e, transferable: s, ...t @@ -515,23 +603,53 @@ const _o = class _o extends EventTarget { }); } _fetchFromWorker(e, t) { + if (this._destroyed) { + t(new Error("JASSUB instance destroyed")); + return; + } + const worker = this._worker; + let timer, settled = false; + const cleanup = () => { + worker.removeEventListener("message", onMessage); + worker.removeEventListener("error", onError); + clearTimeout(timer); + this._requests.delete(cancel); + }; + const finish = (error, data) => { + if (settled) return; + settled = true; + cleanup(); + t(error, data); + }; + const onMessage = ({ data }) => { + if (data.target === e.target) finish(null, data); + }; + const onError = (error) => finish(error); + const cancel = () => { + if (settled) return; + settled = true; + cleanup(); + return () => t(new Error("JASSUB instance destroyed")); + }; try { - const s = e.target, r = setTimeout(() => { - a(new Error("Error: Timeout while try to fetch " + s)); - }, 5e3), n = ({ data: i }) => { - i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r)); - }, a = (i) => { - t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r); - }; - this._worker.addEventListener("message", n), this._worker.addEventListener("error", a), this._worker.postMessage(e); + this._requests.add(cancel); + worker.addEventListener("message", onMessage); + worker.addEventListener("error", onError); + timer = setTimeout(() => onError(new Error("Error: Timeout while try to fetch " + e.target)), 5e3); + worker.postMessage(e); } catch (s) { - this._error(s); + if (settled) throw s; + finish(s); } } _console({ content: e, command: t }) { console[t].apply(console, JSON.parse(e)); } _onmessage({ data: e }) { + if (this._destroyed) { + for (const image of e.images || []) image.image?.close?.(); + return; + } this["_" + e.target] && this["_" + e.target](e); } _error(e) { @@ -539,6 +657,12 @@ const _o = class _o extends EventTarget { return this.dispatchEvent(s), console.error(t), t; } _removeListeners() { + this._videoGeneration = (this._videoGeneration || 0) + 1; + for (const id of [this._frameCallback, this._colorCallback]) + if (id != null) this._video?.cancelVideoFrameCallback?.(id); + this._frameCallback = this._colorCallback = null; + this._lastDemandTime = null; + if (this._onDemandRender) this.busy = false; 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)); } /** @@ -546,7 +670,38 @@ const _o = class _o extends EventTarget { * @param {String|Error} [err] Error to throw when destroying. */ destroy(e) { - return e && (e = this._error(e)), this._video && this._canvasParent && this._video.parentNode?.removeChild(this._canvasParent), this._destroyed = true, this._removeListeners(), this.sendMessage("destroy"), this._worker?.terminate(), e; + if (this._destroyed) return e && this._error(e); + this._destroyed = true; + let failure, failed = false; + const attempt = (callback) => { + try { + callback(); + } catch (error) { + if (!failed) { + failure = error; + failed = true; + } + } + }; + if (e) attempt(() => { + e = this._error(e); + }); + attempt(() => this._removeListeners()); + attempt(() => this._ro?.disconnect()); + attempt(() => this._canvasParent?.remove()); + this._init?.(); + const callbacks = []; + for (const cancel of [...this._requests || []]) attempt(() => { + const notify = cancel(); + if (notify) callbacks.push(notify); + }); + for (const notify of callbacks) attempt(notify); + if (this._worker) { + this._worker.onmessage = this._worker.onerror = null; + attempt(() => this._worker.terminate()); + } + if (failed) throw failure; + return e; } }; // test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page diff --git a/packages/artplayer-plugin-jassub/src/jassub.es.js b/packages/artplayer-plugin-jassub/src/jassub.es.js index f5a8f292c..904b40888 100644 --- a/packages/artplayer-plugin-jassub/src/jassub.es.js +++ b/packages/artplayer-plugin-jassub/src/jassub.es.js @@ -1,25 +1,50 @@ /* eslint-disable */ -typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function (c) { - const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames, r = (n, a) => { - const i = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || i.totalVideoFrames - i.droppedVideoFrames; - if (h > s) { - const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l = a - n; - c(a, { - presentationTime: a + d * 1e3, - expectedDisplayTime: a + l, - width: this.videoWidth, - height: this.videoHeight, - mediaTime: Math.max(0, this.currentTime || 0) + l / 1e3, - presentedFrames: h, - processingDuration: d - }), delete this._rvfcpolyfillmap[e]; - } else - this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => r(a, d)); +if (typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype) { + HTMLVideoElement.prototype.requestVideoFrameCallback = function (callback) { + if (!Object.prototype.hasOwnProperty.call(this, "_rvfcpolyfillmap")) + this._rvfcpolyfillmap = {}; + const id = this._rvfcpolyfillid = (this._rvfcpolyfillid || 0) + 1; + const started = performance.now(), initial = this.getVideoPlaybackQuality(), initialTime = this.currentTime; + const initialFrames = this.mozPresentedFrames || this.mozPaintedFrames || initial.totalVideoFrames - initial.droppedVideoFrames; + const poll = (previous, now) => { + if (!(id in this._rvfcpolyfillmap)) return; + const quality = this.getVideoPlaybackQuality(); + const frames = this.mozPresentedFrames || this.mozPaintedFrames || quality.totalVideoFrames - quality.droppedVideoFrames; + const advanced = frames > initialFrames; + // Some WebKit backends expose quality counters but keep both at zero. + // Approximate media-time delivery only there, capped at 30 Hz; never invent frame counts. + const mediaFallback = initialFrames === 0 && frames === 0 + && initial.totalVideoFrames === 0 && initial.droppedVideoFrames === 0 + && quality.totalVideoFrames === 0 && quality.droppedVideoFrames === 0 + && Number.isFinite(this.currentTime) && this.currentTime !== initialTime + && this.readyState >= 2 && !this.seeking && now - started >= 1000 / 30; + if (advanced || mediaFallback) { + const delay = this.mozFrameDelay || quality.totalFrameDelay - initial.totalFrameDelay || 0; + const elapsed = now - previous; + delete this._rvfcpolyfillmap[id]; + callback(now, { + presentationTime: now + delay * 1e3, + expectedDisplayTime: now + elapsed, + width: this.videoWidth, + height: this.videoHeight, + mediaTime: mediaFallback && !advanced ? Math.max(0, this.currentTime) : Math.max(0, this.currentTime || 0) + elapsed / 1e3, + presentedFrames: frames, + processingDuration: delay + }); + } else { + this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(now, time)); + } + }; + this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(started, time)); + return id; }; - return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) => r(e, n)), e; -}, HTMLVideoElement.prototype.cancelVideoFrameCallback = function (c) { - cancelAnimationFrame(this._rvfcpolyfillmap[c]), delete this._rvfcpolyfillmap[c]; -}); + HTMLVideoElement.prototype.cancelVideoFrameCallback = function (id) { + const callbacks = this._rvfcpolyfillmap; + if (!callbacks || !(id in callbacks)) return; + cancelAnimationFrame(callbacks[id]); + delete callbacks[id]; + }; +} const _ = { bt709: "BT709", // these might not be exactly correct? oops? @@ -74,7 +99,11 @@ class o extends EventTarget { * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate). */ constructor(e) { - if (super(), !globalThis.Worker) + super(); + this._requests = new Set(); + let t; + try { + if (!globalThis.Worker) throw this.destroy("Worker not supported"); if (!e) throw this.destroy("No options provided"); @@ -82,14 +111,15 @@ class o extends EventTarget { new Promise((s) => { this._init = s; }); - const t = o._test(); + t = o._test(); if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? !0), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? !0), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas) this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._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 = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = !1, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { + this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas, this._ctx = !this._offscreenRender && this._canvasctrl.getContext("2d"), this._lastRenderTime = 0, this.debug = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = () => { if (!this._destroyed) this.setRate(this._video.playbackRate); }, this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = !1, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { + if (this._destroyed) return; this._worker.postMessage({ target: "init", wasmUrl: o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm", @@ -114,7 +144,14 @@ class o extends EventTarget { useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? !0), hasBitmapBug: o._hasBitmapBug }), this._offscreenRender === !0 && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]); + }).catch((error) => { + if (!this._destroyed) this.destroy(error); }); + } catch (error) { + t?.catch(() => {}); + try { this.destroy(); } catch {} + throw error; + } } _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; @@ -182,6 +219,7 @@ class o extends EventTarget { * @param {Boolean} [force=false] */ resize(e = 0, t = 0, s = 0, r = 0, n = this._video?.paused) { + if (this._destroyed) return; if ((!e || !t) && this._video) { const a = this._getVideoPosition(); let i = null; @@ -192,7 +230,7 @@ class o extends EventTarget { i = this._computeCanvasSize(a.width || 0, a.height || 0); e = i.width, t = i.height, this._canvasParent && (s = a.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), r = 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 = r + "px", n && this.busy === !1 ? this.busy = !0 : n = !1, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video.videoWidth, videoHeight: this._videoHeight || this._video.videoHeight, force: n }); + this._canvas.style.top = s + "px", this._canvas.style.left = r + "px", n && this.busy === !1 ? this.busy = !0 : n = !1, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video?.videoWidth || e, videoHeight: this._videoHeight || this._video?.videoHeight || t, force: n }); } _getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) { const s = e / t, { offsetWidth: r, offsetHeight: n } = this._video, a = r / n; @@ -212,6 +250,7 @@ class o extends EventTarget { return { width: e, height: t }; } _timeupdate({ type: e }) { + if (this._destroyed) return; const s = { seeking: !0, waiting: !0, @@ -224,7 +263,34 @@ class o extends EventTarget { * @param {HTMLVideoElement} video */ setVideo(e) { - e instanceof HTMLVideoElement ? (this._removeListeners(), this._video = e, this._onDemandRender ? this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)) : (this._playstate = e.paused, e.addEventListener("timeupdate", this._boundTimeUpdate, !1), e.addEventListener("progress", this._boundTimeUpdate, !1), e.addEventListener("waiting", this._boundTimeUpdate, !1), e.addEventListener("seeking", this._boundTimeUpdate, !1), e.addEventListener("playing", this._boundTimeUpdate, !1), e.addEventListener("ratechange", this._boundSetRate, !1), e.addEventListener("resize", this._boundResize, !1)), "VideoFrame" in window && (e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, !1), e.readyState > 2 && this._updateColorSpace()), e.videoWidth > 0 && this.resize(), typeof ResizeObserver < "u" && (this._ro || (this._ro = new ResizeObserver(() => this.resize())), this._ro.observe(e))) : this._error("Video element invalid!"); + if (this._destroyed) return; + if (!(e instanceof HTMLVideoElement)) { + this._error("Video element invalid!"); + return; + } + this._removeListeners(); + this._video = e; + this._videoWidth = this._videoHeight = 0; + this._videoColorSpace = null; + if (this._canvasParent && this._canvasParent.parentNode !== e.parentNode) + e.insertAdjacentElement("afterend", this._canvasParent); + if (this._onDemandRender) this._requestFrame(); + else { + this._playstate = e.paused; + for (const name of ["timeupdate", "progress", "waiting", "seeking", "playing"]) + e.addEventListener(name, this._boundTimeUpdate, false); + e.addEventListener("ratechange", this._boundSetRate, false); + e.addEventListener("resize", this._boundResize, false); + } + if ("VideoFrame" in window) { + e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, false); + if (e.readyState > 2) this._updateColorSpace(); + } + if (e.videoWidth > 0) this.resize(); + if (typeof ResizeObserver < "u") { + this._ro || (this._ro = new ResizeObserver(() => this.resize())); + this._ro.observe(e); + } } runBenchmark() { this.sendMessage("runBenchmark"); @@ -316,7 +382,7 @@ class o extends EventTarget { getEvents(e) { this._fetchFromWorker({ target: "getEvents" - }, (t, { events: s }) => { + }, (t, { events: s } = {}) => { e(t, s); }); } @@ -391,7 +457,7 @@ class o extends EventTarget { getStyles(e) { this._fetchFromWorker({ target: "getStyles" - }, (t, { styles: s }) => { + }, (t, { styles: s } = {}) => { e(t, s); }); } @@ -433,12 +499,22 @@ class o extends EventTarget { } } _unbusy() { + if (this._destroyed) return; this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = !1; } _handleRVFC(e, { mediaTime: t, width: s, height: r }) { if (this._destroyed) return null; - this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: r })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)); + this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: r })), this._requestFrame(); + } + _requestFrame() { + if (this._destroyed || !this._video || this._frameCallback != null) return; + const video = this._video, generation = this._videoGeneration; + this._frameCallback = video.requestVideoFrameCallback((time, metadata) => { + if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; + this._frameCallback = null; + this._handleRVFC(time, metadata); + }); } _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 }); @@ -456,12 +532,20 @@ class o extends EventTarget { this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas.transferControlToOffscreen(), this._ctx = !1, this.sendMessage("offscreenCanvas", null, [this._canvasctrl]), this.resize(0, 0, 0, 0, !0); } _updateColorSpace() { - this._video.requestVideoFrameCallback(() => { + if (this._destroyed || this._colorCallback != null) return; + const video = this._video, generation = this._videoGeneration; + this._colorCallback = video.requestVideoFrameCallback(() => { + if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; + this._colorCallback = null; + let frame; try { - const e = new VideoFrame(this._video); - this._videoColorSpace = _[e.colorSpace.matrix], e.close(), this.sendMessage("getColorSpace"); + frame = new VideoFrame(video); + this._videoColorSpace = _[frame.colorSpace.matrix]; + this.sendMessage("getColorSpace"); } catch (e) { console.warn(e); + } finally { + frame?.close(); } }); } @@ -495,6 +579,7 @@ class o extends EventTarget { return e; } _ready() { + if (this._destroyed) return; this._init(), this.dispatchEvent(new CustomEvent("ready")); } /** @@ -504,7 +589,10 @@ class o extends EventTarget { * @param {Transferable[]} [transferable] Array of transferables. */ async sendMessage(e, t = {}, s) { - await this._loaded, s ? this._worker.postMessage({ + if (this._destroyed) return; + await this._loaded; + if (this._destroyed) return; + s ? this._worker.postMessage({ target: e, transferable: s, ...t @@ -514,23 +602,51 @@ class o extends EventTarget { }); } _fetchFromWorker(e, t) { + if (this._destroyed) { + t(new Error("JASSUB instance destroyed")); + return; + } + const worker = this._worker; + let timer, settled = false; + const cleanup = () => { + worker.removeEventListener("message", onMessage); + worker.removeEventListener("error", onError); + clearTimeout(timer); + this._requests.delete(cancel); + }; + const finish = (error, data) => { + if (settled) return; + settled = true; + cleanup(); + t(error, data); + }; + const onMessage = ({ data }) => { if (data.target === e.target) finish(null, data); }; + const onError = (error) => finish(error); + const cancel = () => { + if (settled) return; + settled = true; + cleanup(); + return () => t(new Error("JASSUB instance destroyed")); + }; try { - const s = e.target, r = setTimeout(() => { - a(new Error("Error: Timeout while try to fetch " + s)); - }, 5e3), n = ({ data: i }) => { - i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r)); - }, a = (i) => { - t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r); - }; - this._worker.addEventListener("message", n), this._worker.addEventListener("error", a), this._worker.postMessage(e); + this._requests.add(cancel); + worker.addEventListener("message", onMessage); + worker.addEventListener("error", onError); + timer = setTimeout(() => onError(new Error("Error: Timeout while try to fetch " + e.target)), 5e3); + worker.postMessage(e); } catch (s) { - this._error(s); + if (settled) throw s; + finish(s); } } _console({ content: e, command: t }) { console[t].apply(console, JSON.parse(e)); } _onmessage({ data: e }) { + if (this._destroyed) { + for (const image of e.images || []) image.image?.close?.(); + return; + } this["_" + e.target] && this["_" + e.target](e); } _error(e) { @@ -538,6 +654,12 @@ class o extends EventTarget { return this.dispatchEvent(s), console.error(t), t; } _removeListeners() { + this._videoGeneration = (this._videoGeneration || 0) + 1; + for (const id of [this._frameCallback, this._colorCallback]) + if (id != null) this._video?.cancelVideoFrameCallback?.(id); + this._frameCallback = this._colorCallback = null; + this._lastDemandTime = null; + if (this._onDemandRender) this.busy = false; 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)); } /** @@ -545,7 +667,29 @@ class o extends EventTarget { * @param {String|Error} [err] Error to throw when destroying. */ destroy(e) { - return e && (e = this._error(e)), this._video && this._canvasParent && this._video.parentNode?.removeChild(this._canvasParent), this._destroyed = !0, this._removeListeners(), this.sendMessage("destroy"), this._worker?.terminate(), e; + if (this._destroyed) return e && this._error(e); + this._destroyed = true; + let failure, failed = false; + const attempt = (callback) => { + try { callback(); } catch (error) { if (!failed) { failure = error; failed = true; } } + }; + if (e) attempt(() => { e = this._error(e); }); + attempt(() => this._removeListeners()); + attempt(() => this._ro?.disconnect()); + attempt(() => this._canvasParent?.remove()); + this._init?.(); + const callbacks = []; + for (const cancel of [...this._requests || []]) attempt(() => { + const notify = cancel(); + if (notify) callbacks.push(notify); + }); + for (const notify of callbacks) attempt(notify); + if (this._worker) { + this._worker.onmessage = this._worker.onerror = null; + attempt(() => this._worker.terminate()); + } + if (failed) throw failure; + return e; } } export { diff --git a/packages/artplayer-plugin-jassub/types/README.md b/packages/artplayer-plugin-jassub/types/README.md index ec9892ad9..b1d667870 100644 --- a/packages/artplayer-plugin-jassub/types/README.md +++ b/packages/artplayer-plugin-jassub/types/README.md @@ -41,15 +41,18 @@ Switching to `/runtime` intentionally changes type inference. For example, `instance.resize(640, 360, 0, 0, true)` describes the actual call order. `await instance.destroy()` remains legal JavaScript, but `.then()` on that result never worked at runtime. Resource teardown completion is not represented by a Promise. -The callback error channel is `Error | ErrorEvent | null`; an error may omit the data. -The currently frozen vendor has a request timeout/error bug that throws before user -callbacks run. PKG-JASSUB-07 tracks the fix; typing that channel does not fix or guarantee -callback delivery. Query outputs do not contain the upstream npm declaration's `_index`. +The callback error channel is `Error | Event | null`; ErrorEvent is included, and real +CSP Worker failures can deliver a plain Event. Requests clean their listeners/timer +before calling the consumer once. Destroy settles pending callbacks with an Error and +undefined data after all request resources are removed, even if another callback throws. +Query outputs do not contain the upstream npm declaration's `_index`. Issue queries after +ready; the Worker protocol still identifies responses by target, not a new request ID. The accurate instance extends EventTarget and preserves its normal listener APIs. `destroy(error)` returns the original Error, or converts a nonempty string to Error; -an empty string is returned unchanged. Repeated direct destruction and construction/ -video clock failures remain separate vendor lifecycle work, documented in ARCHITECTURE.md. +an empty string is returned unchanged. Direct destruction is idempotent, and queued +sendMessage operations settle without posting after destruction. Lifecycle and the +limited media-time fallback for zero frame counters are documented in ARCHITECTURE.md. NodeNext ESM retains the root declaration's historical CommonJS namespace behavior. If existing code uses the root namespace's `.default` to satisfy that declaration, @@ -59,8 +62,8 @@ Do not invent `jassub.default`: the current JavaScript factory has no self alias Older TypeScript resolves `/legacy` and `/runtime` through exact typesVersions mappings. Existing direct declaration paths are retained; no wildcard remaps unrelated paths. -Maintain `runtime-api.d.ts` against the frozen wrapper and worker behavior, not a new -upstream version. Keep CJS/ESM entry aliases synchronized. Run `yarn test:jassub-types`, +Maintain `runtime-api.d.ts` against the recorded base plus local wrapper patches and +actual worker behavior, not a new upstream version. Keep CJS/ESM entry aliases synchronized. Run `yarn test:jassub-types`, `yarn test:jassub-types-package`, `yarn test:jassub`, and `yarn typecheck` after changes. The package runner installs actual historical archives and the candidate outside the workspace, checks strict positive/negative consumers, and verifies artifact identity. diff --git a/packages/artplayer-plugin-jassub/types/runtime-api.d.ts b/packages/artplayer-plugin-jassub/types/runtime-api.d.ts index 1d220fd4d..f40c5bc31 100644 --- a/packages/artplayer-plugin-jassub/types/runtime-api.d.ts +++ b/packages/artplayer-plugin-jassub/types/runtime-api.d.ts @@ -88,13 +88,12 @@ export interface AssStyle { export type AssEventInput = Partial export type AssStyleInput = Partial -/** Timeout errors and native Worker error events are separate channels. */ -export type WorkerRequestError = Error | ErrorEvent +/** Timeout errors and native Worker events (including plain CSP error Events). */ +export type WorkerRequestError = Error | Event /** * Success supplies null plus an array. Error payloads are absent. - * The frozen vendor currently throws before notifying this callback on request - * errors/timeouts; PKG-JASSUB-07 owns that defect. These types do not guarantee - * callback delivery or claim that the error path has been repaired. + * Requests release their listeners/timer before invoking callbacks. Destruction + * settles pending requests with an Error; native Worker error Events retain identity. */ export type EventsCallback = (error: WorkerRequestError | null, events?: AssEvent[]) => void export type StylesCallback = (error: WorkerRequestError | null, styles?: AssStyle[]) => void @@ -139,7 +138,7 @@ export interface RuntimeInstance extends EventTarget { disableStyleOverride: () => void setDefaultFont: (font: string) => void addFont: (font: FontSource) => void - /** Resolves after posting the message; it is not a Worker-operation acknowledgement. */ + /** Resolves after posting, or without posting after destruction; not a Worker acknowledgement. */ sendMessage: (target: string, data?: Record | null, transferable?: Transferable[]) => Promise destroy: (() => void) & ((error: Error) => Error) & ((error: string) => Error | '') & ((error: Error | string | undefined) => Error | '' | undefined) diff --git a/refactor/baselines/jassub-vendor-patch.json b/refactor/baselines/jassub-vendor-patch.json new file mode 100644 index 000000000..25d44beda --- /dev/null +++ b/refactor/baselines/jassub-vendor-patch.json @@ -0,0 +1,19 @@ +{ + "task": "PKG-JASSUB-07", + "baselineCommit": "5e445b1af655a1cafbfa3abca62d45207c042936", + "file": "packages/artplayer-plugin-jassub/src/jassub.es.js", + "originalSha256LF": "b973508ade00b5b9cea2f299d49f4943003372729be7350b6938e44fddb32038", + "patchedSha256LF": "1fdcf24d206e8711348dddcb5fe7b656007a9b1d81a992bb497c6d10bd0a2cdb", + "patchFile": "refactor/baselines/jassub-vendor.patch", + "patchSha256LF": "fe6d429418a676f5d1b9c4daf9cd6a20be27c3a1b18f2a3d691a943188f7bfce", + "origin": "Original wrapper matches frozen npm jassub 1.8.8 after format normalization. This is an explicit local patch, not a claim of unchanged upstream bytes.", + "changes": [ + "constructor rollback and cancellation of late initialization", + "idempotent destruction and ownership of actual canvas parent", + "per-video generations and cancellation for render/color-space callbacks", + "numeric fallback playbackRate adapter", + "query settlement and cleanup before callbacks, including destroy cancellation", + "per-video polyfill IDs/maps and zero-quality-counter media-time fallback capped at 30 Hz per continuous chain" + ], + "unchanged": "Worker JS, WASM, fonts, resource URLs, root declarations and actual public instance identity remain unchanged. Original provenance manifests are not rewritten." +} diff --git a/refactor/baselines/jassub-vendor-validation.json b/refactor/baselines/jassub-vendor-validation.json new file mode 100644 index 000000000..48f196514 --- /dev/null +++ b/refactor/baselines/jassub-vendor-validation.json @@ -0,0 +1,11831 @@ +{ + "task": "PKG-JASSUB-07", + "recordedAt": "2026-09-13T18:14:39.096Z", + "node": "v24.21.0", + "platform": "win32", + "sourceFiles": [ + { + "file": "packages/artplayer-plugin-jassub/src/jassub.es.js", + "sha256": "1fdcf24d206e8711348dddcb5fe7b656007a9b1d81a992bb497c6d10bd0a2cdb" + }, + { + "file": "test/jassub.test.js", + "sha256": "e4e932c88ea8f72fb23d6bd65f2ef79c8b920f013819493aa121fe28f7ef1df6" + }, + { + "file": "test/jassub-vendor-lifecycle.test.js", + "sha256": "5d64af8b9a4ccc273d854146fff31fa4a6d20f832cdfbabf32246c92dee19227" + }, + { + "file": "test/jassub-frame-polyfill.test.js", + "sha256": "d04a6b50e1fb0bb4a773b6a8ed382c4e65e639125bd32fe4866c090700faeed1" + }, + { + "file": "test/browser/jassub-native.spec.js", + "sha256": "b72a44eec31e2fc9cc286431ecfba0d0e5d458328d44a065948956434e293df7" + }, + { + "file": "test/browser/jassub-lifecycle.spec.js", + "sha256": "71c39e77deb1fbc62fc708792d9ac092908f05f48c7419d578c23c5f30c9ce0b" + }, + { + "file": "test/helpers/jassub.js", + "sha256": "a02b639f1eb28c4d8be1724365c8407fff758165a1c849dbbf794135e02396d0" + }, + { + "file": "refactor/scripts/jassub-vendor-patch.test.mjs", + "sha256": "e393586c26765e7989cfb4ecd8c6a728215ac4d78c8582adb84fadbcea71d1e1" + } + ], + "patch": { + "task": "PKG-JASSUB-07", + "baselineCommit": "5e445b1af655a1cafbfa3abca62d45207c042936", + "file": "packages/artplayer-plugin-jassub/src/jassub.es.js", + "originalSha256LF": "b973508ade00b5b9cea2f299d49f4943003372729be7350b6938e44fddb32038", + "patchedSha256LF": "1fdcf24d206e8711348dddcb5fe7b656007a9b1d81a992bb497c6d10bd0a2cdb", + "patchFile": "refactor/baselines/jassub-vendor.patch", + "patchSha256LF": "fe6d429418a676f5d1b9c4daf9cd6a20be27c3a1b18f2a3d691a943188f7bfce", + "origin": "Original wrapper matches frozen npm jassub 1.8.8 after format normalization. This is an explicit local patch, not a claim of unchanged upstream bytes.", + "changes": [ + "constructor rollback and cancellation of late initialization", + "idempotent destruction and ownership of actual canvas parent", + "per-video generations and cancellation for render/color-space callbacks", + "numeric fallback playbackRate adapter", + "query settlement and cleanup before callbacks, including destroy cancellation", + "per-video polyfill IDs/maps and zero-quality-counter media-time fallback capped at 30 Hz per continuous chain" + ], + "unchanged": "Worker JS, WASM, fonts, resource URLs, root declarations and actual public instance identity remain unchanged. Original provenance manifests are not rewritten." + }, + "controlled": { + "combined": { + "file": "refactor/.cache/jassub07-all-final.log", + "sha256": "2e91d987be868259f88a892ef32d4b8b9a210cb85fda8d5ae7fec7df96bfd926", + "passed": 185, + "failed": 0 + }, + "source": { + "file": "refactor/.cache/jassub07-candidate-source.log", + "sha256": "7af699402993b3044849059f141506469cee5833c5f3582883da2e25279a9ea0", + "passed": 68, + "failed": 0 + }, + "main": { + "file": "refactor/.cache/jassub07-candidate-js.log", + "sha256": "e21d6b0d72d0bf537f6154c22d15aef65928dba4f254b9e17af66e58cdfd1aca", + "passed": 68, + "failed": 0 + }, + "legacy": { + "file": "refactor/.cache/jassub07-candidate-legacy.js.log", + "sha256": "bd930007f8ace816fb4a86fc09370cde6a581cf0279a6603e478ef4154eb870e", + "passed": 68, + "failed": 0 + }, + "lifecycleOldArtifact": { + "file": "refactor/.cache/jassub07-unit-red-final.log", + "sha256": "68ac5c8b63f6a2c80ca11d7c0486012e61dd94f7e3c885b7e60b110206f6a32b", + "passed": 0, + "failed": 21 + }, + "polyfillOldArtifact": { + "file": "refactor/.cache/jassub07-polyfill-red.log", + "sha256": "35514dea800e1834d37a1de0ad98b6d01d14bed7cf9b8fcf265ef5cd6e581a76", + "passed": 13, + "failed": 11 + }, + "polyfillCandidate": { + "file": "refactor/.cache/jassub07-polyfill-green.log", + "sha256": "b1357f022568075a09e0b42a8b07ef1c3796d6a4c314c668363c8df4d48ee42e", + "passed": 24, + "failed": 0 + }, + "firstCandidateRun": { + "file": "refactor/.cache/jassub07-candidate-source-initial.log", + "sha256": "27b6c608dc7e2544560ed6f9eb8149d68a9441c38c28c8dc904091f45cef5ff7", + "passed": 66, + "failed": 1 + }, + "firstCandidateFailureResolution": "The historical mixed return/resize test inspected queued resize after terminating the Worker. It now checks delivery before destroy without weakening dimensions or synchronous-return assertions. A separate exact old-red/candidate-green regression verifies cancellation after readiness. Source/main/legacy now each pass 68.", + "scope": "Actual vendor under controlled platform, including complete real WASM query tests; historical defect confirmations are not candidate browser acceptance." + }, + "checks": { + "typecheck": { + "file": "refactor/.cache/jassub07-typecheck.log", + "sha256": "52005168125dd24377233f7c3209108b6a1a52b8509ed4c7ecaee61406da69c5", + "productionTypeScriptFiles": 406 + }, + "lint": { + "file": "refactor/.cache/jassub07-lint-final.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "build": { + "file": "refactor/.cache/jassub07-build.log", + "sha256": "2cb123f637a0b67b7655b4206d552a56171ef43a89f0e7c3c64cf8ac879d6089" + } + }, + "browser": { + "scope": "Windows Playwright real Worker/WASM/font/video; offscreenRender=false. Default onDemandRender, native RVFC where available, all-zero quality polyfill only where needed. Not Chrome extension interaction, physical Safari/mobile, default offscreen or sustained GPU/memory verification.", + "passed": 51, + "reports": [ + { + "file": "refactor/.cache/jassub07-native-default.json", + "sha256": "43c2a3742f8627c5d7830ddd6d9f27cc92437720ca720777c1db1085484952f9", + "stats": { + "startTime": "2026-09-13T17:52:00.993Z", + "duration": 26206.073, + "expected": 9, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.5077, + "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, + "time": 10.015238, + "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, + "time": 10.113651, + "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": 1304.8, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1288.7, + "presentedFrames": 20, + "processingDuration": 0.0974, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.113651, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.504256, + "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, + "time": 10.015565, + "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, + "time": 10.109827, + "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": 1259.6, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1243.4, + "presentedFrames": 20, + "processingDuration": 0.0947, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.109827, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + }, + { + "target": "demand", + "time": 10.24 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.501338, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "decodedFrames": 15 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.014296, + "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, + "time": 10.116931, + "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": 1337.4, + "height": 720, + "mediaTime": 10.24, + "presentationTime": 1322, + "presentedFrames": 20, + "processingDuration": 0.1427, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.116931, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 16, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + } + ] + }, + "received": { + "count": 15, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.571282, + "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, + "time": 10.000025, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 14, + "droppedFrames": 0, + "mozPresentedFrames": 14 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040154, + "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": 1801.4, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1801.4, + "presentedFrames": 45, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040154, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 15, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + } + ] + }, + "received": { + "count": 14, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.321162, + "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, + "time": 10.000019, + "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, + "time": 10.040117, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 9, + "droppedFrames": 0, + "mozPresentedFrames": 9 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1234.56, + "height": 720, + "mediaTime": 10.08, + "presentationTime": 1234.56, + "presentedFrames": 31, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040117, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 9, + "droppedFrames": 0, + "mozPresentedFrames": 9 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 16, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + } + ] + }, + "received": { + "count": 15, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.572135, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 14, + "droppedFrames": 0, + "mozPresentedFrames": 14 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 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, + "time": 10.040459, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "mozPresentedFrames": 16 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 12, + "delivered": 11, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1593.62, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1593.62, + "presentedFrames": 47, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040459, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 16, + "droppedFrames": 0, + "mozPresentedFrames": 16 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 15, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10.0178333 + }, + { + "target": "canvas", + "width": 2560, + "height": 1440 + }, + { + "target": "demand", + "time": 10.4700417 + } + ] + }, + "received": { + "count": 14, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.5530001, + "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, + "time": 10.02375, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.0947917, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "presentationTime": 3181, + "expectedDisplayTime": 3601, + "width": 640, + "height": 360, + "mediaTime": 10.4700417, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.0947917, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 14, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10.0559167 + }, + { + "target": "canvas", + "width": 2560, + "height": 1440 + }, + { + "target": "demand", + "time": 10.4582292 + } + ] + }, + "received": { + "count": 12, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.4982084, + "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, + "time": 10.0115208, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.0767292, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 10, + "delivered": 9, + "after": "function", + "lastMetadata": { + "presentationTime": 2927, + "expectedDisplayTime": 3329, + "width": 640, + "height": 360, + "mediaTime": 10.4582292, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.0767292, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 20, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10.1690834 + }, + { + "target": "demand", + "time": 10.2770209 + }, + { + "target": "demand", + "time": 10.3461876 + }, + { + "target": "demand", + "time": 10.6953334 + } + ] + }, + "received": { + "count": 19, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.5485626, + "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, + "time": 10.0521875, + "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, + "time": 10.3457501, + "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": 3389, + "expectedDisplayTime": 3738, + "width": 640, + "height": 360, + "mediaTime": 10.6953334, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.3457501, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + } + ] + }, + { + "file": "refactor/.cache/jassub07-native-lifecycle-final.json", + "sha256": "ffacd7417d4ebd6194888b936e589b75fda4df4c59628f188944a9ff961f46e8", + "stats": { + "startTime": "2026-09-13T17:59:29.312Z", + "duration": 16721.407000000003, + "expected": 12, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=true", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "onDemandRender": true, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.005034, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 12, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "demand", + "time": 0.04 + } + ], + "last": [ + { + "target": "canvas" + }, + { + "target": "demand", + "time": 9 + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=false", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "onDemandRender": false, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.005034, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 13, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "getColorSpace" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "video", + "rate": 1.5 + }, + { + "target": "video" + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual invalid Worker URL construction releases the newly created canvas and media listeners", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-construction-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "state": { + "error": { + "name": "SyntaxError", + "message": "Failed to construct 'Worker': 'http://[' is not a valid URL." + }, + "activeListeners": 0, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB native asynchronous CSP Worker error reaches a pending query and explicit destruction cleans up", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-worker-error": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "state": { + "outcome": { + "errorIsNativeEvent": true, + "trusted": true, + "sameNativeError": true, + "type": "error", + "rowsAbsent": true + }, + "calls": 1, + "terminated": 1, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [ + "Creating a worker from 'http://127.0.0.1:8084/assets/jassub/jassub-worker.js' violates the following Content Security Policy directive: \"worker-src 'none'\". The action has been blocked.", + "Error: [object Event]\n at e._error (:7:16169)\n at _worker.onerror (:7:4573)" + ], + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=true", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "onDemandRender": true, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.00003, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 11, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "canvas" + }, + { + "target": "demand", + "time": 9 + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=false", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "onDemandRender": false, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.000031, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 13, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "video" + } + ], + "last": [ + { + "target": "video", + "rate": 1.5 + }, + { + "target": "video" + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual invalid Worker URL construction releases the newly created canvas and media listeners", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-construction-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "state": { + "error": { + "name": "SyntaxError", + "message": "Worker constructor: Failed to load worker script at \"http://[\"" + }, + "activeListeners": 0, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB native asynchronous CSP Worker error reaches a pending query and explicit destruction cleans up", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-worker-error": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "state": { + "outcome": { + "errorIsNativeEvent": true, + "trusted": true, + "sameNativeError": true, + "type": "error", + "rowsAbsent": true + }, + "calls": 1, + "terminated": 1, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [ + "Error", + "[JavaScript Error: \"Content-Security-Policy: The page’s settings blocked a worker script (worker-src) at http://127.0.0.1:8084/assets/jassub/jassub-worker.js from being executed because it violates the following directive: “worker-src 'none'”\" {file: \"http://127.0.0.1:8084/test/player.html?core=candidate\" line: 0}]" + ], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=true", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "onDemandRender": true, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.1518755, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 10, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "demand", + "time": 9 + }, + { + "target": "demand", + "time": 9.0728755 + }, + { + "target": "getEvents" + }, + { + "target": "demand", + "time": 9.1251672 + } + ] + } + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=false", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "onDemandRender": false, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.1371711, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 13, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "video" + } + ], + "last": [ + { + "target": "video", + "rate": 1.5 + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual invalid Worker URL construction releases the newly created canvas and media listeners", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-construction-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "state": { + "error": { + "name": "SyntaxError", + "message": "The string did not match the expected pattern." + }, + "activeListeners": 0, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB native asynchronous CSP Worker error reaches a pending query and explicit destruction cleans up", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-worker-error": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "state": { + "outcome": { + "errorIsNativeEvent": true, + "trusted": true, + "sameNativeError": true, + "type": "error", + "rowsAbsent": true + }, + "calls": 1, + "terminated": 1, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [ + "Refused to load http://127.0.0.1:8084/assets/jassub/jassub-worker.js because it does not appear in the worker-src directive of the Content Security Policy.", + "Error: [object Event]" + ], + "failedRequests": [] + } + }, + "errors": [] + } + ] + }, + { + "file": "refactor/.cache/jassub07-native-legacy.json", + "sha256": "b6991ba5ac120243fcb5495e5035d60c1e83b92e685c8c5b9cc2a7f3e217e955", + "stats": { + "startTime": "2026-09-13T18:02:05.562Z", + "duration": 38710.349, + "expected": 21, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=true", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "onDemandRender": true, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.005034, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 9, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "canvas" + }, + { + "target": "demand", + "time": 9 + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + }, + { + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=false", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "onDemandRender": false, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.005034, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 14, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "getColorSpace" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "video", + "rate": 1.5 + }, + { + "target": "video" + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + }, + { + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual invalid Worker URL construction releases the newly created canvas and media listeners", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-construction-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "state": { + "error": { + "name": "SyntaxError", + "message": "Failed to construct 'Worker': 'http://[' is not a valid URL." + }, + "activeListeners": 0, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB native asynchronous CSP Worker error reaches a pending query and explicit destruction cleans up", + "project": "chromium", + "status": "passed", + "attachments": { + "jassub-native-worker-error": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "state": { + "outcome": { + "errorIsNativeEvent": true, + "trusted": true, + "sameNativeError": true, + "type": "error", + "rowsAbsent": true + }, + "calls": 1, + "terminated": 1, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [ + "Creating a worker from 'http://127.0.0.1:8084/assets/jassub/jassub-worker.js' violates the following Content Security Policy directive: \"worker-src 'none'\". The action has been blocked.", + "Error: [object Event]\n at e._error (:7:16961)\n at _worker.onerror (:7:5023)" + ], + "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" + } + }, + { + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=true", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "onDemandRender": true, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 9, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "canvas" + }, + { + "target": "demand", + "time": 9 + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=false", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "onDemandRender": false, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 12, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "video", + "rate": 1.5 + }, + { + "target": "video" + }, + { + "target": "getColorSpace" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual invalid Worker URL construction releases the newly created canvas and media listeners", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-construction-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "state": { + "error": { + "name": "SyntaxError", + "message": "Worker constructor: Failed to load worker script at \"http://[\"" + }, + "activeListeners": 0, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB native asynchronous CSP Worker error reaches a pending query and explicit destruction cleans up", + "project": "firefox", + "status": "passed", + "attachments": { + "jassub-native-worker-error": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "state": { + "outcome": { + "errorIsNativeEvent": true, + "trusted": true, + "sameNativeError": true, + "type": "error", + "rowsAbsent": true + }, + "calls": 1, + "terminated": 1, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [ + "Error", + "[JavaScript Error: \"Content-Security-Policy: The page’s settings blocked a worker script (worker-src) at http://127.0.0.1:8084/assets/jassub/jassub-worker.js from being executed because it violates the following directive: “worker-src 'none'”\" {file: \"http://127.0.0.1:8084/test/player.html?core=candidate\" line: 0}]" + ], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=true", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "onDemandRender": true, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.1362733, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 9, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "canvas" + }, + { + "target": "demand", + "time": 9.0282108 + }, + { + "target": "demand", + "time": 9.1117941 + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual video replacement and repeated destruction, onDemandRender=false", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-lifecycle": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "onDemandRender": false, + "state": { + "parentMatches": true, + "originalContainerCount": 0, + "time": 9.1116377, + "event": { + "Start": 0, + "Duration": 30000, + "ReadOrder": 0, + "Layer": 0, + "Style": 1, + "MarginL": 0, + "MarginR": 0, + "MarginV": 0, + "Name": "", + "Text": "Native lifecycle subtitle", + "Effect": "" + }, + "terminated": 1, + "ownedContainers": 0, + "replacementPreserved": true, + "errors": [], + "sent": { + "count": 14, + "first": [ + { + "target": "init" + }, + { + "target": "canvas" + }, + { + "target": "video" + }, + { + "target": "canvas" + } + ], + "last": [ + { + "target": "video", + "rate": 1.5 + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "getEvents" + } + ] + } + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB actual invalid Worker URL construction releases the newly created canvas and media listeners", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-construction-failure": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "state": { + "error": { + "name": "SyntaxError", + "message": "The string did not match the expected pattern." + }, + "activeListeners": 0, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB native asynchronous CSP Worker error reaches a pending query and explicit destruction cleans up", + "project": "webkit", + "status": "passed", + "attachments": { + "jassub-native-worker-error": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "state": { + "outcome": { + "errorIsNativeEvent": true, + "trusted": true, + "sameNativeError": true, + "type": "error", + "rowsAbsent": true + }, + "calls": 1, + "terminated": 1, + "ownedContainers": 0 + } + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [ + "Refused to load http://127.0.0.1:8084/assets/jassub/jassub-worker.js because it does not appear in the worker-src directive of the Content Security Policy.", + "Error: [object Event]" + ], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.506766, + "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, + "time": 10.014744, + "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, + "time": 10.128309, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 146, + "droppedFrames": 0, + "decodedFrames": 146 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 13, + "delivered": 12, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1317.9, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1301.7, + "presentedFrames": 20, + "processingDuration": 0.1003, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.128309, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 146, + "droppedFrames": 0, + "decodedFrames": 146 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 16, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + } + ] + }, + "received": { + "count": 15, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.480347, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "decodedFrames": 15 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.014678, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 141, + "droppedFrames": 0, + "decodedFrames": 141 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.108774, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 144, + "droppedFrames": 0, + "decodedFrames": 144 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 12, + "delivered": 11, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1223.6, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1208.7, + "presentedFrames": 19, + "processingDuration": 0.0925, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.108774, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 144, + "droppedFrames": 0, + "decodedFrames": 144 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 16, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + } + ] + }, + "received": { + "count": 15, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.497929, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "decodedFrames": 15 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.013603, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 141, + "droppedFrames": 0, + "decodedFrames": 141 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.114667, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 144, + "droppedFrames": 0, + "decodedFrames": 144 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 12, + "delivered": 11, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1257, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1241.2, + "presentedFrames": 19, + "processingDuration": 0.0936, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.114667, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 144, + "droppedFrames": 0, + "decodedFrames": 144 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 15, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + } + ] + }, + "received": { + "count": 14, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.335079, + "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, + "time": 10.000028, + "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, + "time": 10.040961, + "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.36, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1332.36, + "presentedFrames": 34, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040961, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 10, + "droppedFrames": 0, + "mozPresentedFrames": 10 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 15, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + } + ] + }, + "received": { + "count": 14, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.347, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 8, + "droppedFrames": 0, + "mozPresentedFrames": 8 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.00002, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 9, + "droppedFrames": 0, + "mozPresentedFrames": 9 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040404, + "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": 1328.82, + "height": 720, + "mediaTime": 10.08, + "presentationTime": 1328.82, + "presentedFrames": 33, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040404, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 10, + "droppedFrames": 0, + "mozPresentedFrames": 10 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 15, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + } + ] + }, + "received": { + "count": 14, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.569793, + "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, + "time": 10.000023, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 14, + "droppedFrames": 0, + "mozPresentedFrames": 14 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040606, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1489.02, + "height": 720, + "mediaTime": 10.08, + "presentationTime": 1489.02, + "presentedFrames": 43, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040606, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 15, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10.0355 + }, + { + "target": "canvas", + "width": 2560, + "height": 1440 + }, + { + "target": "demand", + "time": 10.4549792 + } + ] + }, + "received": { + "count": 14, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.6057501, + "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, + "time": 10.0159375, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.08625, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "presentationTime": 3149, + "expectedDisplayTime": 3569, + "width": 640, + "height": 360, + "mediaTime": 10.4549792, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.08625, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=false", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 19, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "canvas", + "width": 2560, + "height": 1440 + }, + { + "target": "demand", + "time": 10.157375 + }, + { + "target": "demand", + "time": 10.2807917 + }, + { + "target": "demand", + "time": 10.6508126 + } + ] + }, + "received": { + "count": 18, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.6121459, + "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, + "time": 10, + "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, + "time": 10.3144375, + "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": 3473, + "expectedDisplayTime": 3843, + "width": 640, + "height": 360, + "mediaTime": 10.6508126, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.3144375, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=false", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": false, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21" + }, + "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, + "sent": { + "count": 18, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10.0220833 + }, + { + "target": "demand", + "time": 10.0841459 + }, + { + "target": "canvas", + "width": 2560, + "height": 1440 + }, + { + "target": "demand", + "time": 10.5962293 + } + ] + }, + "received": { + "count": 17, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.6883335, + "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, + "time": 10.0588542, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.1305, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 14, + "delivered": 13, + "after": "function", + "lastMetadata": { + "presentationTime": 3652, + "expectedDisplayTime": 4164, + "width": 640, + "height": 360, + "mediaTime": 10.5962293, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.1305, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + } + ] + }, + { + "file": "refactor/.cache/jassub07-native-custom.json", + "sha256": "862b8c9c8c5be3ca5680f675e3c32ff2efa5dbb0dad5193ef0f916f3582855a7", + "stats": { + "startTime": "2026-09-13T18:04:14.922Z", + "duration": 27780.788, + "expected": 9, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=true", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + }, + { + "target": "demand", + "time": 10.24 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.527703, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 17, + "droppedFrames": 0, + "decodedFrames": 17 + }, + "onDemand": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.015694, + "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, + "time": 10.121897, + "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": 1401.5, + "height": 720, + "mediaTime": 10.24, + "presentationTime": 1385.6, + "presentedFrames": 21, + "processingDuration": 0.1353, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.121897, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=true", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + }, + { + "target": "demand", + "time": 10.24 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.485216, + "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, + "time": 10.016916, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 141, + "droppedFrames": 0, + "decodedFrames": 141 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.118439, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 144, + "droppedFrames": 0, + "decodedFrames": 144 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 13, + "delivered": 12, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1308.6, + "height": 720, + "mediaTime": 10.24, + "presentationTime": 1292.3, + "presentedFrames": 20, + "processingDuration": 0.1395, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.118439, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 144, + "droppedFrames": 0, + "decodedFrames": 144 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=true", + "project": "chromium", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.520582, + "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, + "time": 10.015324, + "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, + "time": 10.112666, + "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": 1310.1, + "height": 720, + "mediaTime": 10.2, + "presentationTime": 1293.9, + "presentedFrames": 20, + "processingDuration": 0.1003, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.112666, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 145, + "droppedFrames": 0, + "decodedFrames": 145 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "153.0.8010.12", + "platform": "win32", + "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" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=true", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 15, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + } + ] + }, + "received": { + "count": 14, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.563494, + "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, + "time": 10.000021, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 14, + "droppedFrames": 0, + "mozPresentedFrames": 14 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040157, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "function", + "registered": 11, + "delivered": 10, + "after": "function", + "lastMetadata": { + "expectedDisplayTime": 1859.04, + "height": 720, + "mediaTime": 10.08, + "presentationTime": 1859.04, + "presentedFrames": 43, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040157, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=true", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 16, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "demand", + "time": 10.04 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + } + ] + }, + "received": { + "count": 15, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.565891, + "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, + "time": 10.000028, + "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, + "time": 10.040412, + "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": 1446.74, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1446.74, + "presentedFrames": 44, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040412, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=true", + "project": "firefox", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 16, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + } + ] + }, + "received": { + "count": 15, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.567786, + "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, + "time": 10.000022, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 14, + "droppedFrames": 0, + "mozPresentedFrames": 14 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.042538, + "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": 1546.24, + "height": 720, + "mediaTime": 10.12, + "presentationTime": 1546.24, + "presentedFrames": 45, + "width": 1280 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.042538, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 15, + "droppedFrames": 0, + "mozPresentedFrames": 15 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with candidate core, customCanvas=true", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 16, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10.0352708 + }, + { + "target": "demand", + "time": 10.0693333 + }, + { + "target": "canvas", + "width": 2560, + "height": 1440 + }, + { + "target": "demand", + "time": 10.5030834 + } + ] + }, + "received": { + "count": 15, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.5389792, + "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, + "time": 10.04125, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.1211875, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 12, + "delivered": 11, + "after": "function", + "lastMetadata": { + "presentationTime": 3399, + "expectedDisplayTime": 3832, + "width": 640, + "height": 360, + "mediaTime": 10.5030834, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.1211875, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published core, customCanvas=true", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 18, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10.1811042 + }, + { + "target": "demand", + "time": 10.3061042 + }, + { + "target": "demand", + "time": 10.3795209 + }, + { + "target": "demand", + "time": 10.7556876 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.633521, + "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, + "time": 10.0170833, + "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, + "time": 10.3791042, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 14, + "delivered": 13, + "after": "function", + "lastMetadata": { + "presentationTime": 3522, + "expectedDisplayTime": 3898, + "width": 640, + "height": 360, + "mediaTime": 10.7556876, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.3791042, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + }, + { + "title": "JASSUB candidate artifact renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core, customCanvas=true", + "project": "webkit", + "status": "passed", + "attachments": { + "actual-jassub-evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "customCanvas": true, + "source": { + "artifact": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f" + }, + "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, + "sent": { + "count": 17, + "first": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10.0363958 + }, + { + "target": "demand", + "time": 10.113 + }, + { + "target": "canvas", + "width": 2560, + "height": 1440 + } + ] + }, + "received": { + "count": 16, + "first": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ], + "last": [ + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.6264793, + "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, + "time": 10.0599375, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + }, + { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.1616875, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + ], + "errors": [], + "frames": { + "before": "undefined", + "registered": 13, + "delivered": 12, + "after": "function", + "lastMetadata": { + "presentationTime": 2860, + "expectedDisplayTime": 2907, + "width": 640, + "height": 360, + "mediaTime": 10.113, + "presentedFrames": 0, + "processingDuration": 0 + } + }, + "lastPixel": { + "phase": "fullscreen", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.1616875, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [] + } + ] + } + ], + "originalFailure": { + "file": "refactor/.cache/jassub07-frame-probe.json", + "sha256": "70f67a08f47e21f2eca263d9e59638274d3a93d5b6b95610e8a569fd5d900a73", + "stats": { + "startTime": "2026-09-13T17:45:49.882Z", + "duration": 11594.470000000001, + "expected": 0, + "skipped": 0, + "unexpected": 1, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core, customCanvas=false", + "project": "webkit", + "status": "failed", + "attachments": { + "actual-jassub-evidence": { + "core": "candidate", + "onDemandRender": true, + "customCanvas": false, + "source": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "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": 0, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ] + } + ], + "snapshots": [], + "errors": [], + "frames": { + "before": "undefined", + "registered": 1, + "delivered": 0, + "after": "function" + }, + "lastPixel": { + "visible": 0, + "signature": 0, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 6.4950636, + "connected": true, + "media": { + "paused": false, + "readyState": 4, + "totalFrames": 0, + "droppedFrames": 0, + "decodedFrames": 0 + }, + "onDemand": true + } + }, + "limitation": "Actual worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery or device acceptance; candidate adds direct-then-host destruction." + }, + "browser-evidence": { + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + } + }, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js", + "column": 83, + "line": 139 + }, + "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 137 | }, { subContent: subtitles, onDemandRender, customCanvas })\n 138 | await page.locator('#play').click()\n> 139 | await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100)\n | ^\n 140 | const before = await page.evaluate(() => window.jassubPixels('before-seek'))\n 141 | expect(before.connected).toBe(true)\n 142 | await page.locator('#pause').click()\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:139:83" + } + ] + } + ] + }, + "intermediateFailures": [ + { + "file": "refactor/.cache/jassub07-native-lifecycle-initial.json", + "sha256": "812f63bc00f3330332e5107972ad9400ab7706ac3495145a7105cbae5f92de80", + "stats": { + "startTime": "2026-09-13T17:55:07.197Z", + "duration": 14977.993, + "expected": 4, + "skipped": 0, + "unexpected": 5, + "flaky": 0 + } + }, + { + "file": "refactor/.cache/jassub07-native-lifecycle-second.json", + "sha256": "59505a9328330dea06336ae8900af5c225bfa7fe951fec7c12b386a7999114d8", + "stats": { + "startTime": "2026-09-13T17:57:11.182Z", + "duration": 17290.656, + "expected": 9, + "skipped": 0, + "unexpected": 3, + "flaky": 0 + } + } + ], + "intermediateResolution": "WebKit initial seek before first playback reset to zero; fixture now plays first, then pauses/seeks. Native CSP blocking is asynchronous and emits a trusted plain Event; invalid Worker URL separately exercises synchronous native SyntaxError rollback. Final tests retain all failure paths, including same Event identity reaching the callback once." + }, + "installed": { + "file": "refactor/.cache/jassub-package-types-5FcWdB/report.json", + "sha256": "1a90d6a4e62f29327d723b362e75cd5e2983d5142214ff72685e470f9550f265", + "reusedRunnerTask": "PKG-JASSUB-04", + "verifiedCurrentMembers": [ + { + "name": "artplayer", + "count": 68, + "archiveSha256": "0c0ea52d41575261b836aefac68280733b119c75834ecad6d4bc78edb073f4c1" + }, + { + "name": "artplayer-plugin-jassub", + "count": 15, + "archiveSha256": "50dd61e6ae9722a1f7e7ca4b254b554330fa58477a86ed1e633c25df003054d5" + } + ], + "report": { + "task": "PKG-JASSUB-04", + "node": "v24.21.0", + "fixtures": { + "refactor/scripts/jassub-consumer.mjs": "d155926155a5bbf1ef66d55883b171aed8b448a2fbb80619df60645b17091431", + "test/types/jassub-public.ts": "2ade876801a478121c4e726fc79efa268758d51e807f1b93122bf9d96fe8b02f", + "test/types/jassub-runtime.ts": "f5e31a263c5b2793182ac9ebde944ba589f6a09b15547eeb568c6ffbf228d81a" + }, + "rootDeclarations": [ + { + "version": "1.0.0", + "candidateSha256": "0c00ce8fd60780c7c01d23534c193cedd32898dc79739e7cf13d8700a6ab7676", + "publishedSha256": "b591ab9895eef714e5bf3db17c805a2c5278c3244f6bbd614c7c02c397a0d450", + "bytesEqual": false, + "normalizedLFSha256": "b591ab9895eef714e5bf3db17c805a2c5278c3244f6bbd614c7c02c397a0d450" + }, + { + "version": "1.1.0", + "candidateSha256": "0c00ce8fd60780c7c01d23534c193cedd32898dc79739e7cf13d8700a6ab7676", + "publishedSha256": "b591ab9895eef714e5bf3db17c805a2c5278c3244f6bbd614c7c02c397a0d450", + "bytesEqual": false, + "normalizedLFSha256": "b591ab9895eef714e5bf3db17c805a2c5278c3244f6bbd614c7c02c397a0d450" + } + ], + "packages": [ + { + "name": "artplayer", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\jassub-package-types-5FcWdB\\artplayer.tgz", + "manifest": { + "name": "artplayer", + "version": "5.4.1", + "description": "ArtPlayer.js is a modern and full featured HTML5 video player", + "author": "Harvey Zhao ", + "license": "MIT", + "homepage": "https://artplayer.org", + "repository": { + "type": "git", + "url": "git+https://github.com/zhw2590582/ArtPlayer.git" + }, + "bugs": { + "url": "https://github.com/zhw2590582/ArtPlayer/issues" + }, + "keywords": [ + "html5", + "video", + "player" + ], + "exports": { + ".": { + "import": { + "types": "./types/artplayer.d.mts", + "default": "./dist/artplayer.mjs" + }, + "require": { + "types": "./types/artplayer.d.cts", + "default": "./dist/artplayer.js" + } + }, + "./types": { + "types": "./types/artplayer.d.ts" + }, + "./runtime": { + "import": { + "types": "./types/runtime.d.mts", + "default": "./dist/artplayer.mjs" + }, + "require": { + "types": "./types/runtime.d.cts", + "default": "./dist/artplayer.js" + } + }, + "./runtime/types": { + "types": "./types/runtime.d.ts" + }, + "./runtime/legacy": { + "import": { + "types": "./types/runtime.d.cts", + "default": "./dist/artplayer.legacy.js" + }, + "require": { + "types": "./types/runtime.d.cts", + "default": "./dist/artplayer.legacy.js" + } + }, + "./legacy": { + "import": { + "types": "./types/artplayer.d.cts", + "default": "./dist/artplayer.legacy.js" + }, + "require": { + "types": "./types/artplayer.d.cts", + "default": "./dist/artplayer.legacy.js" + } + }, + "./i18n/*": { + "import": { + "types": "./types/language.d.mts", + "default": "./dist/i18n/*.mjs" + }, + "require": { + "types": "./types/language.d.cts", + "default": "./dist/i18n/*.js" + } + } + }, + "main": "./dist/artplayer.js", + "module": "./dist/artplayer.mjs", + "types": "./types/artplayer.d.ts", + "typesVersions": { + "*": { + "types": [ + "types/artplayer.d.ts" + ], + "runtime": [ + "types/runtime.d.ts" + ], + "runtime/types": [ + "types/runtime.d.ts" + ], + "runtime/legacy": [ + "types/runtime.d.ts" + ], + "i18n/*": [ + "types/legacy/language.d.ts" + ], + "legacy": [ + "types/artplayer.d.ts" + ] + } + }, + "legacy": "./dist/artplayer.legacy.js", + "browserslist": "last 1 Chrome version", + "dependencies": { + "option-validator": "^2.0.6" + } + }, + "sha256": "0c0ea52d41575261b836aefac68280733b119c75834ecad6d4bc78edb073f4c1", + "files": { + "package/ARCHITECTURE.md": "bee98fc9d93a3911d58e98ef15f2dbe07e12f2274c444d989094248a22079e46", + "package/README.md": "1e185e65f67fc2081b4182bbc6c4558ea05b6e3fa3acc5dd0c3ac7df1d542b09", + "package/THIRD_PARTY_NOTICES": "b77b5b46b79fa0cda850b0781f78c3b1112c4ed9672fa291956fe8eff53098e7", + "package/dist/artplayer.js": "6dc8ca6d6eb34aa8a056cbc31328efd634530b3c5c60f0014cb7ce719fabc335", + "package/dist/artplayer.legacy.js": "80f74a8a39df4baf64cc00c65d21cd57c011bfcdead0da38eea077fbc67c5c04", + "package/dist/artplayer.mjs": "432e2b0ad92009a09743e020bf7c928c8b8f18a52496622b16259ec9ab20059f", + "package/dist/i18n/ar.js": "bd0bef65f4f7dc3f8ed562904cc6fa9071ff4537fb64c1e0fc3d554141bcd3cb", + "package/dist/i18n/ar.mjs": "59310b7bbeea83bbb7d73aadc5ca82a6ec269c3530d19aa741c94d5c64fc5da8", + "package/dist/i18n/cs.js": "6e2c93daba24c2e48dfa206bcf4f2f2a8a02713b856e404d2e4e4ecd8309ca2f", + "package/dist/i18n/cs.mjs": "c73fba18797d62d7b2581e67fe7340c35fe84510b2250ff1ecaad253d9cabbac", + "package/dist/i18n/es.js": "f4133ac6ea06b69a474b33c4a1809a4066e2376bb9464a3f3aa8330f01f1944a", + "package/dist/i18n/es.mjs": "1390bca3def2be115e811de337df0a0c3f63998e51359f283023246245c552a7", + "package/dist/i18n/fa.js": "8d3ef2e0f0cc0ea480c2167b6fcb87cdf444e90759c3519e8daa139fec576da4", + "package/dist/i18n/fa.mjs": "ab441e8a2d79043b29ceca87c783bf06874bb9d3a149c1604bc167eb481be2a9", + "package/dist/i18n/fr.js": "89dc4194a6c0a962b9ce756b52c90401c1acf3b159f265f3bc0d7d6d7d987f03", + "package/dist/i18n/fr.mjs": "99491878430fb7b6f2b7bf93da0588a831704b412e01116f23ba1d9d45863816", + "package/dist/i18n/id.js": "4c6dc86db741bcc2857df55a9d037e82e91d375fa6fd22e0562809ff621b92db", + "package/dist/i18n/id.mjs": "383e5803a6b6686c6de4e867770241de75b6f517dcca2bdedc6f2004bf6e1fb3", + "package/dist/i18n/pl.js": "3a38828b1c3d719c0aca9906cf6a46c23555a8411f8d57645f0e0ce8147dc664", + "package/dist/i18n/pl.mjs": "079ed96732979035297af387c64a7b99519b5a43107fb86735a654ea4fe8e9b5", + "package/dist/i18n/ru.js": "62fac9dddb302eb16382e6e9301fc4b2c66f5f69d4a358a26b67026a5fa4e787", + "package/dist/i18n/ru.mjs": "a893aeaee1d79409db72e324fbff200e3864f33ed168f04d91d25f7cf2efa765", + "package/dist/i18n/tr.js": "1492c36b069a7d73db4f3913bd18085faff7afd8b3c158138fe264e31600e38c", + "package/dist/i18n/tr.mjs": "a90478cac078836b76c2a98261907610fee07f3e4b28671b8e7dbf533778f918", + "package/dist/i18n/vi.js": "c7216ef7e608a79b7e2c479a07bf310d4ab79577947ac4a62d676faaced0e9cf", + "package/dist/i18n/vi.mjs": "66270fdfa0b5da396322d1769169b7df37a030ba15ac18eb30bd87b3af8074ec", + "package/dist/i18n/zh-tw.js": "11c49857d6d40a5ffd224ffa59c9026e71621b345f63c5a086326db80a22371a", + "package/dist/i18n/zh-tw.mjs": "a38e630cb96de96c15d824686aec750e25cf6c8ada01285a292442a81566314f", + "package/package.json": "82e92699e7c29847f32ac6767ece0401290e7d7a12854722bdbcfd09b90b7954", + "package/types/COMPATIBILITY.md": "a7da9e102a116104b513df4f7451247b7759bc502d3375f30bd5b14a541826e0", + "package/types/README.md": "1bd9868e773610e6312fd48a1c5aa20772d29df0af40c3db66e75b7ab4a1aee0", + "package/types/artplayer.d.cts": "3685065e4263abcfd9d953176e461d5b3eb990b0363b54689a7f55deba770fef", + "package/types/artplayer.d.mts": "b664fec51ee5c5381f2ceab25d99d52aee56feab04a2ee7b4ea5b2145ea5fa0e", + "package/types/artplayer.d.ts": "5d0162adf7ac6d0f2bb9b5cfe361f28657c4d9c540cadbf36eba3cc954ce50f8", + "package/types/component.d.ts": "ae5bc1ddeaa1ee1ebbc62573e32423993727079205c64751a5a314170e588a32", + "package/types/config.d.ts": "36f9a84cd436f17701a0d11d3417fc1fe0414e0c52c80083b94a85dfc329e37e", + "package/types/cssVar.d.ts": "93217860fff2ebf750f606d95ddd22220a58837119e63bdf2f5f78fd7488458b", + "package/types/emitter.d.ts": "7e1f9a2a8ce65feb95734f1ed52d1334e57aa388bbef2a43d8bc53a9b6f6623c", + "package/types/events.d.ts": "f83624c9268a284477e8bd793fe3562a2363b72f2a44d5c64fe8c6a6cede8830", + "package/types/i18n.d.ts": "f4f2f05bdfda73c74fef34767cb18ee8e11b743120b1c4598e9714d7846018e5", + "package/types/icons.d.ts": "326a67ba7bdcf52862c5b2a269e540140aff98d05c7b96196485a77f84fe5960", + "package/types/language.d.cts": "f7928e108cf7b7814c2d75cb0a525333e95873fca6cd6d89a740450656b9c1cd", + "package/types/language.d.mts": "6619a26d604b7cccbe2f596243d87ae37cfaae33b8b52874ec071d9787446f9e", + "package/types/legacy/language.d.ts": "c7abda4d506fd933c350e9030290bf9e64347a921d683a66115da1995ebe0a81", + "package/types/option.d.ts": "ccf2c8ddc537dc65c9682d39f044726e23bff18f476b622512d7e6a4b949b5f6", + "package/types/playback.d.ts": "58705c273df6e76a77a6575e769a9ec6c013785c25080f2551aa22415788d870", + "package/types/player.d.ts": "92b00211b70ac71336e495899df972a0519dd138111846044fe4e0f967966177", + "package/types/plugin.d.ts": "fc3594a41c51d8248713e33c486fb1f6a518ac5c7ce31be6988a6cd8d052c5f3", + "package/types/quality.d.ts": "1ac1a48b4f74ff9ba1ce60443e1463d50d1c6a2e5d87d63dc7d2a8eb7f4c2c34", + "package/types/runtime.d.cts": "7ee988dfea9a0ac71a34a656f96f7b74e78aa92547cfdfab7ff7e1139a77d4a3", + "package/types/runtime.d.mts": "dcb85f59a8b7714918ced0c1d18c2bb24e8b80b56d8c594e019dcbdb5ad88b08", + "package/types/runtime.d.ts": "9c6ccd39a1be293f17ae46dae8ea077053e12635ee79d73a5e3b3a559230fcff", + "package/types/runtime/component.d.ts": "1b4c0cee44568649e80140fe778febfcae88af0ac85515f1cbeff78ab0c6d14f", + "package/types/runtime/construction.d.ts": "7a87bedf6f7ffff0c46078aa5f1551e0077d35574e286516121f5f935e2d9ce4", + "package/types/runtime/events.d.ts": "a1e2f9d387475453eaf95c2a5ee79e228636c0bb859bda3fcc46da46574a0c31", + "package/types/runtime/media.d.ts": "8fc89401be7fe25542c169d07dd5675f10916ed20be8493e19695966cf3864a8", + "package/types/runtime/option.d.ts": "6e334e1a76fdcc40b4665c02523bd31c3994d96078fadeb40e23422aa54c1a1d", + "package/types/runtime/player.d.ts": "8ac6383502e4872e83659838844856903031a81a0adc8e8535feb78453aa042c", + "package/types/runtime/plugin.d.ts": "8528f2b17843149f42f9cc16d6fd70baa238c9dc2c329b67d2c38096799c69bd", + "package/types/runtime/services.d.ts": "604c9f8e4b6c0da818f96576bf9f4b0e96085a9003927b2f7ba30027b1ba237e", + "package/types/runtime/setting.d.ts": "349c1fcb205ad312916bd3ee55e6bf241ea8a569fb2ab6c80822e69fc518f7d7", + "package/types/runtime/subtitle.d.ts": "269ff84678d327b3a9dc2f5bc6cc0d731d1e081b85422e474621d5d792efb153", + "package/types/runtime/template.d.ts": "7a62f968a38b4bf9109a4439557c02074eb1d62ba1f6e21a8eb194e91f0badfe", + "package/types/runtime/utils.d.ts": "6bf731705d86853e066dbaf3b8d4003b6548423fe615b7412b722bd0d3a6e48b", + "package/types/setting.d.ts": "08305a75dff96f06490430e099338b463df471b550c1d0dac3abc8995b39eb09", + "package/types/subtitle.d.ts": "b18314e2d64efdb6045830b6e12f615ae2ed585416130e562329ad3ea337f6ce", + "package/types/template.d.ts": "4792bc0c2cbcb7966e4375118bd34b326166e0831862e94ace4cf6b1bbe6a8c8", + "package/types/utils.d.ts": "fca0d3d814df94a25f9eb067c86fa0cf272e2791bac793b91229d57680797712" + } + }, + { + "name": "artplayer-plugin-jassub", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\jassub-package-types-5FcWdB\\artplayer-plugin-jassub.tgz", + "manifest": { + "name": "artplayer-plugin-jassub", + "version": "1.1.0", + "description": "jassub plugin for ArtPlayer", + "author": "Harvey Zhao ", + "license": "MIT", + "homepage": "https://artplayer.org", + "repository": { + "type": "git", + "url": "git+https://github.com/zhw2590582/ArtPlayer.git" + }, + "bugs": { + "url": "https://github.com/zhw2590582/ArtPlayer/issues" + }, + "keywords": [ + "html5", + "video", + "player" + ], + "exports": { + ".": { + "types": "./types/artplayer-plugin-jassub.d.ts", + "import": "./dist/artplayer-plugin-jassub.mjs", + "require": "./dist/artplayer-plugin-jassub.js" + }, + "./legacy": { + "types": "./types/artplayer-plugin-jassub.d.ts", + "import": "./dist/artplayer-plugin-jassub.legacy.js", + "require": "./dist/artplayer-plugin-jassub.legacy.js" + }, + "./runtime": { + "import": { + "types": "./types/runtime.d.mts", + "default": "./dist/artplayer-plugin-jassub.mjs" + }, + "require": { + "types": "./types/runtime.d.cts", + "default": "./dist/artplayer-plugin-jassub.js" + } + } + }, + "main": "dist/artplayer-plugin-jassub.js", + "module": "./dist/artplayer-plugin-jassub.mjs", + "types": "types/artplayer-plugin-jassub.d.ts", + "legacy": "dist/artplayer-plugin-jassub.legacy.js", + "browserslist": "last 1 Chrome version", + "typesVersions": { + "*": { + "legacy": [ + "types/artplayer-plugin-jassub.d.ts" + ], + "runtime": [ + "types/runtime.d.ts" + ] + } + } + }, + "sha256": "50dd61e6ae9722a1f7e7ca4b254b554330fa58477a86ed1e633c25df003054d5", + "files": { + "package/ARCHITECTURE.md": "c823d5026b8b99b0c08670d4691d34539f6c0043514b8f6b3a575459a8ff8dab", + "package/README.md": "06ec53e11a1aed27c978987a126781874e569e6f211d712ee7021f776d7ea10b", + "package/dist/artplayer-plugin-jassub.js": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "package/dist/artplayer-plugin-jassub.legacy.js": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "package/dist/artplayer-plugin-jassub.mjs": "14a75ef5f82cff0524dafe453dba1cd87021fbfead2c69ed354e2fcc64b84a06", + "package/package.json": "e5098c03fb59c920ba28318348ffa5573e162d38293d0ac39c4fd74f9ee70c9b", + "package/types/README.md": "bc8e2943831aad7022c2ce0ae1cde704284da043857d5aa4acdd3fa161da63dd", + "package/types/artplayer-plugin-jassub.d.ts": "0c00ce8fd60780c7c01d23534c193cedd32898dc79739e7cf13d8700a6ab7676", + "package/types/runtime-api.d.ts": "ece90385f39c3d3dc648476bfd798dc42348c8f050742a8226870136add5cd5b", + "package/types/runtime.d.cts": "b4557e2016dd21b6ef34206ed3116e8879afe16a59ed85eaf1618846539b24bd", + "package/types/runtime.d.mts": "6ca863aed785caa1f2707ddfc4cb8456bed2465dbb0a2a1bc12e949a2ffccadc", + "package/types/runtime.d.ts": "b4557e2016dd21b6ef34206ed3116e8879afe16a59ed85eaf1618846539b24bd", + "package/worker/jassub-worker-modern.wasm": "8a8b37463713d1f2ddbc1fbb8a35889d9565e5a6670851758b46b2216d8e513c", + "package/worker/jassub-worker.js": "0fb9a83f96fc39426e733a94f3e7fe4e37099324c08328450ba032b653e85707", + "package/worker/jassub-worker.wasm": "73880d99ba9260ac39fc4476491cf254ca784c76f3af2fb03f3f9be8065ad3c0" + } + } + ], + "published": [ + { + "label": "published-1.0.0", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-jassub-1.0.0.tgz", + "sha256": "3b46d3ea149af65150cdc42f9ca5eee5fef2893a87bb9ee108050e73849811dc" + }, + { + "label": "published-1.1.0", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-jassub-1.1.0.tgz", + "sha256": "00713b4eee4b90e7dc13d1f2c88bc785f9b62f3903b00be6f7bec738fbb78c57" + } + ], + "matrix": [ + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 4, + "message": "Cannot find module 'artplayer-plugin-jassub/legacy' or its corresponding type declarations.\n There are types at '/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.cts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "raw": [ + { + "code": 2344, + "file": "consumer.mts", + "line": 10, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 11, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 11, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 12, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 12, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 18, + "message": "Type '(_option: JassubOption) => (_art: Artplayer) => { name: string; instance: JassubInstance; }' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 19, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' is not assignable to type 'OldFactory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(option: JassubOption): (art: Artplayer) => OldResult'." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 21, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 22, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' has no call signatures." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.mts", + "line": 32, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 33, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 34, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 35, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 36, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 37, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 38, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.0.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 4, + "message": "Cannot find module 'artplayer-plugin-jassub/legacy' or its corresponding type declarations." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 4, + "message": "Cannot find module 'artplayer-plugin-jassub/legacy' or its corresponding type declarations.\n There are types at '/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.cts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "raw": [ + { + "code": 2344, + "file": "consumer.mts", + "line": 10, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 11, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 11, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 12, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 12, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 18, + "message": "Type '(_option: JassubOption) => (_art: Artplayer) => { name: string; instance: JassubInstance; }' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 19, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' is not assignable to type 'OldFactory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(option: JassubOption): (art: Artplayer) => OldResult'." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 21, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 22, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' has no call signatures." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.mts", + "line": 32, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 33, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 34, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 35, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 36, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 37, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 38, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "published-1.1.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 4, + "message": "Cannot find module 'artplayer-plugin-jassub/legacy' or its corresponding type declarations." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": null + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": { + "diagnostics": [], + "negative": [ + { + "line": 74, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 74, + "message": "Type 'number' is not assignable to type 'string'." + } + ] + }, + { + "line": 76, + "diagnostics": [ + { + "code": 2353, + "file": "consumer.ts", + "line": 76, + "message": "Object literal may only specify known properties, and 'typoOption' does not exist in type 'RuntimeOption'." + } + ] + }, + { + "line": 78, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.ts", + "line": 78, + "message": "Argument of type 'boolean' is not assignable to parameter of type 'number'." + } + ] + }, + { + "line": 80, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 80, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 82, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 82, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 84, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 84, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 86, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 86, + "message": "Property 'then' does not exist on type 'RuntimeResult'." + } + ] + }, + { + "line": 88, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 88, + "message": "Type 'Promise' is not assignable to type 'void'." + } + ] + }, + { + "line": 90, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 90, + "message": "Type 'string' is not assignable to type 'number'." + } + ] + }, + { + "line": 92, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 92, + "message": "Property '_index' does not exist on type 'AssEvent'." + } + ] + }, + { + "line": 94, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 94, + "message": "Type 'AssEvent[] | undefined' is not assignable to type 'AssEvent[]'.\n Type 'undefined' is not assignable to type 'AssEvent[]'." + } + ] + }, + { + "line": 96, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.ts", + "line": 96, + "message": "Argument of type 'number' is not assignable to parameter of type 'FontSource'." + } + ] + }, + { + "line": 98, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 98, + "message": "Property 'nonexistentMethod' does not exist on type 'RuntimeInstance'." + } + ] + }, + { + "line": 100, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 100, + "message": "Type '\"gpu\"' is not assignable to type '\"js\" | \"wasm\" | undefined'." + } + ] + } + ], + "declarations": [ + "consumer.ts", + "node_modules/artplayer-plugin-jassub/types/runtime-api.d.ts", + "node_modules/artplayer-plugin-jassub/types/runtime.d.ts", + "node_modules/artplayer/types/artplayer.d.ts", + "node_modules/artplayer/types/component.d.ts", + "node_modules/artplayer/types/config.d.ts", + "node_modules/artplayer/types/cssVar.d.ts", + "node_modules/artplayer/types/emitter.d.ts", + "node_modules/artplayer/types/events.d.ts", + "node_modules/artplayer/types/i18n.d.ts", + "node_modules/artplayer/types/icons.d.ts", + "node_modules/artplayer/types/option.d.ts", + "node_modules/artplayer/types/playback.d.ts", + "node_modules/artplayer/types/player.d.ts", + "node_modules/artplayer/types/plugin.d.ts", + "node_modules/artplayer/types/quality.d.ts", + "node_modules/artplayer/types/setting.d.ts", + "node_modules/artplayer/types/subtitle.d.ts", + "node_modules/artplayer/types/template.d.ts", + "node_modules/artplayer/types/utils.d.ts" + ], + "sourceSha256": "f5e31a263c5b2793182ac9ebde944ba589f6a09b15547eeb568c6ffbf228d81a" + } + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.cts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.cts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": { + "diagnostics": [], + "negative": [ + { + "line": 74, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 74, + "message": "Type 'number' is not assignable to type 'string'." + } + ] + }, + { + "line": 76, + "diagnostics": [ + { + "code": 2353, + "file": "consumer.cts", + "line": 76, + "message": "Object literal may only specify known properties, and 'typoOption' does not exist in type 'RuntimeOption'." + } + ] + }, + { + "line": 78, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.cts", + "line": 78, + "message": "Argument of type 'boolean' is not assignable to parameter of type 'number'." + } + ] + }, + { + "line": 80, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 80, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 82, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 82, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 84, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 84, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 86, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.cts", + "line": 86, + "message": "Property 'then' does not exist on type 'RuntimeResult'." + } + ] + }, + { + "line": 88, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 88, + "message": "Type 'Promise' is not assignable to type 'void'." + } + ] + }, + { + "line": 90, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 90, + "message": "Type 'string' is not assignable to type 'number'." + } + ] + }, + { + "line": 92, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.cts", + "line": 92, + "message": "Property '_index' does not exist on type 'AssEvent'." + } + ] + }, + { + "line": 94, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 94, + "message": "Type 'AssEvent[] | undefined' is not assignable to type 'AssEvent[]'.\n Type 'undefined' is not assignable to type 'AssEvent[]'." + } + ] + }, + { + "line": 96, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.cts", + "line": 96, + "message": "Argument of type 'number' is not assignable to parameter of type 'FontSource'." + } + ] + }, + { + "line": 98, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.cts", + "line": 98, + "message": "Property 'nonexistentMethod' does not exist on type 'RuntimeInstance'." + } + ] + }, + { + "line": 100, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 100, + "message": "Type '\"gpu\"' is not assignable to type '\"js\" | \"wasm\" | undefined'." + } + ] + } + ], + "declarations": [ + "consumer.cts", + "node_modules/artplayer-plugin-jassub/types/runtime-api.d.ts", + "node_modules/artplayer-plugin-jassub/types/runtime.d.cts", + "node_modules/artplayer/types/artplayer.d.cts", + "node_modules/artplayer/types/artplayer.d.ts", + "node_modules/artplayer/types/component.d.ts", + "node_modules/artplayer/types/config.d.ts", + "node_modules/artplayer/types/cssVar.d.ts", + "node_modules/artplayer/types/emitter.d.ts", + "node_modules/artplayer/types/events.d.ts", + "node_modules/artplayer/types/i18n.d.ts", + "node_modules/artplayer/types/icons.d.ts", + "node_modules/artplayer/types/option.d.ts", + "node_modules/artplayer/types/playback.d.ts", + "node_modules/artplayer/types/player.d.ts", + "node_modules/artplayer/types/plugin.d.ts", + "node_modules/artplayer/types/quality.d.ts", + "node_modules/artplayer/types/setting.d.ts", + "node_modules/artplayer/types/subtitle.d.ts", + "node_modules/artplayer/types/template.d.ts", + "node_modules/artplayer/types/utils.d.ts" + ], + "sourceSha256": "f5e31a263c5b2793182ac9ebde944ba589f6a09b15547eeb568c6ffbf228d81a", + "commonjs": { + "esModuleInterop": false, + "diagnostics": [], + "declarations": [ + "consumer.cts", + "node_modules/artplayer-plugin-jassub/types/runtime-api.d.ts", + "node_modules/artplayer-plugin-jassub/types/runtime.d.cts", + "node_modules/artplayer/types/artplayer.d.cts", + "node_modules/artplayer/types/artplayer.d.ts", + "node_modules/artplayer/types/component.d.ts", + "node_modules/artplayer/types/config.d.ts", + "node_modules/artplayer/types/cssVar.d.ts", + "node_modules/artplayer/types/emitter.d.ts", + "node_modules/artplayer/types/events.d.ts", + "node_modules/artplayer/types/i18n.d.ts", + "node_modules/artplayer/types/icons.d.ts", + "node_modules/artplayer/types/option.d.ts", + "node_modules/artplayer/types/playback.d.ts", + "node_modules/artplayer/types/player.d.ts", + "node_modules/artplayer/types/plugin.d.ts", + "node_modules/artplayer/types/quality.d.ts", + "node_modules/artplayer/types/setting.d.ts", + "node_modules/artplayer/types/subtitle.d.ts", + "node_modules/artplayer/types/template.d.ts", + "node_modules/artplayer/types/utils.d.ts" + ], + "sourceSha256": "bdfbbfdc72847fcc44ee5a605b9e1a55f4452f84f85aa12f5991eaa7ba4092c5" + } + } + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "raw": [ + { + "code": 2344, + "file": "consumer.mts", + "line": 10, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 11, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 11, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 12, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 12, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 18, + "message": "Type '(_option: JassubOption) => (_art: Artplayer) => { name: string; instance: JassubInstance; }' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 19, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' is not assignable to type 'OldFactory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' provides no match for the signature '(option: JassubOption): (art: Artplayer) => OldResult'." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 21, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 22, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-jassub/types/artplayer-plugin-jassub\")' has no call signatures." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.mts", + "line": 32, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 33, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 34, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 35, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.mts", + "line": 36, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 37, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 38, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": { + "diagnostics": [], + "negative": [ + { + "line": 74, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 74, + "message": "Type 'number' is not assignable to type 'string'." + } + ] + }, + { + "line": 76, + "diagnostics": [ + { + "code": 2353, + "file": "consumer.mts", + "line": 76, + "message": "Object literal may only specify known properties, and 'typoOption' does not exist in type 'RuntimeOption'." + } + ] + }, + { + "line": 78, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.mts", + "line": 78, + "message": "Argument of type 'boolean' is not assignable to parameter of type 'number'." + } + ] + }, + { + "line": 80, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 80, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 82, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 82, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 84, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 84, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 86, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.mts", + "line": 86, + "message": "Property 'then' does not exist on type 'RuntimeResult'." + } + ] + }, + { + "line": 88, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 88, + "message": "Type 'Promise' is not assignable to type 'void'." + } + ] + }, + { + "line": 90, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 90, + "message": "Type 'string' is not assignable to type 'number'." + } + ] + }, + { + "line": 92, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.mts", + "line": 92, + "message": "Property '_index' does not exist on type 'AssEvent'." + } + ] + }, + { + "line": 94, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 94, + "message": "Type 'AssEvent[] | undefined' is not assignable to type 'AssEvent[]'.\n Type 'undefined' is not assignable to type 'AssEvent[]'." + } + ] + }, + { + "line": 96, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.mts", + "line": 96, + "message": "Argument of type 'number' is not assignable to parameter of type 'FontSource'." + } + ] + }, + { + "line": 98, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.mts", + "line": 98, + "message": "Property 'nonexistentMethod' does not exist on type 'RuntimeInstance'." + } + ] + }, + { + "line": 100, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 100, + "message": "Type '\"gpu\"' is not assignable to type '\"js\" | \"wasm\" | undefined'." + } + ] + } + ], + "declarations": [ + "consumer.mts", + "node_modules/artplayer-plugin-jassub/types/runtime-api.d.ts", + "node_modules/artplayer-plugin-jassub/types/runtime.d.mts", + "node_modules/artplayer/types/artplayer.d.cts", + "node_modules/artplayer/types/artplayer.d.mts", + "node_modules/artplayer/types/artplayer.d.ts", + "node_modules/artplayer/types/component.d.ts", + "node_modules/artplayer/types/config.d.ts", + "node_modules/artplayer/types/cssVar.d.ts", + "node_modules/artplayer/types/emitter.d.ts", + "node_modules/artplayer/types/events.d.ts", + "node_modules/artplayer/types/i18n.d.ts", + "node_modules/artplayer/types/icons.d.ts", + "node_modules/artplayer/types/option.d.ts", + "node_modules/artplayer/types/playback.d.ts", + "node_modules/artplayer/types/player.d.ts", + "node_modules/artplayer/types/plugin.d.ts", + "node_modules/artplayer/types/quality.d.ts", + "node_modules/artplayer/types/setting.d.ts", + "node_modules/artplayer/types/subtitle.d.ts", + "node_modules/artplayer/types/template.d.ts", + "node_modules/artplayer/types/utils.d.ts" + ], + "sourceSha256": "f5e31a263c5b2793182ac9ebde944ba589f6a09b15547eeb568c6ffbf228d81a" + } + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "bundler-esm", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": { + "diagnostics": [], + "negative": [ + { + "line": 74, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 74, + "message": "Type 'number' is not assignable to type 'string'." + } + ] + }, + { + "line": 76, + "diagnostics": [ + { + "code": 2353, + "file": "consumer.ts", + "line": 76, + "message": "Object literal may only specify known properties, and 'typoOption' does not exist in type 'RuntimeOption'." + } + ] + }, + { + "line": 78, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.ts", + "line": 78, + "message": "Argument of type 'boolean' is not assignable to parameter of type 'number'." + } + ] + }, + { + "line": 80, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 80, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 82, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 82, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 84, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 84, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 86, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 86, + "message": "Property 'then' does not exist on type 'RuntimeResult'." + } + ] + }, + { + "line": 88, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 88, + "message": "Type 'Promise' is not assignable to type 'void'." + } + ] + }, + { + "line": 90, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 90, + "message": "Type 'string' is not assignable to type 'number'." + } + ] + }, + { + "line": 92, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 92, + "message": "Property '_index' does not exist on type 'AssEvent'." + } + ] + }, + { + "line": 94, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 94, + "message": "Type 'AssEvent[] | undefined' is not assignable to type 'AssEvent[]'.\n Type 'undefined' is not assignable to type 'AssEvent[]'." + } + ] + }, + { + "line": 96, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.ts", + "line": 96, + "message": "Argument of type 'number' is not assignable to parameter of type 'FontSource'." + } + ] + }, + { + "line": 98, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 98, + "message": "Property 'nonexistentMethod' does not exist on type 'RuntimeInstance'." + } + ] + }, + { + "line": 100, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 100, + "message": "Type '\"gpu\"' is not assignable to type '\"js\" | \"wasm\" | undefined'." + } + ] + } + ], + "declarations": [ + "consumer.ts", + "node_modules/artplayer-plugin-jassub/types/runtime-api.d.ts", + "node_modules/artplayer-plugin-jassub/types/runtime.d.mts", + "node_modules/artplayer/types/artplayer.d.cts", + "node_modules/artplayer/types/artplayer.d.mts", + "node_modules/artplayer/types/artplayer.d.ts", + "node_modules/artplayer/types/component.d.ts", + "node_modules/artplayer/types/config.d.ts", + "node_modules/artplayer/types/cssVar.d.ts", + "node_modules/artplayer/types/emitter.d.ts", + "node_modules/artplayer/types/events.d.ts", + "node_modules/artplayer/types/i18n.d.ts", + "node_modules/artplayer/types/icons.d.ts", + "node_modules/artplayer/types/option.d.ts", + "node_modules/artplayer/types/playback.d.ts", + "node_modules/artplayer/types/player.d.ts", + "node_modules/artplayer/types/plugin.d.ts", + "node_modules/artplayer/types/quality.d.ts", + "node_modules/artplayer/types/setting.d.ts", + "node_modules/artplayer/types/subtitle.d.ts", + "node_modules/artplayer/types/template.d.ts", + "node_modules/artplayer/types/utils.d.ts" + ], + "sourceSha256": "f5e31a263c5b2793182ac9ebde944ba589f6a09b15547eeb568c6ffbf228d81a" + } + }, + { + "plugin": "candidate", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2554, + "file": "consumer.ts", + "line": 30, + "message": "Expected 1 arguments, but got 0." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 31, + "message": "Argument of type 'undefined' is not assignable to parameter of type 'JassubOption'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 32, + "message": "Argument of type '{ workerUrl: string; wasmUrl: string; }' is not assignable to parameter of type 'JassubOption'.\n Property 'modernWasmUrl' is missing in type '{ workerUrl: string; wasmUrl: string; }' but required in type 'JassubOption'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 33, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2345, + "file": "consumer.ts", + "line": 34, + "message": "Argument of type '640' is not assignable to parameter of type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 35, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type '\"artplayerPluginJassub\"' is not assignable to type '\"other\"'." + } + ], + "runtime": { + "diagnostics": [], + "negative": [ + { + "line": 74, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 74, + "message": "Type 'number' is not assignable to type 'string | undefined'." + } + ] + }, + { + "line": 76, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.ts", + "line": 76, + "message": "Argument of type '{ typoOption: boolean; }' is not assignable to parameter of type 'RuntimeOption'.\n Object literal may only specify known properties, and 'typoOption' does not exist in type 'RuntimeOption'." + } + ] + }, + { + "line": 78, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.ts", + "line": 78, + "message": "Argument of type 'true' is not assignable to parameter of type 'number | undefined'." + } + ] + }, + { + "line": 80, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 80, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 82, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 82, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 84, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 84, + "message": "Type 'void' is not assignable to type 'Promise'." + } + ] + }, + { + "line": 86, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 86, + "message": "Property 'then' does not exist on type 'RuntimeResult'." + } + ] + }, + { + "line": 88, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 88, + "message": "Type 'Promise' is not assignable to type 'void'." + } + ] + }, + { + "line": 90, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 90, + "message": "Type 'string' is not assignable to type 'number | undefined'." + } + ] + }, + { + "line": 92, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 92, + "message": "Property '_index' does not exist on type 'AssEvent'." + } + ] + }, + { + "line": 94, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 94, + "message": "Type 'AssEvent[] | undefined' is not assignable to type 'AssEvent[]'.\n Type 'undefined' is not assignable to type 'AssEvent[]'." + } + ] + }, + { + "line": 96, + "diagnostics": [ + { + "code": 2345, + "file": "consumer.ts", + "line": 96, + "message": "Argument of type 'number' is not assignable to parameter of type 'FontSource'." + } + ] + }, + { + "line": 98, + "diagnostics": [ + { + "code": 2339, + "file": "consumer.ts", + "line": 98, + "message": "Property 'nonexistentMethod' does not exist on type 'RuntimeInstance'." + } + ] + }, + { + "line": 100, + "diagnostics": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 100, + "message": "Type '\"gpu\"' is not assignable to type '\"js\" | \"wasm\" | undefined'." + } + ] + } + ], + "declarations": [ + "consumer.ts", + "node_modules/artplayer-plugin-jassub/types/runtime-api.d.ts", + "node_modules/artplayer-plugin-jassub/types/runtime.d.ts", + "node_modules/artplayer/types/artplayer.d.ts", + "node_modules/artplayer/types/component.d.ts", + "node_modules/artplayer/types/config.d.ts", + "node_modules/artplayer/types/cssVar.d.ts", + "node_modules/artplayer/types/emitter.d.ts", + "node_modules/artplayer/types/events.d.ts", + "node_modules/artplayer/types/i18n.d.ts", + "node_modules/artplayer/types/icons.d.ts", + "node_modules/artplayer/types/option.d.ts", + "node_modules/artplayer/types/playback.d.ts", + "node_modules/artplayer/types/player.d.ts", + "node_modules/artplayer/types/plugin.d.ts", + "node_modules/artplayer/types/quality.d.ts", + "node_modules/artplayer/types/setting.d.ts", + "node_modules/artplayer/types/subtitle.d.ts", + "node_modules/artplayer/types/template.d.ts", + "node_modules/artplayer/types/utils.d.ts" + ], + "sourceSha256": "f5e31a263c5b2793182ac9ebde944ba589f6a09b15547eeb568c6ffbf228d81a", + "commonjs": { + "esModuleInterop": false, + "diagnostics": [], + "declarations": [ + "consumer.ts", + "node_modules/artplayer-plugin-jassub/types/runtime-api.d.ts", + "node_modules/artplayer-plugin-jassub/types/runtime.d.ts", + "node_modules/artplayer/types/artplayer.d.ts", + "node_modules/artplayer/types/component.d.ts", + "node_modules/artplayer/types/config.d.ts", + "node_modules/artplayer/types/cssVar.d.ts", + "node_modules/artplayer/types/emitter.d.ts", + "node_modules/artplayer/types/events.d.ts", + "node_modules/artplayer/types/i18n.d.ts", + "node_modules/artplayer/types/icons.d.ts", + "node_modules/artplayer/types/option.d.ts", + "node_modules/artplayer/types/playback.d.ts", + "node_modules/artplayer/types/player.d.ts", + "node_modules/artplayer/types/plugin.d.ts", + "node_modules/artplayer/types/quality.d.ts", + "node_modules/artplayer/types/setting.d.ts", + "node_modules/artplayer/types/subtitle.d.ts", + "node_modules/artplayer/types/template.d.ts", + "node_modules/artplayer/types/utils.d.ts" + ], + "sourceSha256": "bdfbbfdc72847fcc44ee5a605b9e1a55f4452f84f85aa12f5991eaa7ba4092c5" + } + } + } + ], + "scope": "Actual npm 1.0.0/1.1.0 and Yarn-packed candidate/core installed outside workspace; offline installation and forced frozen reinstall with unchanged lock, strictly verified installed member hashes and non-link checks; root declaration text unchanged after CRLF normalization with both raw hashes recorded, exact old diagnostics, conditional accurate runtime declarations and Node lazy factory/legacy identity. Registrars are never invoked in Node; this is not browser Worker/WASM/font rendering or publication acceptance." + } + }, + "artifacts": [ + { + "file": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "d20f3918a60f63476775eb3bdf18730e53364b1499be87f3bec238310e457f3f", + "baselineCommit": "5e445b1af655a1cafbfa3abca62d45207c042936", + "before": { + "bytes": 16407, + "gzip": 5434, + "brotli": 4771 + }, + "after": { + "bytes": 18325, + "gzip": 6045, + "brotli": 5334 + }, + "docsIdentical": true + }, + { + "file": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "1d9dcad94b53f7e04d0c3fb7ceff3ed096c81d62ab3b471b6b3277f948644a21", + "baselineCommit": "5e445b1af655a1cafbfa3abca62d45207c042936", + "before": { + "bytes": 17129, + "gzip": 5716, + "brotli": 5041 + }, + "after": { + "bytes": 19261, + "gzip": 6385, + "brotli": 5641 + }, + "docsIdentical": true + }, + { + "file": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs", + "sha256": "14a75ef5f82cff0524dafe453dba1cd87021fbfead2c69ed354e2fcc64b84a06", + "baselineCommit": "5e445b1af655a1cafbfa3abca62d45207c042936", + "before": { + "bytes": 34199, + "gzip": 10146, + "brotli": 8695 + }, + "after": { + "bytes": 38509, + "gzip": 11117, + "brotli": 9508 + }, + "docsIdentical": true + } + ], + "remaining": [ + "PKG-JASSUB-05 full core/combination/device/error-recovery acceptance", + "PKG-JASSUB-06 distribution and notices, VENDOR-04/05 remain open", + "Default offscreen path, physical devices and sustained memory/GPU release", + "Same-target concurrent queries retain the existing target-matching protocol; no new request IDs or pre-ready query queue" + ] +} diff --git a/refactor/baselines/jassub-vendor.patch b/refactor/baselines/jassub-vendor.patch new file mode 100644 index 000000000..0df8d4565 --- /dev/null +++ b/refactor/baselines/jassub-vendor.patch @@ -0,0 +1,373 @@ +diff --git a/packages/artplayer-plugin-jassub/src/jassub.es.js b/packages/artplayer-plugin-jassub/src/jassub.es.js +index f5a8f292c..904b40888 100644 +--- a/packages/artplayer-plugin-jassub/src/jassub.es.js ++++ b/packages/artplayer-plugin-jassub/src/jassub.es.js +@@ -1,25 +1,50 @@ + /* eslint-disable */ +-typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function (c) { +- const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames, r = (n, a) => { +- const i = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || i.totalVideoFrames - i.droppedVideoFrames; +- if (h > s) { +- const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l = a - n; +- c(a, { +- presentationTime: a + d * 1e3, +- expectedDisplayTime: a + l, +- width: this.videoWidth, +- height: this.videoHeight, +- mediaTime: Math.max(0, this.currentTime || 0) + l / 1e3, +- presentedFrames: h, +- processingDuration: d +- }), delete this._rvfcpolyfillmap[e]; +- } else +- this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => r(a, d)); ++if (typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype) { ++ HTMLVideoElement.prototype.requestVideoFrameCallback = function (callback) { ++ if (!Object.prototype.hasOwnProperty.call(this, "_rvfcpolyfillmap")) ++ this._rvfcpolyfillmap = {}; ++ const id = this._rvfcpolyfillid = (this._rvfcpolyfillid || 0) + 1; ++ const started = performance.now(), initial = this.getVideoPlaybackQuality(), initialTime = this.currentTime; ++ const initialFrames = this.mozPresentedFrames || this.mozPaintedFrames || initial.totalVideoFrames - initial.droppedVideoFrames; ++ const poll = (previous, now) => { ++ if (!(id in this._rvfcpolyfillmap)) return; ++ const quality = this.getVideoPlaybackQuality(); ++ const frames = this.mozPresentedFrames || this.mozPaintedFrames || quality.totalVideoFrames - quality.droppedVideoFrames; ++ const advanced = frames > initialFrames; ++ // Some WebKit backends expose quality counters but keep both at zero. ++ // Approximate media-time delivery only there, capped at 30 Hz; never invent frame counts. ++ const mediaFallback = initialFrames === 0 && frames === 0 ++ && initial.totalVideoFrames === 0 && initial.droppedVideoFrames === 0 ++ && quality.totalVideoFrames === 0 && quality.droppedVideoFrames === 0 ++ && Number.isFinite(this.currentTime) && this.currentTime !== initialTime ++ && this.readyState >= 2 && !this.seeking && now - started >= 1000 / 30; ++ if (advanced || mediaFallback) { ++ const delay = this.mozFrameDelay || quality.totalFrameDelay - initial.totalFrameDelay || 0; ++ const elapsed = now - previous; ++ delete this._rvfcpolyfillmap[id]; ++ callback(now, { ++ presentationTime: now + delay * 1e3, ++ expectedDisplayTime: now + elapsed, ++ width: this.videoWidth, ++ height: this.videoHeight, ++ mediaTime: mediaFallback && !advanced ? Math.max(0, this.currentTime) : Math.max(0, this.currentTime || 0) + elapsed / 1e3, ++ presentedFrames: frames, ++ processingDuration: delay ++ }); ++ } else { ++ this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(now, time)); ++ } ++ }; ++ this._rvfcpolyfillmap[id] = requestAnimationFrame((time) => poll(started, time)); ++ return id; + }; +- return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) => r(e, n)), e; +-}, HTMLVideoElement.prototype.cancelVideoFrameCallback = function (c) { +- cancelAnimationFrame(this._rvfcpolyfillmap[c]), delete this._rvfcpolyfillmap[c]; +-}); ++ HTMLVideoElement.prototype.cancelVideoFrameCallback = function (id) { ++ const callbacks = this._rvfcpolyfillmap; ++ if (!callbacks || !(id in callbacks)) return; ++ cancelAnimationFrame(callbacks[id]); ++ delete callbacks[id]; ++ }; ++} + const _ = { + bt709: "BT709", + // these might not be exactly correct? oops? +@@ -74,7 +99,11 @@ class o extends EventTarget { + * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate). + */ + constructor(e) { +- if (super(), !globalThis.Worker) ++ super(); ++ this._requests = new Set(); ++ let t; ++ try { ++ if (!globalThis.Worker) + throw this.destroy("Worker not supported"); + if (!e) + throw this.destroy("No options provided"); +@@ -82,14 +111,15 @@ class o extends EventTarget { + new Promise((s) => { + this._init = s; + }); +- const t = o._test(); ++ t = o._test(); + if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? !0), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? !0), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas) + this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._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 = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = !1, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { ++ this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas, this._ctx = !this._offscreenRender && this._canvasctrl.getContext("2d"), this._lastRenderTime = 0, this.debug = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = () => { if (!this._destroyed) this.setRate(this._video.playbackRate); }, this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = !1, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => { ++ if (this._destroyed) return; + this._worker.postMessage({ + target: "init", + wasmUrl: o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm", +@@ -114,7 +144,14 @@ class o extends EventTarget { + useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? !0), + hasBitmapBug: o._hasBitmapBug + }), this._offscreenRender === !0 && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]); ++ }).catch((error) => { ++ if (!this._destroyed) this.destroy(error); + }); ++ } catch (error) { ++ t?.catch(() => {}); ++ try { this.destroy(); } catch {} ++ throw error; ++ } + } + _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; +@@ -182,6 +219,7 @@ class o extends EventTarget { + * @param {Boolean} [force=false] + */ + resize(e = 0, t = 0, s = 0, r = 0, n = this._video?.paused) { ++ if (this._destroyed) return; + if ((!e || !t) && this._video) { + const a = this._getVideoPosition(); + let i = null; +@@ -192,7 +230,7 @@ class o extends EventTarget { + i = this._computeCanvasSize(a.width || 0, a.height || 0); + e = i.width, t = i.height, this._canvasParent && (s = a.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), r = 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 = r + "px", n && this.busy === !1 ? this.busy = !0 : n = !1, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video.videoWidth, videoHeight: this._videoHeight || this._video.videoHeight, force: n }); ++ this._canvas.style.top = s + "px", this._canvas.style.left = r + "px", n && this.busy === !1 ? this.busy = !0 : n = !1, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video?.videoWidth || e, videoHeight: this._videoHeight || this._video?.videoHeight || t, force: n }); + } + _getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) { + const s = e / t, { offsetWidth: r, offsetHeight: n } = this._video, a = r / n; +@@ -212,6 +250,7 @@ class o extends EventTarget { + return { width: e, height: t }; + } + _timeupdate({ type: e }) { ++ if (this._destroyed) return; + const s = { + seeking: !0, + waiting: !0, +@@ -224,7 +263,34 @@ class o extends EventTarget { + * @param {HTMLVideoElement} video + */ + setVideo(e) { +- e instanceof HTMLVideoElement ? (this._removeListeners(), this._video = e, this._onDemandRender ? this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)) : (this._playstate = e.paused, e.addEventListener("timeupdate", this._boundTimeUpdate, !1), e.addEventListener("progress", this._boundTimeUpdate, !1), e.addEventListener("waiting", this._boundTimeUpdate, !1), e.addEventListener("seeking", this._boundTimeUpdate, !1), e.addEventListener("playing", this._boundTimeUpdate, !1), e.addEventListener("ratechange", this._boundSetRate, !1), e.addEventListener("resize", this._boundResize, !1)), "VideoFrame" in window && (e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, !1), e.readyState > 2 && this._updateColorSpace()), e.videoWidth > 0 && this.resize(), typeof ResizeObserver < "u" && (this._ro || (this._ro = new ResizeObserver(() => this.resize())), this._ro.observe(e))) : this._error("Video element invalid!"); ++ if (this._destroyed) return; ++ if (!(e instanceof HTMLVideoElement)) { ++ this._error("Video element invalid!"); ++ return; ++ } ++ this._removeListeners(); ++ this._video = e; ++ this._videoWidth = this._videoHeight = 0; ++ this._videoColorSpace = null; ++ if (this._canvasParent && this._canvasParent.parentNode !== e.parentNode) ++ e.insertAdjacentElement("afterend", this._canvasParent); ++ if (this._onDemandRender) this._requestFrame(); ++ else { ++ this._playstate = e.paused; ++ for (const name of ["timeupdate", "progress", "waiting", "seeking", "playing"]) ++ e.addEventListener(name, this._boundTimeUpdate, false); ++ e.addEventListener("ratechange", this._boundSetRate, false); ++ e.addEventListener("resize", this._boundResize, false); ++ } ++ if ("VideoFrame" in window) { ++ e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, false); ++ if (e.readyState > 2) this._updateColorSpace(); ++ } ++ if (e.videoWidth > 0) this.resize(); ++ if (typeof ResizeObserver < "u") { ++ this._ro || (this._ro = new ResizeObserver(() => this.resize())); ++ this._ro.observe(e); ++ } + } + runBenchmark() { + this.sendMessage("runBenchmark"); +@@ -316,7 +382,7 @@ class o extends EventTarget { + getEvents(e) { + this._fetchFromWorker({ + target: "getEvents" +- }, (t, { events: s }) => { ++ }, (t, { events: s } = {}) => { + e(t, s); + }); + } +@@ -391,7 +457,7 @@ class o extends EventTarget { + getStyles(e) { + this._fetchFromWorker({ + target: "getStyles" +- }, (t, { styles: s }) => { ++ }, (t, { styles: s } = {}) => { + e(t, s); + }); + } +@@ -433,12 +499,22 @@ class o extends EventTarget { + } + } + _unbusy() { ++ if (this._destroyed) return; + this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = !1; + } + _handleRVFC(e, { mediaTime: t, width: s, height: r }) { + if (this._destroyed) + return null; +- this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: r })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)); ++ this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: r })), this._requestFrame(); ++ } ++ _requestFrame() { ++ if (this._destroyed || !this._video || this._frameCallback != null) return; ++ const video = this._video, generation = this._videoGeneration; ++ this._frameCallback = video.requestVideoFrameCallback((time, metadata) => { ++ if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; ++ this._frameCallback = null; ++ this._handleRVFC(time, metadata); ++ }); + } + _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 }); +@@ -456,12 +532,20 @@ class o extends EventTarget { + this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas.transferControlToOffscreen(), this._ctx = !1, this.sendMessage("offscreenCanvas", null, [this._canvasctrl]), this.resize(0, 0, 0, 0, !0); + } + _updateColorSpace() { +- this._video.requestVideoFrameCallback(() => { ++ if (this._destroyed || this._colorCallback != null) return; ++ const video = this._video, generation = this._videoGeneration; ++ this._colorCallback = video.requestVideoFrameCallback(() => { ++ if (this._destroyed || generation !== this._videoGeneration || video !== this._video) return; ++ this._colorCallback = null; ++ let frame; + try { +- const e = new VideoFrame(this._video); +- this._videoColorSpace = _[e.colorSpace.matrix], e.close(), this.sendMessage("getColorSpace"); ++ frame = new VideoFrame(video); ++ this._videoColorSpace = _[frame.colorSpace.matrix]; ++ this.sendMessage("getColorSpace"); + } catch (e) { + console.warn(e); ++ } finally { ++ frame?.close(); + } + }); + } +@@ -495,6 +579,7 @@ class o extends EventTarget { + return e; + } + _ready() { ++ if (this._destroyed) return; + this._init(), this.dispatchEvent(new CustomEvent("ready")); + } + /** +@@ -504,7 +589,10 @@ class o extends EventTarget { + * @param {Transferable[]} [transferable] Array of transferables. + */ + async sendMessage(e, t = {}, s) { +- await this._loaded, s ? this._worker.postMessage({ ++ if (this._destroyed) return; ++ await this._loaded; ++ if (this._destroyed) return; ++ s ? this._worker.postMessage({ + target: e, + transferable: s, + ...t +@@ -514,23 +602,51 @@ class o extends EventTarget { + }); + } + _fetchFromWorker(e, t) { ++ if (this._destroyed) { ++ t(new Error("JASSUB instance destroyed")); ++ return; ++ } ++ const worker = this._worker; ++ let timer, settled = false; ++ const cleanup = () => { ++ worker.removeEventListener("message", onMessage); ++ worker.removeEventListener("error", onError); ++ clearTimeout(timer); ++ this._requests.delete(cancel); ++ }; ++ const finish = (error, data) => { ++ if (settled) return; ++ settled = true; ++ cleanup(); ++ t(error, data); ++ }; ++ const onMessage = ({ data }) => { if (data.target === e.target) finish(null, data); }; ++ const onError = (error) => finish(error); ++ const cancel = () => { ++ if (settled) return; ++ settled = true; ++ cleanup(); ++ return () => t(new Error("JASSUB instance destroyed")); ++ }; + try { +- const s = e.target, r = setTimeout(() => { +- a(new Error("Error: Timeout while try to fetch " + s)); +- }, 5e3), n = ({ data: i }) => { +- i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r)); +- }, a = (i) => { +- t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r); +- }; +- this._worker.addEventListener("message", n), this._worker.addEventListener("error", a), this._worker.postMessage(e); ++ this._requests.add(cancel); ++ worker.addEventListener("message", onMessage); ++ worker.addEventListener("error", onError); ++ timer = setTimeout(() => onError(new Error("Error: Timeout while try to fetch " + e.target)), 5e3); ++ worker.postMessage(e); + } catch (s) { +- this._error(s); ++ if (settled) throw s; ++ finish(s); + } + } + _console({ content: e, command: t }) { + console[t].apply(console, JSON.parse(e)); + } + _onmessage({ data: e }) { ++ if (this._destroyed) { ++ for (const image of e.images || []) image.image?.close?.(); ++ return; ++ } + this["_" + e.target] && this["_" + e.target](e); + } + _error(e) { +@@ -538,6 +654,12 @@ class o extends EventTarget { + return this.dispatchEvent(s), console.error(t), t; + } + _removeListeners() { ++ this._videoGeneration = (this._videoGeneration || 0) + 1; ++ for (const id of [this._frameCallback, this._colorCallback]) ++ if (id != null) this._video?.cancelVideoFrameCallback?.(id); ++ this._frameCallback = this._colorCallback = null; ++ this._lastDemandTime = null; ++ if (this._onDemandRender) this.busy = false; + 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)); + } + /** +@@ -545,7 +667,29 @@ class o extends EventTarget { + * @param {String|Error} [err] Error to throw when destroying. + */ + destroy(e) { +- return e && (e = this._error(e)), this._video && this._canvasParent && this._video.parentNode?.removeChild(this._canvasParent), this._destroyed = !0, this._removeListeners(), this.sendMessage("destroy"), this._worker?.terminate(), e; ++ if (this._destroyed) return e && this._error(e); ++ this._destroyed = true; ++ let failure, failed = false; ++ const attempt = (callback) => { ++ try { callback(); } catch (error) { if (!failed) { failure = error; failed = true; } } ++ }; ++ if (e) attempt(() => { e = this._error(e); }); ++ attempt(() => this._removeListeners()); ++ attempt(() => this._ro?.disconnect()); ++ attempt(() => this._canvasParent?.remove()); ++ this._init?.(); ++ const callbacks = []; ++ for (const cancel of [...this._requests || []]) attempt(() => { ++ const notify = cancel(); ++ if (notify) callbacks.push(notify); ++ }); ++ for (const notify of callbacks) attempt(notify); ++ if (this._worker) { ++ this._worker.onmessage = this._worker.onerror = null; ++ attempt(() => this._worker.terminate()); ++ } ++ if (failed) throw failure; ++ return e; + } + } + export { diff --git a/refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md b/refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md new file mode 100644 index 000000000..3ce88543e --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md @@ -0,0 +1,115 @@ +# PKG-JASSUB-07 生命周期与字幕时钟修复 + +## 变更与兼容边界 + +本任务修改已登记的第三方 wrapper `src/jassub.es.js`,保留 vendor JS 例外, +不把第三方生成代码机械迁为 TS。自有入口和 registration 继续由两个严格 TS +模块负责。原 jassub 1.8.8 来源记录保留,新增可反向校验的 +[补丁](../baselines/jassub-vendor.patch)及[来源指纹](../baselines/jassub-vendor-patch.json), +third-party.json 同时注明原始与本地修改后的身份,防止后续更新覆盖修复。 +Worker JS、WASM、字体、资源 URL 和旧根声明均未修改。没有新增依赖或锁文件变更。 + +公开工厂仍同步返回原生 JASSUB 实例;resize/setVideo/setTrack/destroy 的同步 +返回值、字幕协议和实例方法保留。本次修复 API-05/08/10/12 的已复现缺陷: + +- 构造任一阶段抛错时,回收此前已创建的 DOM、监听器、帧回调和 Worker,重新 + 抛出原始异常;异步能力检测不再向已销毁实例发送初始化消息。 +- destroy 首先进入终态,随后尽力完成所有清理,重复调用安全;删除实际归属的 + 字幕容器,保留调用者 canvas/video。排队但尚未发送的消息取消,其 Promise + 正常结束,不再向已经 terminate 的 Worker 发消息。 +- setVideo 转移自建容器并释放旧视频订阅。render/color-space 回调分别记录归属 + 和代次,即使旧回调已经排队,也不能重新启动旧循环或传入旧字幕时间。 +- 事件时钟的 ratechange 读取当前视频的数值 playbackRate,直接 setRate(number) + 保持原样,不再把 Event 对象发送给 Worker。 +- getEvents/getStyles 的 timeout/Worker error 缺少数据时不再先解构 undefined。 + 每请求监听器与定时器在回调前释放,过期事件不重复通知。销毁先清理全部请求, + 再各通知一次取消错误;某个用户回调抛出异常仍完成其他清理和 Worker 终止, + 最后重新抛出第一个异常。正常查询仍返回真实 WASM 数据。 + +原生 CSP 错误在三个浏览器中均观察到 trusted plain Event,并不保证是 ErrorEvent。 +因此新增可选 `/runtime` 的准确回调类型使用 `Error | Event | null`;旧根声明完全 +保持,不要求旧入口消费者修改。此入口的迁移说明和 sendMessage 取消语义在 +包内 types/README.md 更新。Worker 加载错误沿已有 error 通道送达,调用者仍可 +显式销毁;本任务不承诺自动重试、全量错误恢复或新的查询请求 ID 协议。 + +## Windows WebKit 默认字幕停滞 + +冻结 npm 1.1.0 的真实播放中,currentTime 已到 6.49 秒且 readyState=4, +totalVideoFrames/droppedVideoFrames 始终为零。浏览器没有原生 RVFC,旧 polyfill +注册一次但不交付,Worker 仅产生空绘制,没有任何字幕 demand。 + +修复只针对 polyfill 中初始和当前质量计数均为零的情况:媒体时间必须有限且 +确实变化、readyState >= 2、没有正在 seek;按当前媒体时间交付近似回调,并限制 +每条连续注册链不超过 30 Hz。暂停后完成 seek 可交付,静止暂停不持续交付。 +presentedFrames 仍如实为零,不虚构解码帧。原生 RVFC 和正常增长的质量计数路径 +保留,后者不受该近似限速影响。独立视频的 map 和单调 ID 同时修复相同时间戳 +覆盖回调、跨视频取消和回调异常后未清理的问题。 + +这是有运行证据的局部兼容修复,不是全局强制 onDemandRender=false。实际默认 +onDemandRender=true 的播放、拖动、网页全屏、自定义 canvas 都已验证;所有这些 +绘制验证显式 offscreenRender=false,不能替代默认 offscreen、设备和 GPU 验收。 + +## 验证与中间失败 + +[验证记录](../baselines/jassub-vendor-validation.json)保留输入 hash、原始日志位置、 +安装报告和浏览器 JSON 摘要;本地 cache 不作为唯一永久证据。 + +| 层次 | 本次结果及范围 | +| --- | --- | +| 生命周期回归 | 同一 21 项在冻结旧产物全部失败,候选全部通过 | +| Polyfill 回归 | 旧产物 13 通过/11 失败,候选 24 全通过;涵盖 144 Hz 模拟时钟、原生与正常计数不变、多视频和暂停 seek | +| 联合测试 | 185 项通过,含旧行为基线、候选修复、真实 WASM 查询、类型、来源和可逆补丁校验 | +| 源码与分发 | 源码、main、legacy 各 68 项通过;正常三格式构建及 docs 副本一致 | +| 严格类型 | 全仓 406 个生产 TS 文件及既有消费者流程通过 | +| 实际安装 | 15 个编译器/包矩阵通过,准确入口每模式 14 个负例、两种无 interop 消费通过;核心 68/JASSUB 15 个安装成员与最终工作区复核一致 | +| 真实浏览器 | Windows Playwright Chromium 153.0.8010.12、Firefox 155、WebKit 26.6,main 默认 9、main 生命周期 12、legacy 21、main 自定义 canvas 9,共 51 项通过,无跳过或重试通过 | + +浏览器播放组合包含候选核心、实际 5.4.0 和 5.3.1-beta.1;生命周期错误测试使用 +候选核心。原生 Worker/WASM/font/video 真实运行,验证字幕像素、切视频、数值倍率、 +重复销毁、非法 Worker URL 的同步 SyntaxError 回滚,以及 CSP 异步错误原对象 +向待处理查询交付一次。没有冒充 Chrome 扩展交互或物理 Safari/iOS/Android 验收。 + +保留三个测试编写过程中发现的问题及修正依据: + +1. WebKit 首次播放前 seek 会回到零。fixture 先真实播放,再暂停/seek,随后仍检查 + 播放进度和非透明字幕像素,未删除 WebKit 或放松成功条件。 +2. CSP 不是同步 SecurityError,回调对象也不保证 ErrorEvent。拆出实际非法 URL + 构造异常测试,并保留 CSP 异步错误及严格对象身份检查。 +3. 旧“同步返回值与 resize”测试在立即 destroy 后才检查排队消息。现在销毁前确认 + resize 尺寸、原同步返回值;额外旧红/新绿测试确认 ready 后排队消息在销毁时取消。 + 初次候选 66/67 的失败日志保留,最终源码及两格式各 68 通过。 + +受控 DOM helper 的 appendChild 同步修正为先从旧父节点移除,与真实 DOM 移动 +语义一致;跨容器结论同时有真实浏览器验证。没有以 mock 取代真实播放。 + +main gzip 从 5,434 增至 6,045 字节(+611),legacy 从 5,716 增至 6,385(+669)。 +新增成本用于资源归属、清理和时钟回退;完整原始/gzip/Brotli 数值存于验证记录, +不因此调整其他全项目性能门槛。安装验证复用 04 runner,报告保留原任务标识, +07 包装记录明确其最终候选来源。 + +## 复跑、维护与后续 + +使用 .node-version 的 Node 和 Yarn Classic 1.22.22: + +```sh +yarn test:jassub +yarn typecheck +yarn test:jassub-types-package +yarn build artplayer-plugin-jassub +node --test refactor/scripts/jassub-vendor-patch.test.mjs +``` + +test:unit 纳入生命周期/polyfill;test:jassub 还纳入补丁身份校验,baseline glob +自动覆盖该来源测试。候选源码与 main/legacy 的环境变量用法见包内 ARCHITECTURE.md。 +浏览器设置 ARTPLAYER_JASSUB_ARTIFACT 为正常构建,再运行 +`yarn test:browser test/browser/jassub-native.spec.js test/browser/jassub-lifecycle.spec.js --workers=1`; +自定义 canvas 另设 ARTPLAYER_JASSUB_CUSTOM_CANVAS=true。浏览器共享报告每次运行后先归档。 + +六项本任务风险已用对应证据关闭。05 继续完整组合、真实设备、默认 offscreen 和 +错误恢复;06 与 SITE-01 保留 VENDOR-04/05 的通知和字体分发门槛。公开类型/导出 +的更广泛准入风险不在本补丁任务一并关闭。仍需全部独立 major、CI/CD 与三轮发布 +复盘,不推送或发布。 + +回退本任务 commit 并正常构建即可恢复原 wrapper 与声明;先停止/销毁现有实例, +不对运行中的 Worker 热替换。上游升级应重新核对原始来源、逐项移植或确认上游 +已修复,再运行红绿、类型、安装和原生验证,不能只替换文件并更新指纹。 diff --git a/refactor/plan.md b/refactor/plan.md index 5dde78a03..a7c7efe16 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 232 项,范围 22 个包及工作区/示例。 -状态:todo 63 / doing 14 / blocked 0 / done 155 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 62 / doing 14 / blocked 0 / done 156 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -235,7 +235,7 @@ | 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 | 真实 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 | 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 | ## 5 包迁移:artplayer-plugin-danmuku-mask @@ -536,6 +536,7 @@ - PKG-JASSUB-02: [记录](changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md) [记录](baselines/jassub-failures-validation.json) - PKG-JASSUB-03: [记录](changes/2026-09-14-PKG-JASSUB-03-registration.md) [记录](baselines/jassub-registration-validation.json) - PKG-JASSUB-04: [记录](changes/2026-09-14-PKG-JASSUB-04-types.md) [记录](baselines/jassub-types-validation.json) +- PKG-JASSUB-07: [记录](changes/2026-09-14-PKG-JASSUB-07-vendor.md) [记录](baselines/jassub-vendor-validation.json) [记录](baselines/jassub-vendor-patch.json) - PKG-MASK-01: [记录](baselines/danmuku-mask-release.json) [记录](baselines/danmuku-mask-registry.json) [记录](baselines/danmuku-mask-contract.md) [记录](baselines/danmuku-mask-contract-validation.json) [记录](changes/2026-09-13-PKG-MASK-01-contract.md) - PKG-MASK-02: [记录](changes/2026-09-13-PKG-MASK-02-failures.md) [记录](baselines/danmuku-mask-failures-validation.json) - PKG-MASK-03: [记录](changes/2026-09-13-PKG-MASK-03-lifecycle.md) [记录](baselines/danmuku-mask-lifecycle-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index a5d2befaa..683fa8f01 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,17 @@ # 进度与证据 +## PKG-JASSUB-07 vendor 生命周期与默认字幕时钟完成 + +独立补丁保留原上游身份,修复幂等销毁、构造回滚、跨视频帧归属、数值倍率和 +查询异常清理;Windows WebKit 全零质量计数的默认逐帧停滞有旧失败及候选修复。 +联合 185 项、源码/main/legacy 各 68 项、406 生产 TS 检查通过;实际安装 15 个 +类型矩阵,核心 68/JASSUB 15 成员与候选相符。真实浏览器 51 项通过,包括默认 +onDemand 播放/seek、自定义 canvas、跨父节点换视频、原生构造与 CSP 错误。 +[变更与限制](changes/2026-09-14-PKG-JASSUB-07-vendor.md)和 +[验证记录](baselines/jassub-vendor-validation.json)保留测试时序/CSP/WebKit 的中间 +失败及修正,不冒充设备、默认 offscreen 或完整发布验收。关闭六项已证实风险, +05/06、VENDOR-04/05 及全局三轮复盘仍保留。下一步继续分包兼容验收与未完成迁移。 + ## PKG-JASSUB-04 自有 TS 与准确类型入口完成 两个自有模块已迁为严格 TS,编译后的 JS 和三种正常构建产物保持上一任务行为。 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index d3f5b2b64..67b2d9b00 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -242,10 +242,10 @@ | DANMUKU-TIMING-01 | resolved / 已复现 | Hidden Worker wait is deducted from visible lifetime and can recycle a comment immediately after visible | PKG-DANMUKU-11 | | DANMUKU-SAMPLING-01 | resolved / 已复现 | Serial asynchronous placement stops native frame eligibility sampling and delays existing visible expiry | PKG-DANMUKU-12 | | MASK-SCHEDULING-01 | open / 已复现 | Native Mask startup can coincide with a missed narrow Danmuku timestamp window | PKG-MASK-05 | -| JASSUB-DESTROY-01 | open / 已复现 | Repeated destruction or cross-parent setVideo can throw before JASSUB cleanup | PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-07 | +| JASSUB-DESTROY-01 | resolved / 已复现 | Repeated destruction or cross-parent setVideo can throw before JASSUB cleanup | PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-07 | | JASSUB-CANVAS-01 | resolved / 已复现 | Custom canvas registration throws after JASSUB Worker allocation | PKG-JASSUB-02, PKG-JASSUB-03 | -| JASSUB-CONSTRUCTION-01 | open / 已复现 | Worker construction failure leaves previously allocated JASSUB DOM and listeners | PKG-JASSUB-02, PKG-JASSUB-07 | -| JASSUB-VIDEO-FRAME-01 | open / 已复现 | Old video frame callbacks can drive replacement subtitle time and duplicate chains | PKG-JASSUB-02, PKG-JASSUB-07 | -| JASSUB-RATE-01 | open / 已复现 | Fallback ratechange sends an Event instead of numeric playbackRate | PKG-JASSUB-02, PKG-JASSUB-07 | -| JASSUB-NATIVE-FRAME-01 | open / 已复现 | Windows WebKit default frame-clock path does not produce subtitle demands in actual playback | PKG-JASSUB-02, PKG-JASSUB-07, PKG-JASSUB-05 | -| JASSUB-QUERY-01 | open / 已复现 | Subtitle query errors throw before calling the consumer and leave request listeners attached | PKG-JASSUB-07, PKG-JASSUB-05 | +| JASSUB-CONSTRUCTION-01 | resolved / 已复现 | Worker construction failure leaves previously allocated JASSUB DOM and listeners | PKG-JASSUB-02, PKG-JASSUB-07 | +| JASSUB-VIDEO-FRAME-01 | resolved / 已复现 | Old video frame callbacks can drive replacement subtitle time and duplicate chains | PKG-JASSUB-02, PKG-JASSUB-07 | +| JASSUB-RATE-01 | resolved / 已复现 | Fallback ratechange sends an Event instead of numeric playbackRate | PKG-JASSUB-02, PKG-JASSUB-07 | +| JASSUB-NATIVE-FRAME-01 | resolved / 已复现 | Windows WebKit default frame-clock path does not produce subtitle demands in actual playback | PKG-JASSUB-02, PKG-JASSUB-07, PKG-JASSUB-05 | +| JASSUB-QUERY-01 | resolved / 已复现 | Subtitle query errors throw before calling the consumer and leave request listeners attached | PKG-JASSUB-07, PKG-JASSUB-05 | diff --git a/refactor/risks.json b/refactor/risks.json index 32f418db1..f60f445b8 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -5430,7 +5430,7 @@ "id": "JASSUB-DESTROY-01", "title": "Repeated destruction or cross-parent setVideo can throw before JASSUB cleanup", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-JASSUB-02", "PKG-JASSUB-03", @@ -5444,7 +5444,15 @@ "test/jassub-registration.test.js" ], "compatibleResolution": "Use actual resource ownership and tolerate direct instance teardown before host teardown. Task03 fixes host cleanup after direct teardown, reentry and retry/rollback. Direct vendor repetition and cross-parent cleanup remain task07 and this risk stays open.", - "closureCriteria": "Candidate direct/repeated/host teardown and cross-parent video replacement release container, Worker and listeners without throwing or removing user nodes." + "closureCriteria": "Candidate direct/repeated/host teardown and cross-parent video replacement release container, Worker and listeners without throwing or removing user nodes.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md", + "refactor/baselines/jassub-vendor-validation.json", + "test/jassub-vendor-lifecycle.test.js", + "test/jassub-frame-polyfill.test.js", + "test/browser/jassub-lifecycle.spec.js" + ], + "resolutionRationale": "Controlled old-red/candidate-green repeated direct/host teardown and native cross-parent replacement release the actual container, video listeners and Worker once, preserving user nodes." }, { "id": "JASSUB-CANVAS-01", @@ -5475,7 +5483,7 @@ "id": "JASSUB-CONSTRUCTION-01", "title": "Worker construction failure leaves previously allocated JASSUB DOM and listeners", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-JASSUB-02", "PKG-JASSUB-07" @@ -5485,13 +5493,21 @@ "refactor/baselines/jassub-failures-validation.json" ], "compatibleResolution": "Roll back each construction phase and rethrow the original error without assuming new returned an instance.", - "closureCriteria": "Controlled and native construction failure do not retain owned DOM/listeners/callbacks; original error identity and successful construction remain intact." + "closureCriteria": "Controlled and native construction failure do not retain owned DOM/listeners/callbacks; original error identity and successful construction remain intact.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md", + "refactor/baselines/jassub-vendor-validation.json", + "test/jassub-vendor-lifecycle.test.js", + "test/jassub-frame-polyfill.test.js", + "test/browser/jassub-lifecycle.spec.js" + ], + "resolutionRationale": "Original controlled SecurityError identity survives rollback; actual invalid Worker URL produces native SyntaxError and leaves no owned DOM/subscriptions. Late initialization and application messages are cancelled after destruction." }, { "id": "JASSUB-VIDEO-FRAME-01", "title": "Old video frame callbacks can drive replacement subtitle time and duplicate chains", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-JASSUB-02", "PKG-JASSUB-07" @@ -5501,13 +5517,21 @@ "refactor/baselines/jassub-failures-validation.json" ], "compatibleResolution": "Track video generation and callback ownership across setVideo and destroy.", - "closureCriteria": "Actually queued old callbacks cannot deliver stale mediaTime or rearm; replacement video owns one loop, with native evidence." + "closureCriteria": "Actually queued old callbacks cannot deliver stale mediaTime or rearm; replacement video owns one loop, with native evidence.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md", + "refactor/baselines/jassub-vendor-validation.json", + "test/jassub-vendor-lifecycle.test.js", + "test/jassub-frame-polyfill.test.js", + "test/browser/jassub-lifecycle.spec.js" + ], + "resolutionRationale": "Tests execute callbacks actually removed from a pending queue after setVideo/destroy and reject stale time or rearming. Separate real playback after video replacement renders subtitles and tears down successfully in three browser engines and both main/legacy." }, { "id": "JASSUB-RATE-01", "title": "Fallback ratechange sends an Event instead of numeric playbackRate", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-JASSUB-02", "PKG-JASSUB-07" @@ -5517,13 +5541,21 @@ "refactor/baselines/jassub-failures-validation.json" ], "compatibleResolution": "Adapt event subscription to read the current video rate while retaining direct setRate(number).", - "closureCriteria": "Real ratechange and direct calls send numeric values, preserve timing, and avoid stale listeners after source replacement." + "closureCriteria": "Real ratechange and direct calls send numeric values, preserve timing, and avoid stale listeners after source replacement.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md", + "refactor/baselines/jassub-vendor-validation.json", + "test/jassub-vendor-lifecycle.test.js", + "test/jassub-frame-polyfill.test.js", + "test/browser/jassub-lifecycle.spec.js" + ], + "resolutionRationale": "Controlled and actual browser ratechange send the numeric current playbackRate; cross-video listener cleanup and the existing direct setRate contract remain covered." }, { "id": "JASSUB-NATIVE-FRAME-01", "title": "Windows WebKit default frame-clock path does not produce subtitle demands in actual playback", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-JASSUB-02", "PKG-JASSUB-07", @@ -5534,13 +5566,21 @@ "refactor/baselines/jassub-failures-validation.json" ], "compatibleResolution": "Investigate native frame delivery and establish a compatible capability response; explicit event-clock success is diagnostic only.", - "closureCriteria": "Default native behavior is verified or an explicit reviewed environment limitation is recorded; no silent global fallback or substitution of diagnostic mode for default acceptance." + "closureCriteria": "Default native behavior is verified or an explicit reviewed environment limitation is recorded; no silent global fallback or substitution of diagnostic mode for default acceptance.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md", + "refactor/baselines/jassub-vendor-validation.json", + "test/jassub-vendor-lifecycle.test.js", + "test/jassub-frame-polyfill.test.js", + "test/browser/jassub-lifecycle.spec.js" + ], + "resolutionRationale": "Native Windows WebKit zero-counter starvation is reproduced on the published default clock. A narrow media-time fallback only in the polyfill with all-zero quality counters restores default onDemand playback and seek; candidate default and custom-canvas pixel assertions pass. Native RVFC and normally increasing counters retain their paths. Default offscreen and physical devices remain task05." }, { "id": "JASSUB-QUERY-01", "title": "Subtitle query errors throw before calling the consumer and leave request listeners attached", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-JASSUB-07", "PKG-JASSUB-05" @@ -5550,7 +5590,15 @@ "test/jassub-runtime.test.js" ], "compatibleResolution": "Handle absent worker error payloads, release per-request listeners and timers before invoking user callbacks, and preserve successful synchronous query registration and callback data.", - "closureCriteria": "Actual vendor timeout and Worker-error paths invoke the consumer exactly once with the original error, leave no request listeners/timers, and successful real-WASM events/styles remain unchanged. Browser error/recovery acceptance remains PKG-JASSUB-05." + "closureCriteria": "Actual vendor timeout and Worker-error paths invoke the consumer exactly once with the original error, leave no request listeners/timers, and successful real-WASM events/styles remain unchanged. Browser error/recovery acceptance remains PKG-JASSUB-05.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-07-vendor.md", + "refactor/baselines/jassub-vendor-validation.json", + "test/jassub-vendor-lifecycle.test.js", + "test/jassub-frame-polyfill.test.js", + "test/browser/jassub-lifecycle.spec.js" + ], + "resolutionRationale": "Actual wrapper timeout and Worker error invoke once with the original error and clean all request listeners/timers first, including throwing user callbacks. Native CSP plain Event identity is preserved; real WASM queries still succeed. Destroy cancellation settles all pending queries after cleanup; complete error recovery remains task05." } ] } diff --git a/refactor/scripts/jassub-vendor-patch.test.mjs b/refactor/scripts/jassub-vendor-patch.test.mjs new file mode 100644 index 000000000..2c4460c3f --- /dev/null +++ b/refactor/scripts/jassub-vendor-patch.test.mjs @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +// eslint-disable-next-line test/no-import-node-test -- Vendor patch provenance is part of the baseline runner. +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 record = JSON.parse(fs.readFileSync('refactor/baselines/jassub-vendor-patch.json', 'utf8')) + const original = execFileSync('git', ['show', `${record.baselineCommit}:${record.file}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n') + const current = fs.readFileSync(record.file, 'utf8').replaceAll('\r\n', '\n') + const patch = fs.readFileSync(record.patchFile, 'utf8').replaceAll('\r\n', '\n') + assert.equal(hash(original), record.originalSha256LF) + assert.equal(hash(current), record.patchedSha256LF) + assert.equal(hash(patch), record.patchSha256LF) + assert.notEqual(record.originalSha256LF, record.patchedSha256LF) + execFileSync('git', ['apply', '--check', '--reverse', '--ignore-space-change', record.patchFile], { stdio: 'pipe' }) + const registry = JSON.parse(fs.readFileSync('refactor/third-party.json', 'utf8')) + const group = registry.vendored.find(item => item.id === 'jassub-code-and-workers') + assert.equal(group.localPatch.originalSha256LF, record.originalSha256LF) + assert.equal(group.fingerprints.find(item => item.path === record.file).sha256, record.patchedSha256LF) +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index fa02e2960..b092074cf 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -2532,11 +2532,15 @@ "PKG-JASSUB-03", "PKG-JASSUB-02" ], - "status": "todo", + "status": "done", "risk": "H", "deliverable": "独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界;查询 timeout/Worker error 的缺失数据回调及每请求监听器/定时器清理(JASSUB-QUERY-01)", "acceptance": "保持公开真实 instance、同步方法与资源路径;保留原 vendor 来源指纹并记录每项实际补丁;候选修复正例和真实字幕/失败/时钟验证通过,未证实设备不得冒充通过", - "evidence": [] + "evidence": [ + "changes/2026-09-14-PKG-JASSUB-07-vendor.md", + "baselines/jassub-vendor-validation.json", + "baselines/jassub-vendor-patch.json" + ] }, { "id": "PKG-MASK-01", diff --git a/refactor/third-party.json b/refactor/third-party.json index bd7f6d172..31c2a223e 100644 --- a/refactor/third-party.json +++ b/refactor/third-party.json @@ -353,9 +353,10 @@ ], "owners": [ "PKG-JASSUB-01", - "PKG-JASSUB-06" + "PKG-JASSUB-06", + "PKG-JASSUB-07" ], - "sourceStatus": "Wrapper/worker JS match npm 1.8.8 as previously frozen. Both local WASM binaries exactly match Pages nightly 747e392d7f6fe7c38bbcdfe05c9ada46b0b62daf linked to source 6b19a04ddfbad8f9bfd3237395788dd76218841b, with pinned build files and seven submodules. No independent rebuild. See baselines/jassub-provenance.json.", + "sourceStatus": "Original wrapper matched npm 1.8.8; PKG-JASSUB-07 now carries an explicitly recorded local lifecycle/clock/query patch (baselines/jassub-vendor-patch.json). Worker JS is unchanged. Both WASM binaries still exactly match Pages nightly 747e392d7f6fe7c38bbcdfe05c9ada46b0b62daf/source 6b19a04ddfbad8f9bfd3237395788dd76218841b. Original wrapper hash, unchanged binaries/fonts, pinned build and submodules remain separately recorded; no independent WASM rebuild.", "licenseStatus": "Matching source LICENSE and nightly COPYRIGHT identified and byte-verified; full component notice assembly, LGPL source/distribution review and full FreeType license remain PKG-JASSUB-06 gates. VENDOR-04 remains open.", "upstreamSources": [ "https://github.com/ThaUnknown/jassub/blob/main/LICENSE", @@ -385,7 +386,7 @@ { "path": "packages/artplayer-plugin-jassub/src/jassub.es.js", "algorithm": "sha256-lf", - "sha256": "b973508ade00b5b9cea2f299d49f4943003372729be7350b6938e44fddb32038" + "sha256": "1fdcf24d206e8711348dddcb5fe7b656007a9b1d81a992bb497c6d10bd0a2cdb" }, { "path": "packages/artplayer-plugin-jassub/worker/jassub-worker-modern.wasm", @@ -402,7 +403,13 @@ "algorithm": "sha256", "sha256": "73880d99ba9260ac39fc4476491cf254ca784c76f3af2fb03f3f9be8065ad3c0" } - ] + ], + "localPatch": { + "task": "PKG-JASSUB-07", + "record": "refactor/baselines/jassub-vendor-patch.json", + "originalSha256LF": "b973508ade00b5b9cea2f299d49f4943003372729be7350b6938e44fddb32038", + "patchedSha256LF": "1fdcf24d206e8711348dddcb5fe7b656007a9b1d81a992bb497c6d10bd0a2cdb" + } }, { "id": "jassub-font-assets", diff --git a/test/browser/jassub-lifecycle.spec.js b/test/browser/jassub-lifecycle.spec.js new file mode 100644 index 000000000..45b0b1f71 --- /dev/null +++ b/test/browser/jassub-lifecycle.spec.js @@ -0,0 +1,190 @@ +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 subtitles = `[Script Info] +ScriptType: v4.00+ +PlayResX: 640 +PlayResY: 360 +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Liberation Sans,36,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,20,1 +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.00,0:00:30.00,Default,,0,0,0,,Native lifecycle subtitle +` + +async function setup(page) { + await page.goto('/test/player.html?core=candidate') + await page.addScriptTag({ content: code }) + await page.evaluate(() => { + window.createPlayer('/assets/sample/steve-jobs.mp4') + window.jassubLifecycle = { sent: [], terminated: 0, errors: [] } + const NativeWorker = window.Worker + window.Worker = class extends NativeWorker { + constructor(...args) { + super(...args) + this.addEventListener('error', (event) => { + window.jassubLifecycle.lastWorkerError = event + }) + } + + postMessage(message, ...rest) { + window.jassubLifecycle.sent.push({ target: message.target, time: message.time, rate: message.rate }) + return super.postMessage(message, ...rest) + } + + terminate() { + window.jassubLifecycle.terminated++ + return super.terminate() + } + } + window.jassubOptions = { 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'], offscreenRender: false } + }) + await expect.poll(() => page.evaluate(() => window.art.duration)).toBeGreaterThan(30) +} + +for (const onDemandRender of [true, false]) { + test(`JASSUB actual video replacement and repeated destruction, onDemandRender=${onDemandRender}`, async ({ page }, testInfo) => { + await setup(page) + try { + await page.evaluate(async ({ subtitles, onDemandRender }) => { + const register = window.artplayerPluginJassub.default || window.artplayerPluginJassub + window.art.plugins.add(register({ ...window.jassubOptions, subContent: subtitles, onDemandRender })) + const instance = window.jassub = window.art.plugins.artplayerPluginJassub.instance + instance.addEventListener('error', event => window.jassubLifecycle.errors.push(String(event.error))) + await new Promise(resolve => instance.addEventListener('ready', resolve, { once: true })) + const container = document.createElement('div') + const video = document.createElement('video') + video.width = 640 + video.height = 360 + video.muted = true + video.preload = 'auto' + container.append(video) + document.body.append(container) + const loaded = new Promise(resolve => video.addEventListener('loadeddata', resolve, { once: true })) + video.src = '/assets/sample/steve-jobs.mp4' + await loaded + await video.play() + await new Promise((resolve) => { + const playing = () => { + if (video.currentTime <= 0.1) + return + video.removeEventListener('timeupdate', playing) + resolve() + } + video.addEventListener('timeupdate', playing) + playing() + }) + video.pause() + instance.setVideo(video) + window.replacementVideo = video + window.replacementContainer = container + const seeked = new Promise(resolve => video.addEventListener('seeked', resolve, { once: true })) + video.currentTime = 9 + await seeked + video.playbackRate = 1.5 + await video.play() + }, { subtitles, onDemandRender }) + await expect.poll(() => page.evaluate(() => { + const canvas = window.jassub._canvas + return canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data.filter((value, index) => index % 4 === 3 && value > 0).length + })).toBeGreaterThan(100) + const state = await page.evaluate(async () => { + const instance = window.jassub + const events = await new Promise((resolve, reject) => instance.getEvents((error, rows) => error ? reject(error) : resolve(rows))) + const parentMatches = instance._canvasParent.parentNode === window.replacementContainer + const originalContainerCount = window.art.template.$player.querySelectorAll('.JASSUB').length + const time = window.replacementVideo.currentTime + instance.destroy() + instance.destroy() + window.art.destroy(false) + window.replacementVideo.pause() + return { parentMatches, originalContainerCount, time, event: events[0], terminated: window.jassubLifecycle.terminated, ownedContainers: document.querySelectorAll('.JASSUB').length, replacementPreserved: window.replacementVideo.isConnected, errors: window.jassubLifecycle.errors, sent: window.jassubLifecycle.sent } + }) + expect(state.parentMatches).toBe(true) + expect(state.originalContainerCount).toBe(0) + expect(state.time).toBeGreaterThanOrEqual(9) + expect(typeof state.event.Style).toBe('number') + expect(state.event._index).toBeUndefined() + expect(state.terminated).toBe(1) + expect(state.ownedContainers).toBe(0) + expect(state.replacementPreserved).toBe(true) + expect(state.errors).toEqual([]) + if (!onDemandRender) + expect(state.sent.some(message => message.target === 'video' && message.rate === 1.5)).toBe(true) + await testInfo.attach('jassub-native-lifecycle', { body: JSON.stringify({ artifact, sha256: hash(code), onDemandRender, state }), contentType: 'application/json' }) + } + finally { + await page.evaluate(() => { + window.replacementVideo?.pause() + if (!window.art.isDestroy) + window.art.destroy() + }) + } + }) +} + +test('JASSUB actual invalid Worker URL construction releases the newly created canvas and media listeners', async ({ page }, testInfo) => { + await setup(page) + const state = await page.evaluate(() => { + const video = window.art.video + const listeners = new Map() + const add = video.addEventListener.bind(video) + const remove = video.removeEventListener.bind(video) + video.addEventListener = (name, callback, ...options) => { + if (!listeners.has(name)) + listeners.set(name, new Set()) + listeners.get(name).add(callback) + return add(name, callback, ...options) + } + video.removeEventListener = (name, callback, ...options) => { + listeners.get(name)?.delete(callback) + return remove(name, callback, ...options) + } + let error + try { + const register = window.artplayerPluginJassub.default || window.artplayerPluginJassub + register({ ...window.jassubOptions, workerUrl: 'http://[', onDemandRender: false })(window.art) + } + catch (failure) { error = { name: failure.name, message: failure.message } } + const activeListeners = [...listeners.values()].reduce((sum, values) => sum + values.size, 0) + window.art.destroy() + return { error, activeListeners, ownedContainers: document.querySelectorAll('.JASSUB').length } + }) + expect(state.error?.name).toBe('SyntaxError') + expect(state.activeListeners).toBe(0) + expect(state.ownedContainers).toBe(0) + await testInfo.attach('jassub-native-construction-failure', { body: JSON.stringify({ artifact, sha256: hash(code), state }), contentType: 'application/json' }) +}) + +test('JASSUB native asynchronous CSP Worker error reaches a pending query and explicit destruction cleans up', async ({ page }, testInfo) => { + await setup(page) + const state = await page.evaluate(async () => { + const meta = document.createElement('meta') + meta.httpEquiv = 'Content-Security-Policy' + meta.content = 'worker-src \'none\'' + document.head.append(meta) + const register = window.artplayerPluginJassub.default || window.artplayerPluginJassub + const { instance } = register({ ...window.jassubOptions, onDemandRender: false })(window.art) + let calls = 0 + const outcome = await new Promise((resolve) => { + instance.getEvents((error, rows) => { + calls++ + resolve({ errorIsNativeEvent: error instanceof Event, trusted: error.isTrusted, sameNativeError: error === window.jassubLifecycle.lastWorkerError, type: error.type, rowsAbsent: rows === undefined }) + }) + }) + instance.destroy() + instance.destroy() + window.art.destroy(false) + return { outcome, calls, terminated: window.jassubLifecycle.terminated, ownedContainers: document.querySelectorAll('.JASSUB').length } + }) + expect(state.outcome).toEqual({ errorIsNativeEvent: true, trusted: true, sameNativeError: true, type: 'error', rowsAbsent: true }) + expect(state.calls).toBe(1) + expect(state.terminated).toBe(1) + expect(state.ownedContainers).toBe(0) + await testInfo.attach('jassub-native-worker-error', { body: JSON.stringify({ artifact, sha256: hash(code), state }), contentType: 'application/json' }) +}) diff --git a/test/browser/jassub-native.spec.js b/test/browser/jassub-native.spec.js index 69fd5b1eb..228ba387c 100644 --- a/test/browser/jassub-native.spec.js +++ b/test/browser/jassub-native.spec.js @@ -41,9 +41,25 @@ for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) { resources.push({ url: response.url(), status: response.status(), contentType: response.headers()['content-type'] }) }) await page.goto(`/test/player.html?core=${core}`) + await page.evaluate(() => { + window.jassubFramePlatform = { before: typeof HTMLVideoElement.prototype.requestVideoFrameCallback, registered: 0, delivered: 0 } + }) await page.addScriptTag({ content: code }) await page.evaluate(() => { - window.jassubNative = { workers: [], snapshots: [], errors: [] } + const frames = window.jassubFramePlatform + frames.after = typeof HTMLVideoElement.prototype.requestVideoFrameCallback + if (frames.after === 'function') { + const request = HTMLVideoElement.prototype.requestVideoFrameCallback + HTMLVideoElement.prototype.requestVideoFrameCallback = function (callback) { + frames.registered++ + return request.call(this, function (...args) { + frames.delivered++ + frames.lastMetadata = args[1] + return callback.apply(this, args) + }) + } + } + window.jassubNative = { workers: [], snapshots: [], errors: [], frames } const NativeWorker = window.Worker window.Worker = class extends NativeWorker { constructor(...args) { @@ -110,7 +126,9 @@ for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) { signature = (signature + i * pixels[i]) >>> 0 } } - const state = { phase, visible, signature, width: canvas.width, height: canvas.height, cssWidth: rect.width, cssHeight: rect.height, time: window.art.currentTime, connected: canvas.isConnected } + const video = window.art.video + const quality = video.getVideoPlaybackQuality?.() + const state = { phase, visible, signature, width: canvas.width, height: canvas.height, cssWidth: rect.width, cssHeight: rect.height, time: window.art.currentTime, connected: canvas.isConnected, media: { paused: video.paused, readyState: video.readyState, totalFrames: quality?.totalVideoFrames, droppedFrames: quality?.droppedVideoFrames, decodedFrames: video.webkitDecodedFrameCount, mozPresentedFrames: video.mozPresentedFrames }, onDemand: window.jassub._onDemandRender } window.jassubNative.lastPixel = state if (phase) window.jassubNative.snapshots.push(state) diff --git a/test/helpers/jassub.js b/test/helpers/jassub.js index 17de24ea9..418ed78ad 100644 --- a/test/helpers/jassub.js +++ b/test/helpers/jassub.js @@ -39,6 +39,7 @@ export function jassubEnvironment(implementation, { script = false, simd = false } appendChild(child) { + child.parentNode?.removeChild(child) this.children.push(child) child.parentNode = this return child diff --git a/test/jassub-frame-polyfill.test.js b/test/jassub-frame-polyfill.test.js new file mode 100644 index 000000000..04b1ec56e --- /dev/null +++ b/test/jassub-frame-polyfill.test.js @@ -0,0 +1,364 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Frame clock regressions use the repository runner. +import test from 'node:test' +import vm from 'node:vm' +import { jassubCandidate } from './helpers/jassub.js' + +const candidate = await jassubCandidate() +const interval = 1000 / 30 + +function environment({ native = false, quality = true } = {}) { + let now = 0 + let nextFrame = 0 + const frames = new Map() + const cancelled = [] + class Video { + constructor() { + this.currentTime = 0 + this.videoWidth = 640 + this.videoHeight = 360 + this.readyState = 4 + this.paused = false + this.seeking = false + this.quality = { totalVideoFrames: 0, droppedVideoFrames: 0, totalFrameDelay: 0 } + } + } + if (quality) { + Video.prototype.getVideoPlaybackQuality = function () { + return { ...this.quality } + } + } + const nativeRequest = () => 123 + const nativeCancel = () => undefined + if (native) { + Video.prototype.requestVideoFrameCallback = nativeRequest + Video.prototype.cancelVideoFrameCallback = nativeCancel + } + const originalDescriptors = Object.getOwnPropertyDescriptors(Video.prototype) + const module = { exports: {} } + const context = { + HTMLVideoElement: Video, + EventTarget, + Map, + module, + exports: module.exports, + performance: { now: () => now }, + requestAnimationFrame(callback) { + const id = nextFrame++ + frames.set(id, callback) + return id + }, + cancelAnimationFrame(id) { + cancelled.push(id) + frames.delete(id) + }, + } + context.window = context + context.self = context + // Only platform primitives are controlled. Execute the actual package bundle; + // no vendor method is substituted or copied into this test. + vm.runInNewContext(candidate.code, context, { timeout: 5000 }) + assert.equal(typeof (module.exports.default || module.exports), 'function') + return { + Video, + frames, + cancelled, + nativeRequest, + nativeCancel, + originalDescriptors, + frame(time) { + assert(time >= now, 'The controlled wall clock is monotonic') + now = time + for (const [id, callback] of [...frames]) { + if (!frames.delete(id)) + continue + callback(time) + } + }, + } +} + +function collect(video) { + const calls = [] + const id = video.requestVideoFrameCallback((now, metadata) => calls.push({ now, metadata })) + assert.equal(typeof id, 'number') + return { id, calls } +} + +function hasPending(video, id) { + const pending = video._rvfcpolyfillmap + assert(pending, 'The actual polyfill exposes its pending request map') + return pending instanceof Map ? pending.has(id) : Object.hasOwn(pending, id) +} + +test('JASSUB frame polyfill leaves native RVFC and cancellation untouched', () => { + const env = environment({ native: true }) + assert.deepEqual(Object.getOwnPropertyDescriptors(env.Video.prototype), env.originalDescriptors) + const video = new env.Video() + assert.equal(video.requestVideoFrameCallback, env.nativeRequest) + assert.equal(video.cancelVideoFrameCallback, env.nativeCancel) + assert(!Object.hasOwn(video, '_rvfcpolyfillmap')) + assert.equal(env.frames.size, 0) +}) + +test('JASSUB frame polyfill preserves the unsupported quality-API boundary', () => { + const env = environment({ quality: false }) + assert.equal(env.Video.prototype.requestVideoFrameCallback, undefined) + assert.equal(env.Video.prototype.cancelVideoFrameCallback, undefined) + assert.equal(env.frames.size, 0) +}) + +test('JASSUB healthy frame counts retain metadata and bypass fallback throttling', () => { + const env = environment() + const video = new env.Video() + video.quality = { totalVideoFrames: 20, droppedVideoFrames: 5, totalFrameDelay: 0.125 } + const { calls } = collect(video) + video.currentTime = 4 + video.quality = { totalVideoFrames: 21, droppedVideoFrames: 5, totalFrameDelay: 0.25 } + // The new fallback conditions must not alter the original increasing-count path. + video.readyState = 1 + video.seeking = true + env.frame(16) + assert.equal(calls.length, 1) + const { now, metadata } = calls[0] + assert.equal(now, 16) + assert.equal(metadata.presentedFrames, 16) + assert.equal(metadata.processingDuration, 0.125) + assert.equal(metadata.presentationTime, 141) + assert.equal(metadata.expectedDisplayTime, 32) + assert.equal(metadata.mediaTime, 4.016) + assert.equal(metadata.width, 640) + assert.equal(metadata.height, 360) + assert.equal(env.frames.size, 0) +}) + +for (const counter of ['mozPresentedFrames', 'mozPaintedFrames']) { + test(`JASSUB preserves the ${counter} increasing-count path`, () => { + const env = environment() + const video = new env.Video() + video[counter] = 4 + const { calls } = collect(video) + video[counter] = 5 + env.frame(1) + assert.equal(calls.length, 1) + assert.equal(calls[0].metadata.presentedFrames, 5) + }) +} + +test('JASSUB zero-count fallback waits one 30Hz interval and reports actual media time with zero frames', () => { + const env = environment() + const video = new env.Video() + const { calls } = collect(video) + video.currentTime = 0.75 + env.frame(interval - 0.001) + assert.equal(calls.length, 0) + env.frame(interval) + assert.equal(calls.length, 1) + assert.equal(calls[0].now, interval) + assert.equal(calls[0].metadata.mediaTime, 0.75) + assert.equal(calls[0].metadata.presentedFrames, 0) + assert.equal(calls[0].metadata.width, 640) + assert.equal(calls[0].metadata.height, 360) + env.frame(interval * 3) + assert.equal(calls.length, 1, 'A fulfilled request is never delivered twice') + assert.equal(env.frames.size, 0) +}) + +test('JASSUB zero-count fallback remains bounded on a 144Hz wall clock', () => { + const env = environment() + const video = new env.Video() + const deliveries = [] + let pending + const request = () => { + pending = video.requestVideoFrameCallback((now, metadata) => { + deliveries.push({ now, mediaTime: metadata.mediaTime }) + request() + }) + } + request() + for (let frame = 1; frame <= 144; frame++) { + const time = frame * 1000 / 144 + video.currentTime = time / 1000 + env.frame(time) + } + assert(deliveries.length > 20 && deliveries.length <= 30, `Expected bounded useful fallback, received ${deliveries.length} callbacks`) + for (let index = 1; index < deliveries.length; index++) + assert(deliveries[index].now - deliveries[index - 1].now >= interval - 1e-9) + video.cancelVideoFrameCallback(pending) + assert.equal(env.frames.size, 0) +}) + +test('JASSUB resumed real frame counts bypass the preceding fallback interval', () => { + const env = environment() + const video = new env.Video() + const fallback = collect(video) + video.currentTime = 0.5 + env.frame(interval) + assert.equal(fallback.calls.length, 1) + assert.equal(fallback.calls[0].metadata.presentedFrames, 0) + const healthy = collect(video) + video.quality.totalVideoFrames = 1 + video.currentTime = 1 + env.frame(interval + 1) + assert.equal(healthy.calls.length, 1, 'Actual frame recovery must not wait for the fallback cap') + assert.equal(healthy.calls[0].metadata.presentedFrames, 1) + assert.equal(healthy.calls[0].metadata.mediaTime, 1.001) + assert.equal(env.frames.size, 0) +}) + +for (const paused of [false, true]) { + test(`JASSUB unchanged media time does not complete zero-count requests, paused=${paused}`, () => { + const env = environment() + const video = new env.Video() + video.paused = paused + video.currentTime = 5 + const { id, calls } = collect(video) + env.frame(40) + env.frame(1000) + assert.equal(calls.length, 0) + video.cancelVideoFrameCallback(id) + assert.equal(env.frames.size, 0) + }) +} + +for (const destination of [20, 3]) { + test(`JASSUB paused seek to ${destination} waits for seeking completion then delivers the changed time`, () => { + const env = environment() + const video = new env.Video() + video.paused = true + video.currentTime = 10 + const { calls } = collect(video) + video.seeking = true + video.currentTime = destination + env.frame(40) + assert.equal(calls.length, 0) + video.seeking = false + env.frame(60) + assert.equal(calls.length, 1) + assert.equal(calls[0].metadata.mediaTime, destination) + assert.equal(calls[0].metadata.presentedFrames, 0) + }) +} + +for (const readyState of [0, 1]) { + test(`JASSUB zero-count fallback waits for current frame data from readyState=${readyState}`, () => { + const env = environment() + const video = new env.Video() + video.readyState = readyState + const { calls } = collect(video) + video.currentTime = 1 + env.frame(40) + assert.equal(calls.length, 0) + video.readyState = 2 + env.frame(60) + assert.equal(calls.length, 1) + }) +} + +for (const [label, quality] of [ + ['NaN total', { totalVideoFrames: Number.NaN, droppedVideoFrames: 0 }], + ['missing total', { droppedVideoFrames: 0 }], + ['NaN dropped', { totalVideoFrames: 0, droppedVideoFrames: Number.NaN }], + ['missing dropped', { totalVideoFrames: 0 }], + ['all frames dropped', { totalVideoFrames: 10, droppedVideoFrames: 10 }], +]) { + test(`JASSUB does not infer zero-count fallback from ${label}`, () => { + const env = environment() + const video = new env.Video() + video.quality = quality + const { id, calls } = collect(video) + video.currentTime = 1 + env.frame(40) + video.currentTime = 2 + env.frame(100) + assert.equal(calls.length, 0) + video.cancelVideoFrameCallback(id) + assert.equal(env.frames.size, 0) + }) +} + +test('JASSUB same-video simultaneous requests have independent handles and cancellation', () => { + const env = environment() + const video = new env.Video() + const first = collect(video) + const second = collect(video) + assert.notEqual(first.id, second.id) + assert(second.id > first.id, 'Callback identifiers are monotonic even at the same performance.now') + video.cancelVideoFrameCallback(first.id) + video.quality.totalVideoFrames = 1 + env.frame(16) + assert.equal(first.calls.length, 0) + assert.equal(second.calls.length, 1) + assert.equal(env.frames.size, 0) +}) + +test('JASSUB simultaneous requests on different videos cannot cancel each other', () => { + const env = environment() + const firstVideo = new env.Video() + const secondVideo = new env.Video() + const first = collect(firstVideo) + const second = collect(secondVideo) + assert(Object.hasOwn(firstVideo, '_rvfcpolyfillmap')) + assert(Object.hasOwn(secondVideo, '_rvfcpolyfillmap')) + assert.notEqual(firstVideo._rvfcpolyfillmap, secondVideo._rvfcpolyfillmap) + firstVideo.cancelVideoFrameCallback(first.id) + firstVideo.cancelVideoFrameCallback(first.id) + firstVideo.quality.totalVideoFrames = 1 + secondVideo.quality.totalVideoFrames = 1 + env.frame(16) + assert.equal(first.calls.length, 0) + assert.equal(second.calls.length, 1) + assert.equal(env.frames.size, 0) +}) + +test('JASSUB cancellation releases RAF id zero', () => { + const env = environment() + const video = new env.Video() + const { id, calls } = collect(video) + assert(env.frames.has(0)) + video.cancelVideoFrameCallback(id) + assert.deepEqual(env.cancelled, [0]) + video.quality.totalVideoFrames = 1 + env.frame(40) + assert.equal(calls.length, 0) + assert.equal(env.frames.size, 0) +}) + +test('JASSUB removes a pending callback before invoking it even when it throws', () => { + const env = environment() + const video = new env.Video() + const expected = new Error('frame consumer failure') + const id = video.requestVideoFrameCallback(() => { + assert.equal(hasPending(video, id), false) + throw expected + }) + video.quality.totalVideoFrames = 1 + assert.throws(() => env.frame(16), error => error === expected) + assert.equal(hasPending(video, id), false) + assert.equal(env.frames.size, 0) + const next = collect(video) + video.quality.totalVideoFrames = 2 + env.frame(17) + assert.equal(next.calls.length, 1) +}) + +test('JASSUB callback reentry retains a newly registered request independently', () => { + const env = environment() + const video = new env.Video() + let next + const calls = [] + const first = video.requestVideoFrameCallback(() => { + assert.equal(hasPending(video, first), false) + calls.push('first') + next = video.requestVideoFrameCallback(() => calls.push('next')) + video.cancelVideoFrameCallback(first) + }) + video.quality.totalVideoFrames = 1 + env.frame(16) + assert.deepEqual(calls, ['first']) + assert(hasPending(video, next)) + video.quality.totalVideoFrames = 2 + env.frame(17) + assert.deepEqual(calls, ['first', 'next']) + assert.equal(env.frames.size, 0) +}) diff --git a/test/jassub-vendor-lifecycle.test.js b/test/jassub-vendor-lifecycle.test.js new file mode 100644 index 000000000..b84b3ed99 --- /dev/null +++ b/test/jassub-vendor-lifecycle.test.js @@ -0,0 +1,302 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Vendor lifecycle regressions use the repository runner. +import test from 'node:test' +import { jassubCandidate, jassubEnvironment } from './helpers/jassub.js' + +const candidate = await jassubCandidate() + +function environment() { + // Expose platform controls only; every tested method is the actual bundled vendor. + const code = `${candidate.code}\nObject.defineProperty(module.exports.default || module.exports, '__lifecyclePlatform', { value: { Error, ErrorEvent, Video: HTMLVideoElement, setGlobal: (name, value) => { globalThis[name] = value } } });` + const env = jassubEnvironment({ ...candidate, code }) + env.platform = env.factory.__lifecyclePlatform + return env +} + +function trackListeners(target) { + const listeners = new Map() + const add = target.addEventListener + const remove = target.removeEventListener + target.addEventListener = function (type, callback, ...options) { + if (!listeners.has(type)) + listeners.set(type, new Set()) + listeners.get(type).add(callback) + return add?.call(this, type, callback, ...options) + } + target.removeEventListener = function (type, callback, ...options) { + listeners.get(type)?.delete(callback) + return remove?.call(this, type, callback, ...options) + } + return { + size: () => [...listeners.values()].reduce((sum, entries) => sum + entries.size, 0), + callbacks: type => [...listeners.get(type) || []], + emit(type, event) { + for (const callback of [...listeners.get(type) || []]) callback(event) + }, + } +} + +function frames(env) { + const pending = new Map() + let next = 0 + env.platform.Video.prototype.requestVideoFrameCallback = function (callback) { + pending.set(++next, { video: this, callback }) + return next + } + env.platform.Video.prototype.cancelVideoFrameCallback = id => pending.delete(id) + return { + count: video => [...pending.values()].filter(row => row.video === video).length, + take(video) { + const entry = [...pending].find(([, row]) => row.video === video) + assert(entry, 'take an actually queued native frame callback') + pending.delete(entry[0]) + return entry[1].callback + }, + } +} + +async function queryEnvironment() { + const env = environment() + const timers = new Map() + const errors = [] + let next = 0 + env.platform.setGlobal('setTimeout', (callback) => { + timers.set(++next, callback) + return next + }) + env.platform.setGlobal('clearTimeout', id => timers.delete(id)) + // Capture the actual Error allocated by the timeout, without intercepting vendor methods. + env.platform.setGlobal('Error', class extends env.platform.Error { + constructor(...args) { + super(...args) + errors.push(this) + } + }) + const { instance } = env.factory()(env.art) + await env.ready() + const worker = env.workers[0] + const listeners = trackListeners(worker) + return { + ...env, + instance, + worker, + timers, + errors, + requests: listeners, + timeout() { + const entry = timers.entries().next().value + assert(entry, 'fire an actually registered one-shot timeout') + timers.delete(entry[0]) + entry[1]() + }, + } +} + +test('JASSUB repeated direct destroy returns synchronously and terminates once', async () => { + const env = environment() + const listeners = trackListeners(env.art.video) + const { instance } = env.factory()(env.art) + await env.ready() + assert(listeners.size() > 0) + assert.equal(instance.destroy(), undefined) + assert.equal(instance.destroy(), undefined) + env.emit('destroy') + await env.flush() + assert.equal(env.workers[0].terminated, 1) + assert.equal(listeners.size(), 0) + assert.deepEqual(env.parent.children, [env.art.video]) +}) + +test('JASSUB destroy after ready cancels queued resize and track messages and settles sendMessage', async () => { + const env = environment() + const { instance } = env.factory()(env.art) + await env.ready() + const worker = env.workers[0] + const sent = worker.messages.length + assert.equal(instance.resize(640, 360, 4, 5, false), undefined) + assert.equal(instance.setTrack('[Script Info]'), undefined) + const pending = instance.sendMessage('queued-before-destroy') + assert.equal(instance.destroy(), undefined) + assert.equal(await pending, undefined) + await env.flush() + assert.equal(worker.messages.length, sent) + assert.equal(worker.terminated, 1) +}) + +test('JASSUB setVideo across parents destroys the actual owned container', async () => { + const env = environment() + const originalListeners = trackListeners(env.art.video) + const { instance } = env.factory()(env.art) + await env.ready() + const parent = new env.parent.constructor() + const video = parent.appendChild(new env.platform.Video()) + const listeners = trackListeners(video) + instance.setVideo(video) + assert.equal(originalListeners.size(), 0) + assert(listeners.size() > 0) + assert.equal(instance.destroy(), undefined) + assert.equal(listeners.size(), 0) + assert.equal(env.workers[0].terminated, 1) + assert.deepEqual(env.parent.children, [env.art.video]) + assert.deepEqual(parent.children, [video]) +}) + +test('JASSUB Worker constructor SecurityError rolls back DOM and video listeners without replacing the error', async () => { + const env = environment() + const listeners = trackListeners(env.art.video) + const failure = new DOMException('Controlled Worker construction failure', 'SecurityError') + env.platform.setGlobal('Worker', class { + constructor() { throw failure } + }) + assert.throws(() => env.factory()(env.art), error => error === failure) + await env.flush() + assert.equal(listeners.size(), 0) + assert.deepEqual(env.parent.children, [env.art.video]) + assert.equal(env.workers.length, 0) + assert.equal(env.listeners.get('destroy')?.length || 0, 0) +}) + +test('JASSUB destroy before ready prevents queued initialization and application messages from posting', async () => { + const env = environment() + const { instance } = env.factory()(env.art) + const worker = env.workers[0] + const afterTermination = [] + const post = worker.postMessage + worker.postMessage = function (message) { + if (this.terminated) + afterTermination.push(message.target) + return post.call(this, message) + } + instance.sendMessage('queued-before-destroy') + instance.destroy() + await env.flush() + // A ready event already queued by a real Worker may still be delivered after termination. + worker.onmessage?.({ data: { target: 'ready' } }) + await env.flush() + assert.deepEqual(afterTermination, []) + assert.equal(worker.messages.some(message => ['init', 'queued-before-destroy'].includes(message.target)), false) + assert.equal(worker.terminated, 1) +}) + +test('JASSUB stale queued video frame cannot drive or duplicate the replacement video loop', async () => { + const env = environment() + const clock = frames(env) + const { instance } = env.factory()(env.art) + await env.ready() + const oldCallback = clock.take(env.art.video) + const video = env.createVideo() + video.currentTime = 9 + instance.setVideo(video) + assert.equal(clock.count(video), 1) + oldCallback(0, { mediaTime: 123, width: 640, height: 360 }) + await env.flush() + assert.equal(env.workers[0].messages.some(message => message.target === 'demand' && message.time === 123), false) + assert.equal(clock.count(video), 1) + clock.take(video)(0, { mediaTime: 9, width: 640, height: 360 }) + await env.flush() + assert.equal(env.workers[0].messages.filter(message => message.target === 'demand').at(-1).time, 9) + assert.equal(clock.count(video), 1) + instance.destroy() + assert.equal(clock.count(video), 0) +}) + +test('JASSUB fallback ratechange sends the current numeric playbackRate', async () => { + const env = environment() + const { instance } = env.factory({ onDemandRender: false })(env.art) + await env.ready() + env.art.video.playbackRate = 1.75 + env.art.video.dispatchEvent(new Event('ratechange')) + await env.flush() + const message = env.workers[0].messages.at(-1) + assert.equal(message.target, 'video') + assert.equal(message.rate, 1.75) + instance.destroy() +}) + +for (const method of ['getEvents', 'getStyles']) { + for (const mode of ['success', 'timeout', 'worker-error']) { + for (const throws of [false, true]) { + test(`JASSUB ${method} ${mode} cleans before its single callback${throws ? ' even when that callback throws' : ''}`, async () => { + const env = await queryEnvironment() + const calls = [] + const cleanupAtCallback = [] + const callbackFailure = new Error('Controlled user callback failure') + assert.equal(env.instance[method]((...args) => { + calls.push(args) + cleanupAtCallback.push([env.requests.size(), env.timers.size]) + if (throws) + throw callbackFailure + }), undefined) + assert.equal(env.worker.messages.at(-1).target, method) + const staleMessage = env.requests.callbacks('message')[0] + const staleError = env.requests.callbacks('error')[0] + const workerError = new env.platform.ErrorEvent('error', { error: new Error('Controlled Worker error') }) + const result = [{ sample: 1 }] + const message = { data: { target: method, [method === 'getEvents' ? 'events' : 'styles']: result } } + const complete = () => { + if (mode === 'success') + env.requests.emit('message', message) + else if (mode === 'timeout') + env.timeout() + else + env.requests.emit('error', workerError) + } + if (throws) + assert.throws(complete, error => error === callbackFailure) + else + assert.doesNotThrow(complete) + assert.equal(calls.length, 1) + assert.deepEqual(cleanupAtCallback, [[0, 0]]) + assert.equal(calls[0][0], mode === 'success' ? null : mode === 'timeout' ? env.errors[0] : workerError) + assert.equal(calls[0][1], mode === 'success' ? result : undefined) + staleMessage(message) + staleError(workerError) + assert.equal(calls.length, 1) + assert.equal(env.requests.size(), 0) + assert.equal(env.timers.size, 0) + env.instance.destroy() + }) + } + } +} + +for (const throws of [false, true]) { + test(`JASSUB destroy settles all pending queries after releasing resources${throws ? ' despite a throwing callback' : ''}`, async () => { + const env = await queryEnvironment() + const calls = [] + const callbackFailure = new Error('Controlled destroy callback failure') + for (const method of ['getEvents', 'getStyles']) { + env.instance[method]((error, result) => { + calls.push({ method, error, result, listeners: env.requests.size(), timers: env.timers.size }) + if (throws && method === 'getEvents') + throw callbackFailure + }) + } + const lateMessages = env.requests.callbacks('message') + const lateErrors = env.requests.callbacks('error') + assert(env.requests.size() > 0) + assert.equal(env.timers.size, 2) + if (throws) + assert.throws(() => env.instance.destroy(), error => error === callbackFailure) + else + assert.equal(env.instance.destroy(), undefined) + assert.equal(calls.length, 2) + assert.deepEqual(calls.map(call => call.method), ['getEvents', 'getStyles']) + for (const call of calls) { + assert(call.error instanceof env.platform.Error) + assert.match(call.error.message, /destroy/i) + assert.equal(call.result, undefined) + assert.equal(call.listeners, 0) + assert.equal(call.timers, 0) + } + for (const callback of lateMessages) callback({ data: { target: 'getEvents', events: [] } }) + for (const callback of lateErrors) callback(new Error('Late Worker failure')) + assert.equal(calls.length, 2) + assert.equal(env.worker.terminated, 1) + assert.equal(env.requests.size(), 0) + assert.equal(env.timers.size, 0) + assert.deepEqual(env.parent.children, [env.art.video]) + assert.equal(env.instance.destroy(), undefined) + assert.equal(env.worker.terminated, 1) + }) +} diff --git a/test/jassub.test.js b/test/jassub.test.js index 5043b96ac..5ff288fc3 100644 --- a/test/jassub.test.js +++ b/test/jassub.test.js @@ -59,12 +59,12 @@ for (const implementation of implementations) { assert.equal(result.instance.resize(640, 360, 4, 5, false), undefined) assert.equal(result.instance.setVideo(option.video), undefined) assert.equal(result.instance.setTrack('[Script Info]'), undefined) - assert.equal(result.instance.destroy(), undefined) await env.flush() - assert.equal(env.workers[0].terminated, 1) const resize = env.workers[0].messages.find(message => message.target === 'canvas') assert.equal(resize.width, 640) assert.equal(resize.height, 360) + assert.equal(result.instance.destroy(), undefined) + assert.equal(env.workers[0].terminated, 1) }) test(`JASSUB ${implementation.name}: omitted options retain worker and font defaults`, async () => { const env = jassubEnvironment(implementation) diff --git a/test/types/jassub-runtime.ts b/test/types/jassub-runtime.ts index 56281c975..809bd2bdb 100644 --- a/test/types/jassub-runtime.ts +++ b/test/types/jassub-runtime.ts @@ -41,7 +41,7 @@ instance.addFont(new Uint8Array([0])) instance.setDefaultFont('fallback') instance.runBenchmark() instance.getEvents((failure, events) => { - const caught: Error | ErrorEvent | null = failure + const caught: Error | Event | null = failure const values: AssEvent[] | undefined = events if (events) { for (const event of events) {