diff --git a/README.md b/README.md
index 2b02b9f73..952194280 100644
--- a/README.md
+++ b/README.md
@@ -118,7 +118,6 @@ let art = new Artplayer({
| [artplayer-plugin-danmuku-mask](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-danmuku/index.js%0A./uncompiled/artplayer-plugin-danmuku-mask/index.js&example=danmuku.mask) | Danmuku masking (smart blocking) | 
|
| [artplayer-plugin-asr](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-asr/index.js&example=asr) | Automatic Speech Recognition | 
|
| [artplayer-plugin-ads](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-ads/index.js&example=ads) | Video advertising plugin | WIP |
-| [artplayer-plugin-websr](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-websr/index.js&example=websr) | AI video upscaling with WebGPU | 
|
## Libraries
diff --git a/docs/assets/example/websr.js b/docs/assets/example/websr.js
deleted file mode 100644
index e5574458e..000000000
--- a/docs/assets/example/websr.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// https://github.com/sb2702/websr
-// npm i artplayer-plugin-websr
-// import artplayerPluginWebsr from 'artplayer-plugin-websr';
-
-var art = new Artplayer({
- container: ".artplayer-app",
- url: "/assets/sample/video3.mp4",
- fullscreenWeb: true,
- plugins: [
- artplayerPluginWebsr({
- videoScale: 2,
- networkSize: 'medium',
- weightsBaseUrl: '/assets/websr/weights',
- workerUrl: '/assets/websr/worker/main.js',
- compare: true,
- }),
- ],
-});
diff --git a/docs/assets/js/common.js b/docs/assets/js/common.js
index 075a4515d..77203dd95 100644
--- a/docs/assets/js/common.js
+++ b/docs/assets/js/common.js
@@ -67,7 +67,6 @@
'./assets/ts/artplayer-plugin-multiple-subtitles.d.ts',
'./assets/ts/artplayer-plugin-vast.d.ts',
'./assets/ts/artplayer-plugin-vtt-thumbnail.d.ts',
- './assets/ts/artplayer-plugin-websr.d.ts',
'./assets/ts/artplayer-proxy-canvas.d.ts',
'./assets/ts/artplayer-proxy-mediabunny.d.ts',
'./assets/ts/artplayer-tool-iframe.d.ts',
diff --git a/docs/compiled/artplayer-plugin-websr.js b/docs/compiled/artplayer-plugin-websr.js
deleted file mode 100644
index 0441ab998..000000000
--- a/docs/compiled/artplayer-plugin-websr.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * artplayer-plugin-websr.js v1.0.1
- * Github: https://github.com/zhw2590582/ArtPlayer
- * (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?(t.artplayerPluginWebsr=t(),define(function(){return t.artplayerPluginWebsr})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginWebsr=t()}(this,function(){"use strict";var e=Object.defineProperty,t=(t,s,r)=>((t,s,r)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[s]=r)(t,"symbol"!==typeof s?s+"":s,r);const s=class e{static isSupported(){try{const e="undefined"!==typeof Worker,t="undefined"!==typeof OffscreenCanvas||"undefined"!==typeof document&&!!document.createElement("canvas").transferControlToOffscreen,s="undefined"!==typeof Blob;return e&&t&&s}catch{return!1}}static isVideoSupported(){try{return"undefined"!==typeof VideoEncoder&&"undefined"!==typeof VideoDecoder}catch{return!1}}constructor(t={}){const s=t.weightsBaseUrl||"/weights";this.networks=t.networks||{small:{name:"anime4k/cnn-2x-l",weightsUrl:`${s}/cnn-8.json`},medium:{name:"anime4k/cnn-2x-16",weightsUrl:`${s}/cnn-16.json`},large:{name:"anime4k/cnn-2x-28",weightsUrl:`${s}/cnn-28.json`}},this.weightsBaseUrl=s,this.networkSize=t.networkSize||"medium",this.workerUrl=t.workerUrl||"/worker/main.js",this.timeouts={...e.DEFAULT_TIMEOUTS,...t.timeouts||{}},this.delays={...e.DEFAULT_DELAYS,...t.delays||{}},this.imageScale="number"===typeof t.imageScale&&t.imageScale>0?t.imageScale:2,this.videoScale="number"===typeof t.videoScale&&t.videoScale>0?t.videoScale:2,this.weightsCache=new Map,this.workerInstance=null,this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.realtimeLoopId=null,this.realtimeState=null,this.init()}init({prewarm:t=!0}={}){if(!e.isSupported())throw new Error("Upscaler is not supported in this environment");return t&&this.getWorker().postMessage({cmd:"isSupported"}),this}getWorker(){return this.workerInstance||(this.workerInstance=new Worker(this.workerUrl),this.workerInstance.onmessage=e=>this.handleWorkerMessage(e)),this.workerInstance}static extractProgressValue(e){const t=e.data??e.progress??e.value??e.percentage;return"number"===typeof t?Math.min(100,Math.max(0,Math.round(t))):null}handleBlobResponse(e){const t="video"===this.processingType?"videoBlob":"imageBlob",s=this.messageHandlers[t];e.data instanceof Blob&&s&&s({[t]:e.data})}requestBlob(){const e="video"===this.processingType?"getVideoBlob":"getImageBlob";this.getWorker().postMessage({cmd:e})}handleWorkerMessage(t){const{data:s}=t;if(!s.cmd)return;const{cmd:r}=s;if("progress"===r){const t=e.extractProgressValue(s);null!==t&&this.progressCallback&&this.progressCallback(t),this.messageHandlers.progress&&this.messageHandlers.progress(s)}else"finished"===r?s.data instanceof Blob?this.handleBlobResponse(s):this.requestBlob():this.messageHandlers[r]&&this.messageHandlers[r](s)}async loadWeights(e){if(this.validateNetworkSize(e),this.weightsCache.has(e))return this.weightsCache.get(e);const t=this.networks[e],s=await fetch(t.weightsUrl);if(!s.ok)throw new Error(`Failed to fetch weights: ${s.statusText}`);const r=await s.json();return this.weightsCache.set(e,r),r}static loadImageMetadata(e,t){return new Promise((s,r)=>{const i=new Blob([e],{type:t}),a=URL.createObjectURL(i),n=new Image;n.onload=()=>{URL.revokeObjectURL(a),s({width:n.width,height:n.height})},n.onerror=()=>{URL.revokeObjectURL(a),r(new Error("Failed to load image"))},n.src=a})}static loadVideoMetadata(e,t){return new Promise((s,r)=>{const i=URL.createObjectURL(e),a=document.createElement("video");a.src=i;const n=setTimeout(()=>r(new Error("Timeout")),t);a.onloadedmetadata=()=>{clearTimeout(n),URL.revokeObjectURL(i),s({width:a.videoWidth,height:a.videoHeight,duration:a.duration})},a.onerror=()=>{clearTimeout(n),URL.revokeObjectURL(i),r(new Error("Failed to load video"))}})}createOffscreenCanvas(e,t,s=2){const r="number"===typeof s&&s>0?s:1,i=document.createElement("canvas");if(i.width=e*r,i.height=t*r,"function"!==typeof i.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");return i.transferControlToOffscreen()}createBlobPromise(e,t){return new Promise((s,r)=>{const i=setTimeout(()=>{this.messageHandlers[e]&&(delete this.messageHandlers[e],r(new Error(`${e} processing timeout`)))},t);this.messageHandlers[e]=t=>{delete this.messageHandlers[e],clearTimeout(i);const a=t[e]||t.data;a instanceof Blob?s(a):r(new Error(`Invalid ${e} format`))}})}validateNetworkSize(e){if(!this.networks[e])throw new Error(`Invalid networkSize: use one of ${Object.keys(this.networks).join(", ")}`)}async upscaleImage(t,s,r){if(!t)throw new Error("Image file is required");const i=s||this.networkSize;this.validateNetworkSize(i),this.processingType="image";const a=await t.arrayBuffer(),n=t.type||"image/jpeg",{width:o,height:l}=await e.loadImageMetadata(a,n),d=await this.loadWeights(i),c=this.networks[i],h="number"===typeof r&&r>0?r:this.imageScale,[m,u]=[this.createOffscreenCanvas(o,l,h),this.createOffscreenCanvas(o,l,1)],p=this.createBlobPromise("imageBlob",this.timeouts.IMAGE);return this.getWorker().postMessage({cmd:"init",data:{imageArrayBuffer:a,imageMimeType:n,upscaled:m,original:u,resolution:{width:o,height:l},network_name:c.name,weights:d}},[m,u]),setTimeout(()=>{this.getWorker().postMessage({cmd:"network",data:{name:c.name,imageArrayBuffer:a,imageMimeType:n,weights:d}}),setTimeout(()=>{this.getWorker().postMessage({cmd:"getImageBlob",data:{imageArrayBuffer:a,imageMimeType:n}})},this.delays.NETWORK)},this.delays.INIT),p}async upscaleVideo(t,s,r){if(!t)throw new Error("Video file is required");const i=s||this.networkSize;if(this.validateNetworkSize(i),!e.isVideoSupported())throw new Error("WebCodecs API not supported. Requires Chrome 94+");this.processingType="video";const{width:a,height:n,duration:o}=await e.loadVideoMetadata(t,this.timeouts.METADATA),l=await this.loadWeights(i),d=this.networks[i],c="number"===typeof r&&r>0?r:this.videoScale,[h,m]=[this.createOffscreenCanvas(a,n,c),this.createOffscreenCanvas(a,n,1)],u=this.createBlobPromise("videoBlob",this.timeouts.VIDEO);return this.getWorker().postMessage({cmd:"process",file:t,fileSize:t.size,duration:o,adjustedResolution:{adjustedInputWidth:a,adjustedInputHeight:n,adjustedOutputWidth:a*c,adjustedOutputHeight:n*c},upscaled:h,original:m,weights:l,network_name:d.name,skipDemuxProgress:!0},[h,m]),u}async startRealtimeUpscale(e,t,s,r){if(!e||!t)throw new Error("videoElement and canvasElement are required");const i=s||this.networkSize;this.validateNetworkSize(i);const a=this.networks[i],n=await this.loadWeights(i),o="number"===typeof r&&r>0?r:this.videoScale;await new Promise((t,s)=>{if(e.readyState>=1)return void t();let r,i;const a=()=>{e.removeEventListener("loadedmetadata",r),e.removeEventListener("error",i)};r=()=>{a(),t()},i=()=>{a(),s(new Error("Failed to load video metadata"))},e.addEventListener("loadedmetadata",r),e.addEventListener("error",i)});const l=e.videoWidth,d=e.videoHeight;if(!l||!d)throw new Error("Invalid video dimensions");const c=!t._upscalerTransferred;let h=null;if(c){if(t.width=l*o,t.height=d*o,"function"!==typeof t.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");h=t.transferControlToOffscreen(),t._upscalerTransferred=!0}const m=document.createElement("canvas");m.width=l,m.height=d;const u=m.getContext("2d");this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null);const p={cmd:"realtimeInit",data:{upscaled:c?h:null,resolution:{width:l,height:d,scale:o,outputWidth:l*o,outputHeight:d*o},network_name:a.name,weights:n}};c&&h?this.getWorker().postMessage(p,[h]):this.getWorker().postMessage(p),this.realtimeState={running:!0,video:e,canvas:t,scale:o,busy:!1,frameIndex:0,captureCanvas:m,captureCtx:u};const g=async()=>{if(!this.realtimeState||!this.realtimeState.running)return;const e=this.realtimeState,t=e.video,s=t.HAVE_CURRENT_DATA||2;if(!t.paused&&!t.ended&&!e.busy&&t.readyState>=s&&t.videoWidth>0&&t.videoHeight>0){e.busy=!0;try{const s=e.captureCanvas,r=e.captureCtx;if(!s||!r)throw new Error("Missing capture canvas");r.drawImage(t,0,0,s.width,s.height);const i=await createImageBitmap(s);e.frameIndex+=1,this.getWorker().postMessage({cmd:"realtimeFrame",frame:i},[i])}catch(r){console.warn("realtimeFrame error",r)}finally{this.realtimeState&&(this.realtimeState.busy=!1)}}this.realtimeLoopId=requestAnimationFrame(g)};this.realtimeLoopId=requestAnimationFrame(g)}stopRealtimeUpscale(){this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null),this.realtimeState&&(this.realtimeState.running=!1,this.realtimeState=null)}async downloadUpscaled(e,t,s=null){const r=e.type.startsWith("video"),i=t||this.networkSize,a=r?await this.upscaleVideo(e,i):await this.upscaleImage(e,i),n=URL.createObjectURL(a),o=document.createElement("a");return o.href=n,o.download=s||`upscaled_${Date.now()}.${r?"mp4":"png"}`,o.click(),URL.revokeObjectURL(n),a}getSupportedNetworks(){return Object.keys(this.networks)}static async getGPUCapability(){try{if(navigator.gpu){const e=await navigator.gpu.requestAdapter();if(e){const t=e.limits.maxStorageBufferBindingSize/512,s=67108864;return Math.min(t,s)}}const e=document.createElement("canvas").getContext("webgl2");return e?e.getParameter(e.MAX_TEXTURE_SIZE)**2:8388608}catch{return 8388608}}static getBackendType(){try{return navigator.gpu?"webgpu":document.createElement("canvas").getContext("webgl2")?"webgl":"unknown"}catch{return"unknown"}}getAppState(){return{backend:e.getBackendType(),isProcessing:!1,progress:0,width:0,height:0}}onProgress(e){"function"===typeof e&&(this.progressCallback=e)}dispose(){this.stopRealtimeUpscale(),this.workerInstance&&(this.workerInstance.terminate(),this.workerInstance=null),this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.weightsCache.clear()}};t(s,"DEFAULT_TIMEOUTS",{IMAGE:3e5,VIDEO:36e5,METADATA:1e4}),t(s,"DEFAULT_DELAYS",{INIT:500,NETWORK:300});let r=s;return function(e={networkSize:"medium",compare:!1,weightsBaseUrl:"/weights",workerUrl:"/worker/main.js",videoScale:2}){return t=>{const{$video:s,$player:i}=t.template,a=document.createElement("canvas");function n(){const e=t.video,s=i.offsetWidth||640,r=i.offsetHeight||360,a=(e.videoWidth||640)/(e.videoHeight||360);let n=s,o=r;return s/r>a?n=r*a:o=s/a,{displayWidth:n,displayHeight:o}}if(i.appendChild(a),a.style.position="absolute",a.style.zIndex="11",a.style.pointerEvents="none",a.style.top="50%",a.style.left="50%",a.style.transform="translate(-50%, -50%)",a.style.opacity="0",a.style.transition="opacity 0.2s ease",!r||!r.isSupported||!r.isSupported())return void console.warn("Upscaler is not supported in this environment");const o=new r({networkSize:e.networkSize||"medium",weightsBaseUrl:e.weightsBaseUrl||"/weights",workerUrl:e.workerUrl||"/worker/main.js",videoScale:e.videoScale||2});let l=!1;async function d(){if(!l){l=!0;try{await new Promise((e,t)=>{if(s.readyState>=1)return void e();let r,i;const a=()=>{s.removeEventListener("loadedmetadata",r),s.removeEventListener("error",i)};r=()=>{a(),e()},i=()=>{a(),t(new Error("Failed to load video metadata"))},s.addEventListener("loadedmetadata",r),s.addEventListener("error",i)}),await o.startRealtimeUpscale(s,a,e.networkSize||o.networkSize),requestAnimationFrame(()=>{a.style.opacity="1"})}catch(t){console.error("artplayer-plugin-upscaler: failed to start realtime",t)}}}const{displayWidth:c,displayHeight:h}=n();a.style.width=`${c}px`,a.style.height=`${h}px`;let m=50,u=!1,p=!!e.compare;const g=document.createElement("div");function w(){if(p){const{displayWidth:e,displayHeight:t}=n(),s=`linear-gradient(to right, transparent 0%, transparent ${m}%, black ${m}%, black 100%)`;a.style.maskImage=s;const r=i.offsetWidth||640,o=((i.offsetHeight||360)-t)/2,l=(r-e)/2+e*m/100;g.style.top=`${o}px`,g.style.left=`${l}px`,g.style.height=`${t}px`,g.style.transform="translateX(-50%)"}else a.style.maskImage="none"}function f(e){p&&(u=!0)}function y(e){if(p&&u){const t=i.getBoundingClientRect(),{displayWidth:s}=n(),r=((i.offsetWidth||640)-s)/2,a=e.clientX-t.left;m=(a-r)/s*100,m=Math.max(0,Math.min(100,m)),w()}}function v(e){u=!1}return g.style.position="absolute",g.style.width="3px",g.style.backgroundColor="rgba(255, 255, 255, 0.75)",g.style.cursor="ew-resize",g.style.zIndex="12",g.style.pointerEvents="auto",g.style.display=p?"block":"none",g.style.boxShadow="0 0 2px rgba(0, 0, 0, 0.1)",p&&(i.appendChild(g),g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()),t.on("destroy",()=>{o&&"function"===typeof o.dispose&&o.dispose(),g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v)}),t.on("resize",()=>{const{displayWidth:e,displayHeight:t}=n();a.style.width=`${e}px`,a.style.height=`${t}px`,w()}),t.on("play",()=>{d()}),{name:"artplayerPluginWebsr",upscaler:o,update:function(s={}){if(s&&"object"===typeof s){if(Object.prototype.hasOwnProperty.call(s,"compare")){const t=!!s.compare;e.compare=t,p=t,p?(g.parentNode||i.appendChild(g),g.style.display="block",g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()):(a.style.maskImage="none",g.style.display="none",g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v))}if(s.networkSize){const r=s.networkSize;e.networkSize=r,o&&"function"===typeof o.stopRealtimeUpscale&&o.stopRealtimeUpscale(),l=!1,t.paused||d()}}}}}}});
diff --git a/docs/compiled/artplayer-plugin-websr.legacy.js b/docs/compiled/artplayer-plugin-websr.legacy.js
deleted file mode 100644
index 6fd1521ab..000000000
--- a/docs/compiled/artplayer-plugin-websr.legacy.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * artplayer-plugin-websr.js v1.0.1
- * Github: https://github.com/zhw2590582/ArtPlayer
- * (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?(t.artplayerPluginWebsr=t(),define(function(){return t.artplayerPluginWebsr})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginWebsr=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,i=Math.pow,n=(t,r,s)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[r]=s,o=(e,i)=>{for(var o in i||(i={}))r.call(i,o)&&n(e,o,i[o]);if(t)for(var o of t(i))s.call(i,o)&&n(e,o,i[o]);return e},a=(e,t,r)=>n(e,"symbol"!==typeof t?t+"":t,r),l=(e,t,r)=>new Promise((s,i)=>{var n=e=>{try{a(r.next(e))}catch(t){i(t)}},o=e=>{try{a(r.throw(e))}catch(t){i(t)}},a=e=>e.done?s(e.value):Promise.resolve(e.value).then(n,o);a((r=r.apply(e,t)).next())});const d=class e{static isSupported(){try{const e="undefined"!==typeof Worker,t="undefined"!==typeof OffscreenCanvas||"undefined"!==typeof document&&!!document.createElement("canvas").transferControlToOffscreen,r="undefined"!==typeof Blob;return e&&t&&r}catch(e){return!1}}static isVideoSupported(){try{return"undefined"!==typeof VideoEncoder&&"undefined"!==typeof VideoDecoder}catch(e){return!1}}constructor(t={}){const r=t.weightsBaseUrl||"/weights";this.networks=t.networks||{small:{name:"anime4k/cnn-2x-l",weightsUrl:`${r}/cnn-8.json`},medium:{name:"anime4k/cnn-2x-16",weightsUrl:`${r}/cnn-16.json`},large:{name:"anime4k/cnn-2x-28",weightsUrl:`${r}/cnn-28.json`}},this.weightsBaseUrl=r,this.networkSize=t.networkSize||"medium",this.workerUrl=t.workerUrl||"/worker/main.js",this.timeouts=o(o({},e.DEFAULT_TIMEOUTS),t.timeouts||{}),this.delays=o(o({},e.DEFAULT_DELAYS),t.delays||{}),this.imageScale="number"===typeof t.imageScale&&t.imageScale>0?t.imageScale:2,this.videoScale="number"===typeof t.videoScale&&t.videoScale>0?t.videoScale:2,this.weightsCache=new Map,this.workerInstance=null,this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.realtimeLoopId=null,this.realtimeState=null,this.init()}init({prewarm:t=!0}={}){if(!e.isSupported())throw new Error("Upscaler is not supported in this environment");return t&&this.getWorker().postMessage({cmd:"isSupported"}),this}getWorker(){return this.workerInstance||(this.workerInstance=new Worker(this.workerUrl),this.workerInstance.onmessage=e=>this.handleWorkerMessage(e)),this.workerInstance}static extractProgressValue(e){var t,r,s;const i=null!=(s=null!=(r=null!=(t=e.data)?t:e.progress)?r:e.value)?s:e.percentage;return"number"===typeof i?Math.min(100,Math.max(0,Math.round(i))):null}handleBlobResponse(e){const t="video"===this.processingType?"videoBlob":"imageBlob",r=this.messageHandlers[t];e.data instanceof Blob&&r&&r({[t]:e.data})}requestBlob(){const e="video"===this.processingType?"getVideoBlob":"getImageBlob";this.getWorker().postMessage({cmd:e})}handleWorkerMessage(t){const{data:r}=t;if(!r.cmd)return;const{cmd:s}=r;if("progress"===s){const t=e.extractProgressValue(r);null!==t&&this.progressCallback&&this.progressCallback(t),this.messageHandlers.progress&&this.messageHandlers.progress(r)}else"finished"===s?r.data instanceof Blob?this.handleBlobResponse(r):this.requestBlob():this.messageHandlers[s]&&this.messageHandlers[s](r)}loadWeights(e){return l(this,null,function*(){if(this.validateNetworkSize(e),this.weightsCache.has(e))return this.weightsCache.get(e);const t=this.networks[e],r=yield fetch(t.weightsUrl);if(!r.ok)throw new Error(`Failed to fetch weights: ${r.statusText}`);const s=yield r.json();return this.weightsCache.set(e,s),s})}static loadImageMetadata(e,t){return new Promise((r,s)=>{const i=new Blob([e],{type:t}),n=URL.createObjectURL(i),o=new Image;o.onload=()=>{URL.revokeObjectURL(n),r({width:o.width,height:o.height})},o.onerror=()=>{URL.revokeObjectURL(n),s(new Error("Failed to load image"))},o.src=n})}static loadVideoMetadata(e,t){return new Promise((r,s)=>{const i=URL.createObjectURL(e),n=document.createElement("video");n.src=i;const o=setTimeout(()=>s(new Error("Timeout")),t);n.onloadedmetadata=()=>{clearTimeout(o),URL.revokeObjectURL(i),r({width:n.videoWidth,height:n.videoHeight,duration:n.duration})},n.onerror=()=>{clearTimeout(o),URL.revokeObjectURL(i),s(new Error("Failed to load video"))}})}createOffscreenCanvas(e,t,r=2){const s="number"===typeof r&&r>0?r:1,i=document.createElement("canvas");if(i.width=e*s,i.height=t*s,"function"!==typeof i.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");return i.transferControlToOffscreen()}createBlobPromise(e,t){return new Promise((r,s)=>{const i=setTimeout(()=>{this.messageHandlers[e]&&(delete this.messageHandlers[e],s(new Error(`${e} processing timeout`)))},t);this.messageHandlers[e]=t=>{delete this.messageHandlers[e],clearTimeout(i);const n=t[e]||t.data;n instanceof Blob?r(n):s(new Error(`Invalid ${e} format`))}})}validateNetworkSize(e){if(!this.networks[e])throw new Error(`Invalid networkSize: use one of ${Object.keys(this.networks).join(", ")}`)}upscaleImage(t,r,s){return l(this,null,function*(){if(!t)throw new Error("Image file is required");const i=r||this.networkSize;this.validateNetworkSize(i),this.processingType="image";const n=yield t.arrayBuffer(),o=t.type||"image/jpeg",{width:a,height:l}=yield e.loadImageMetadata(n,o),d=yield this.loadWeights(i),c=this.networks[i],h="number"===typeof s&&s>0?s:this.imageScale,[u,m]=[this.createOffscreenCanvas(a,l,h),this.createOffscreenCanvas(a,l,1)],p=this.createBlobPromise("imageBlob",this.timeouts.IMAGE);return this.getWorker().postMessage({cmd:"init",data:{imageArrayBuffer:n,imageMimeType:o,upscaled:u,original:m,resolution:{width:a,height:l},network_name:c.name,weights:d}},[u,m]),setTimeout(()=>{this.getWorker().postMessage({cmd:"network",data:{name:c.name,imageArrayBuffer:n,imageMimeType:o,weights:d}}),setTimeout(()=>{this.getWorker().postMessage({cmd:"getImageBlob",data:{imageArrayBuffer:n,imageMimeType:o}})},this.delays.NETWORK)},this.delays.INIT),p})}upscaleVideo(t,r,s){return l(this,null,function*(){if(!t)throw new Error("Video file is required");const i=r||this.networkSize;if(this.validateNetworkSize(i),!e.isVideoSupported())throw new Error("WebCodecs API not supported. Requires Chrome 94+");this.processingType="video";const{width:n,height:o,duration:a}=yield e.loadVideoMetadata(t,this.timeouts.METADATA),l=yield this.loadWeights(i),d=this.networks[i],c="number"===typeof s&&s>0?s:this.videoScale,[h,u]=[this.createOffscreenCanvas(n,o,c),this.createOffscreenCanvas(n,o,1)],m=this.createBlobPromise("videoBlob",this.timeouts.VIDEO);return this.getWorker().postMessage({cmd:"process",file:t,fileSize:t.size,duration:a,adjustedResolution:{adjustedInputWidth:n,adjustedInputHeight:o,adjustedOutputWidth:n*c,adjustedOutputHeight:o*c},upscaled:h,original:u,weights:l,network_name:d.name,skipDemuxProgress:!0},[h,u]),m})}startRealtimeUpscale(e,t,r,s){return l(this,null,function*(){if(!e||!t)throw new Error("videoElement and canvasElement are required");const i=r||this.networkSize;this.validateNetworkSize(i);const n=this.networks[i],o=yield this.loadWeights(i),a="number"===typeof s&&s>0?s:this.videoScale;yield new Promise((t,r)=>{if(e.readyState>=1)return void t();let s,i;const n=()=>{e.removeEventListener("loadedmetadata",s),e.removeEventListener("error",i)};s=()=>{n(),t()},i=()=>{n(),r(new Error("Failed to load video metadata"))},e.addEventListener("loadedmetadata",s),e.addEventListener("error",i)});const d=e.videoWidth,c=e.videoHeight;if(!d||!c)throw new Error("Invalid video dimensions");const h=!t._upscalerTransferred;let u=null;if(h){if(t.width=d*a,t.height=c*a,"function"!==typeof t.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");u=t.transferControlToOffscreen(),t._upscalerTransferred=!0}const m=document.createElement("canvas");m.width=d,m.height=c;const p=m.getContext("2d");this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null);const g={cmd:"realtimeInit",data:{upscaled:h?u:null,resolution:{width:d,height:c,scale:a,outputWidth:d*a,outputHeight:c*a},network_name:n.name,weights:o}};h&&u?this.getWorker().postMessage(g,[u]):this.getWorker().postMessage(g),this.realtimeState={running:!0,video:e,canvas:t,scale:a,busy:!1,frameIndex:0,captureCanvas:m,captureCtx:p};const f=()=>l(this,null,function*(){if(!this.realtimeState||!this.realtimeState.running)return;const e=this.realtimeState,t=e.video,r=t.HAVE_CURRENT_DATA||2;if(!t.paused&&!t.ended&&!e.busy&&t.readyState>=r&&t.videoWidth>0&&t.videoHeight>0){e.busy=!0;try{const r=e.captureCanvas,s=e.captureCtx;if(!r||!s)throw new Error("Missing capture canvas");s.drawImage(t,0,0,r.width,r.height);const i=yield createImageBitmap(r);e.frameIndex+=1,this.getWorker().postMessage({cmd:"realtimeFrame",frame:i},[i])}catch(s){console.warn("realtimeFrame error",s)}finally{this.realtimeState&&(this.realtimeState.busy=!1)}}this.realtimeLoopId=requestAnimationFrame(f)});this.realtimeLoopId=requestAnimationFrame(f)})}stopRealtimeUpscale(){this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null),this.realtimeState&&(this.realtimeState.running=!1,this.realtimeState=null)}downloadUpscaled(e,t,r=null){return l(this,null,function*(){const s=e.type.startsWith("video"),i=t||this.networkSize,n=s?yield this.upscaleVideo(e,i):yield this.upscaleImage(e,i),o=URL.createObjectURL(n),a=document.createElement("a");return a.href=o,a.download=r||`upscaled_${Date.now()}.${s?"mp4":"png"}`,a.click(),URL.revokeObjectURL(o),n})}getSupportedNetworks(){return Object.keys(this.networks)}static getGPUCapability(){return l(this,null,function*(){try{if(navigator.gpu){const e=yield navigator.gpu.requestAdapter();if(e){const t=e.limits.maxStorageBufferBindingSize/512,r=67108864;return Math.min(t,r)}}const e=document.createElement("canvas").getContext("webgl2");return e?i(e.getParameter(e.MAX_TEXTURE_SIZE),2):8388608}catch(e){return 8388608}})}static getBackendType(){try{return navigator.gpu?"webgpu":document.createElement("canvas").getContext("webgl2")?"webgl":"unknown"}catch(e){return"unknown"}}getAppState(){return{backend:e.getBackendType(),isProcessing:!1,progress:0,width:0,height:0}}onProgress(e){"function"===typeof e&&(this.progressCallback=e)}dispose(){this.stopRealtimeUpscale(),this.workerInstance&&(this.workerInstance.terminate(),this.workerInstance=null),this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.weightsCache.clear()}};a(d,"DEFAULT_TIMEOUTS",{IMAGE:3e5,VIDEO:36e5,METADATA:1e4}),a(d,"DEFAULT_DELAYS",{INIT:500,NETWORK:300});let c=d;return function(e={networkSize:"medium",compare:!1,weightsBaseUrl:"/weights",workerUrl:"/worker/main.js",videoScale:2}){return t=>{const{$video:r,$player:s}=t.template,i=document.createElement("canvas");function n(){const e=t.video,r=s.offsetWidth||640,i=s.offsetHeight||360,n=(e.videoWidth||640)/(e.videoHeight||360);let o=r,a=i;return r/i>n?o=i*n:a=r/n,{displayWidth:o,displayHeight:a}}if(s.appendChild(i),i.style.position="absolute",i.style.zIndex="11",i.style.pointerEvents="none",i.style.top="50%",i.style.left="50%",i.style.transform="translate(-50%, -50%)",i.style.opacity="0",i.style.transition="opacity 0.2s ease",!c||!c.isSupported||!c.isSupported())return void console.warn("Upscaler is not supported in this environment");const o=new c({networkSize:e.networkSize||"medium",weightsBaseUrl:e.weightsBaseUrl||"/weights",workerUrl:e.workerUrl||"/worker/main.js",videoScale:e.videoScale||2});let a=!1;function d(){return l(this,null,function*(){if(!a){a=!0;try{yield new Promise((e,t)=>{if(r.readyState>=1)return void e();let s,i;const n=()=>{r.removeEventListener("loadedmetadata",s),r.removeEventListener("error",i)};s=()=>{n(),e()},i=()=>{n(),t(new Error("Failed to load video metadata"))},r.addEventListener("loadedmetadata",s),r.addEventListener("error",i)}),yield o.startRealtimeUpscale(r,i,e.networkSize||o.networkSize),requestAnimationFrame(()=>{i.style.opacity="1"})}catch(t){console.error("artplayer-plugin-upscaler: failed to start realtime",t)}}})}const{displayWidth:h,displayHeight:u}=n();i.style.width=`${h}px`,i.style.height=`${u}px`;let m=50,p=!1,g=!!e.compare;const f=document.createElement("div");function w(){if(g){const{displayWidth:e,displayHeight:t}=n(),r=`linear-gradient(to right, transparent 0%, transparent ${m}%, black ${m}%, black 100%)`;i.style.maskImage=r;const o=s.offsetWidth||640,a=((s.offsetHeight||360)-t)/2,l=(o-e)/2+e*m/100;f.style.top=`${a}px`,f.style.left=`${l}px`,f.style.height=`${t}px`,f.style.transform="translateX(-50%)"}else i.style.maskImage="none"}function y(e){g&&(p=!0)}function v(e){if(g&&p){const t=s.getBoundingClientRect(),{displayWidth:r}=n(),i=((s.offsetWidth||640)-r)/2,o=e.clientX-t.left;m=(o-i)/r*100,m=Math.max(0,Math.min(100,m)),w()}}function k(e){p=!1}return f.style.position="absolute",f.style.width="3px",f.style.backgroundColor="rgba(255, 255, 255, 0.75)",f.style.cursor="ew-resize",f.style.zIndex="12",f.style.pointerEvents="auto",f.style.display=g?"block":"none",f.style.boxShadow="0 0 2px rgba(0, 0, 0, 0.1)",g&&(s.appendChild(f),f.addEventListener("mousedown",y),document.addEventListener("mousemove",v),document.addEventListener("mouseup",k),w()),t.on("destroy",()=>{o&&"function"===typeof o.dispose&&o.dispose(),f.removeEventListener("mousedown",y),document.removeEventListener("mousemove",v),document.removeEventListener("mouseup",k)}),t.on("resize",()=>{const{displayWidth:e,displayHeight:t}=n();i.style.width=`${e}px`,i.style.height=`${t}px`,w()}),t.on("play",()=>{d()}),{name:"artplayerPluginWebsr",upscaler:o,update:function(r={}){if(r&&"object"===typeof r){if(Object.prototype.hasOwnProperty.call(r,"compare")){const t=!!r.compare;e.compare=t,g=t,g?(f.parentNode||s.appendChild(f),f.style.display="block",f.addEventListener("mousedown",y),document.addEventListener("mousemove",v),document.addEventListener("mouseup",k),w()):(i.style.maskImage="none",f.style.display="none",f.removeEventListener("mousedown",y),document.removeEventListener("mousemove",v),document.removeEventListener("mouseup",k))}if(r.networkSize){const s=r.networkSize;e.networkSize=s,o&&"function"===typeof o.stopRealtimeUpscale&&o.stopRealtimeUpscale(),a=!1,t.paused||d()}}}}}}});
diff --git a/docs/compiled/artplayer-plugin-websr.mjs b/docs/compiled/artplayer-plugin-websr.mjs
deleted file mode 100644
index 3ca71e964..000000000
--- a/docs/compiled/artplayer-plugin-websr.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * artplayer-plugin-websr.js v1.0.1
- * Github: https://github.com/zhw2590582/ArtPlayer
- * (c) 2017-2026 Harvey Zhao
- * Released under the MIT License.
- */
-var e=Object.defineProperty,t=(t,s,r)=>((t,s,r)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[s]=r)(t,"symbol"!==typeof s?s+"":s,r);const s=class e{static isSupported(){try{const e="undefined"!==typeof Worker,t="undefined"!==typeof OffscreenCanvas||"undefined"!==typeof document&&!!document.createElement("canvas").transferControlToOffscreen,s="undefined"!==typeof Blob;return e&&t&&s}catch{return!1}}static isVideoSupported(){try{return"undefined"!==typeof VideoEncoder&&"undefined"!==typeof VideoDecoder}catch{return!1}}constructor(t={}){const s=t.weightsBaseUrl||"/weights";this.networks=t.networks||{small:{name:"anime4k/cnn-2x-l",weightsUrl:`${s}/cnn-8.json`},medium:{name:"anime4k/cnn-2x-16",weightsUrl:`${s}/cnn-16.json`},large:{name:"anime4k/cnn-2x-28",weightsUrl:`${s}/cnn-28.json`}},this.weightsBaseUrl=s,this.networkSize=t.networkSize||"medium",this.workerUrl=t.workerUrl||"/worker/main.js",this.timeouts={...e.DEFAULT_TIMEOUTS,...t.timeouts||{}},this.delays={...e.DEFAULT_DELAYS,...t.delays||{}},this.imageScale="number"===typeof t.imageScale&&t.imageScale>0?t.imageScale:2,this.videoScale="number"===typeof t.videoScale&&t.videoScale>0?t.videoScale:2,this.weightsCache=/* @__PURE__ */new Map,this.workerInstance=null,this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.realtimeLoopId=null,this.realtimeState=null,this.init()}init({prewarm:t=!0}={}){if(!e.isSupported())throw new Error("Upscaler is not supported in this environment");return t&&this.getWorker().postMessage({cmd:"isSupported"}),this}getWorker(){return this.workerInstance||(this.workerInstance=new Worker(this.workerUrl),this.workerInstance.onmessage=e=>this.handleWorkerMessage(e)),this.workerInstance}static extractProgressValue(e){const t=e.data??e.progress??e.value??e.percentage;return"number"===typeof t?Math.min(100,Math.max(0,Math.round(t))):null}handleBlobResponse(e){const t="video"===this.processingType?"videoBlob":"imageBlob",s=this.messageHandlers[t];e.data instanceof Blob&&s&&s({[t]:e.data})}requestBlob(){const e="video"===this.processingType?"getVideoBlob":"getImageBlob";this.getWorker().postMessage({cmd:e})}handleWorkerMessage(t){const{data:s}=t;if(!s.cmd)return;const{cmd:r}=s;if("progress"===r){const t=e.extractProgressValue(s);null!==t&&this.progressCallback&&this.progressCallback(t),this.messageHandlers.progress&&this.messageHandlers.progress(s)}else"finished"===r?s.data instanceof Blob?this.handleBlobResponse(s):this.requestBlob():this.messageHandlers[r]&&this.messageHandlers[r](s)}async loadWeights(e){if(this.validateNetworkSize(e),this.weightsCache.has(e))return this.weightsCache.get(e);const t=this.networks[e],s=await fetch(t.weightsUrl);if(!s.ok)throw new Error(`Failed to fetch weights: ${s.statusText}`);const r=await s.json();return this.weightsCache.set(e,r),r}static loadImageMetadata(e,t){return new Promise((s,r)=>{const i=new Blob([e],{type:t}),a=URL.createObjectURL(i),o=new Image;o.onload=()=>{URL.revokeObjectURL(a),s({width:o.width,height:o.height})},o.onerror=()=>{URL.revokeObjectURL(a),r(new Error("Failed to load image"))},o.src=a})}static loadVideoMetadata(e,t){return new Promise((s,r)=>{const i=URL.createObjectURL(e),a=document.createElement("video");a.src=i;const o=setTimeout(()=>r(new Error("Timeout")),t);a.onloadedmetadata=()=>{clearTimeout(o),URL.revokeObjectURL(i),s({width:a.videoWidth,height:a.videoHeight,duration:a.duration})},a.onerror=()=>{clearTimeout(o),URL.revokeObjectURL(i),r(new Error("Failed to load video"))}})}createOffscreenCanvas(e,t,s=2){const r="number"===typeof s&&s>0?s:1,i=document.createElement("canvas");if(i.width=e*r,i.height=t*r,"function"!==typeof i.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");return i.transferControlToOffscreen()}createBlobPromise(e,t){return new Promise((s,r)=>{const i=setTimeout(()=>{this.messageHandlers[e]&&(delete this.messageHandlers[e],r(new Error(`${e} processing timeout`)))},t);this.messageHandlers[e]=t=>{delete this.messageHandlers[e],clearTimeout(i);const a=t[e]||t.data;a instanceof Blob?s(a):r(new Error(`Invalid ${e} format`))}})}validateNetworkSize(e){if(!this.networks[e])throw new Error(`Invalid networkSize: use one of ${Object.keys(this.networks).join(", ")}`)}async upscaleImage(t,s,r){if(!t)throw new Error("Image file is required");const i=s||this.networkSize;this.validateNetworkSize(i),this.processingType="image";const a=await t.arrayBuffer(),o=t.type||"image/jpeg",{width:n,height:l}=await e.loadImageMetadata(a,o),d=await this.loadWeights(i),c=this.networks[i],h="number"===typeof r&&r>0?r:this.imageScale,[m,u]=[this.createOffscreenCanvas(n,l,h),this.createOffscreenCanvas(n,l,1)],p=this.createBlobPromise("imageBlob",this.timeouts.IMAGE);return this.getWorker().postMessage({cmd:"init",data:{imageArrayBuffer:a,imageMimeType:o,upscaled:m,original:u,resolution:{width:n,height:l},network_name:c.name,weights:d}},[m,u]),setTimeout(()=>{this.getWorker().postMessage({cmd:"network",data:{name:c.name,imageArrayBuffer:a,imageMimeType:o,weights:d}}),setTimeout(()=>{this.getWorker().postMessage({cmd:"getImageBlob",data:{imageArrayBuffer:a,imageMimeType:o}})},this.delays.NETWORK)},this.delays.INIT),p}async upscaleVideo(t,s,r){if(!t)throw new Error("Video file is required");const i=s||this.networkSize;if(this.validateNetworkSize(i),!e.isVideoSupported())throw new Error("WebCodecs API not supported. Requires Chrome 94+");this.processingType="video";const{width:a,height:o,duration:n}=await e.loadVideoMetadata(t,this.timeouts.METADATA),l=await this.loadWeights(i),d=this.networks[i],c="number"===typeof r&&r>0?r:this.videoScale,[h,m]=[this.createOffscreenCanvas(a,o,c),this.createOffscreenCanvas(a,o,1)],u=this.createBlobPromise("videoBlob",this.timeouts.VIDEO);return this.getWorker().postMessage({cmd:"process",file:t,fileSize:t.size,duration:n,adjustedResolution:{adjustedInputWidth:a,adjustedInputHeight:o,adjustedOutputWidth:a*c,adjustedOutputHeight:o*c},upscaled:h,original:m,weights:l,network_name:d.name,skipDemuxProgress:!0},[h,m]),u}async startRealtimeUpscale(e,t,s,r){if(!e||!t)throw new Error("videoElement and canvasElement are required");const i=s||this.networkSize;this.validateNetworkSize(i);const a=this.networks[i],o=await this.loadWeights(i),n="number"===typeof r&&r>0?r:this.videoScale;await new Promise((t,s)=>{if(e.readyState>=1)return void t();let r,i;const a=()=>{e.removeEventListener("loadedmetadata",r),e.removeEventListener("error",i)};r=()=>{a(),t()},i=()=>{a(),s(new Error("Failed to load video metadata"))},e.addEventListener("loadedmetadata",r),e.addEventListener("error",i)});const l=e.videoWidth,d=e.videoHeight;if(!l||!d)throw new Error("Invalid video dimensions");const c=!t._upscalerTransferred;let h=null;if(c){if(t.width=l*n,t.height=d*n,"function"!==typeof t.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");h=t.transferControlToOffscreen(),t._upscalerTransferred=!0}const m=document.createElement("canvas");m.width=l,m.height=d;const u=m.getContext("2d");this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null);const p={cmd:"realtimeInit",data:{upscaled:c?h:null,resolution:{width:l,height:d,scale:n,outputWidth:l*n,outputHeight:d*n},network_name:a.name,weights:o}};c&&h?this.getWorker().postMessage(p,[h]):this.getWorker().postMessage(p),this.realtimeState={running:!0,video:e,canvas:t,scale:n,busy:!1,frameIndex:0,captureCanvas:m,captureCtx:u};const g=async()=>{if(!this.realtimeState||!this.realtimeState.running)return;const e=this.realtimeState,t=e.video,s=t.HAVE_CURRENT_DATA||2;if(!t.paused&&!t.ended&&!e.busy&&t.readyState>=s&&t.videoWidth>0&&t.videoHeight>0){e.busy=!0;try{const s=e.captureCanvas,r=e.captureCtx;if(!s||!r)throw new Error("Missing capture canvas");r.drawImage(t,0,0,s.width,s.height);const i=await createImageBitmap(s);e.frameIndex+=1,this.getWorker().postMessage({cmd:"realtimeFrame",frame:i},[i])}catch(r){console.warn("realtimeFrame error",r)}finally{this.realtimeState&&(this.realtimeState.busy=!1)}}this.realtimeLoopId=requestAnimationFrame(g)};this.realtimeLoopId=requestAnimationFrame(g)}stopRealtimeUpscale(){this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null),this.realtimeState&&(this.realtimeState.running=!1,this.realtimeState=null)}async downloadUpscaled(e,t,s=null){const r=e.type.startsWith("video"),i=t||this.networkSize,a=r?await this.upscaleVideo(e,i):await this.upscaleImage(e,i),o=URL.createObjectURL(a),n=document.createElement("a");return n.href=o,n.download=s||`upscaled_${Date.now()}.${r?"mp4":"png"}`,n.click(),URL.revokeObjectURL(o),a}getSupportedNetworks(){return Object.keys(this.networks)}static async getGPUCapability(){try{if(navigator.gpu){const e=await navigator.gpu.requestAdapter();if(e){const t=e.limits.maxStorageBufferBindingSize/512,s=67108864;return Math.min(t,s)}}const e=document.createElement("canvas").getContext("webgl2");return e?e.getParameter(e.MAX_TEXTURE_SIZE)**2:8388608}catch{return 8388608}}static getBackendType(){try{return navigator.gpu?"webgpu":document.createElement("canvas").getContext("webgl2")?"webgl":"unknown"}catch{return"unknown"}}getAppState(){return{backend:e.getBackendType(),isProcessing:!1,progress:0,width:0,height:0}}onProgress(e){"function"===typeof e&&(this.progressCallback=e)}dispose(){this.stopRealtimeUpscale(),this.workerInstance&&(this.workerInstance.terminate(),this.workerInstance=null),this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.weightsCache.clear()}};t(s,"DEFAULT_TIMEOUTS",{IMAGE:3e5,VIDEO:36e5,METADATA:1e4}),t(s,"DEFAULT_DELAYS",{INIT:500,NETWORK:300});let r=s;function i(e={networkSize:"medium",compare:!1,weightsBaseUrl:"/weights",workerUrl:"/worker/main.js",videoScale:2}){return t=>{const{$video:s,$player:i}=t.template,a=document.createElement("canvas");function o(){const e=t.video,s=i.offsetWidth||640,r=i.offsetHeight||360,a=(e.videoWidth||640)/(e.videoHeight||360);let o=s,n=r;return s/r>a?o=r*a:n=s/a,{displayWidth:o,displayHeight:n}}if(i.appendChild(a),a.style.position="absolute",a.style.zIndex="11",a.style.pointerEvents="none",a.style.top="50%",a.style.left="50%",a.style.transform="translate(-50%, -50%)",a.style.opacity="0",a.style.transition="opacity 0.2s ease",!r||!r.isSupported||!r.isSupported())return void console.warn("Upscaler is not supported in this environment");const n=new r({networkSize:e.networkSize||"medium",weightsBaseUrl:e.weightsBaseUrl||"/weights",workerUrl:e.workerUrl||"/worker/main.js",videoScale:e.videoScale||2});let l=!1;async function d(){if(!l){l=!0;try{await new Promise((e,t)=>{if(s.readyState>=1)return void e();let r,i;const a=()=>{s.removeEventListener("loadedmetadata",r),s.removeEventListener("error",i)};r=()=>{a(),e()},i=()=>{a(),t(new Error("Failed to load video metadata"))},s.addEventListener("loadedmetadata",r),s.addEventListener("error",i)}),await n.startRealtimeUpscale(s,a,e.networkSize||n.networkSize),requestAnimationFrame(()=>{a.style.opacity="1"})}catch(t){console.error("artplayer-plugin-upscaler: failed to start realtime",t)}}}const{displayWidth:c,displayHeight:h}=o();a.style.width=`${c}px`,a.style.height=`${h}px`;let m=50,u=!1,p=!!e.compare;const g=document.createElement("div");function w(){if(p){const{displayWidth:e,displayHeight:t}=o(),s=`linear-gradient(to right, transparent 0%, transparent ${m}%, black ${m}%, black 100%)`;a.style.maskImage=s;const r=i.offsetWidth||640,n=((i.offsetHeight||360)-t)/2,l=(r-e)/2+e*m/100;g.style.top=`${n}px`,g.style.left=`${l}px`,g.style.height=`${t}px`,g.style.transform="translateX(-50%)"}else a.style.maskImage="none"}function f(e){p&&(u=!0)}function y(e){if(p&&u){const t=i.getBoundingClientRect(),{displayWidth:s}=o(),r=((i.offsetWidth||640)-s)/2,a=e.clientX-t.left;m=(a-r)/s*100,m=Math.max(0,Math.min(100,m)),w()}}function v(e){u=!1}return g.style.position="absolute",g.style.width="3px",g.style.backgroundColor="rgba(255, 255, 255, 0.75)",g.style.cursor="ew-resize",g.style.zIndex="12",g.style.pointerEvents="auto",g.style.display=p?"block":"none",g.style.boxShadow="0 0 2px rgba(0, 0, 0, 0.1)",p&&(i.appendChild(g),g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()),t.on("destroy",()=>{n&&"function"===typeof n.dispose&&n.dispose(),g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v)}),t.on("resize",()=>{const{displayWidth:e,displayHeight:t}=o();a.style.width=`${e}px`,a.style.height=`${t}px`,w()}),t.on("play",()=>{d()}),{name:"artplayerPluginWebsr",upscaler:n,update:function(s={}){if(s&&"object"===typeof s){if(Object.prototype.hasOwnProperty.call(s,"compare")){const t=!!s.compare;e.compare=t,p=t,p?(g.parentNode||i.appendChild(g),g.style.display="block",g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()):(a.style.maskImage="none",g.style.display="none",g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v))}if(s.networkSize){const r=s.networkSize;e.networkSize=r,n&&"function"===typeof n.stopRealtimeUpscale&&n.stopRealtimeUpscale(),l=!1,t.paused||d()}}}}}}export{i as default};
diff --git a/docs/index.html b/docs/index.html
index a5385b677..af773c275 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -149,10 +149,6 @@
artplayer-plugin-audio-track
-
- artplayer-plugin-websr
-
- Proxys
diff --git a/docs/upscaler/index.html b/docs/upscaler/index.html
index 0b0698624..0ea8fbd54 100644
--- a/docs/upscaler/index.html
+++ b/docs/upscaler/index.html
@@ -637,8 +637,8 @@
// Create a configurable Upscaler instance
const upscaler = new Upscaler({
networkSize: "medium",
- weightsBaseUrl: "/assets/websr/weights",
- workerUrl: "/assets/websr/worker/main.js",
+ weightsBaseUrl: "/upscaler/weights",
+ workerUrl: "/upscaler/worker/main.js",
});
function updateComparisonPosition(element, rect, x) {
diff --git a/docs/assets/websr/weights/cnn-16.json b/docs/upscaler/weights/cnn-16.json
similarity index 100%
rename from docs/assets/websr/weights/cnn-16.json
rename to docs/upscaler/weights/cnn-16.json
diff --git a/docs/assets/websr/weights/cnn-28.json b/docs/upscaler/weights/cnn-28.json
similarity index 100%
rename from docs/assets/websr/weights/cnn-28.json
rename to docs/upscaler/weights/cnn-28.json
diff --git a/docs/assets/websr/weights/cnn-8.json b/docs/upscaler/weights/cnn-8.json
similarity index 100%
rename from docs/assets/websr/weights/cnn-8.json
rename to docs/upscaler/weights/cnn-8.json
diff --git a/docs/assets/websr/worker/main.js b/docs/upscaler/worker/main.js
similarity index 100%
rename from docs/assets/websr/worker/main.js
rename to docs/upscaler/worker/main.js
diff --git a/docs/assets/websr/worker/video.js b/docs/upscaler/worker/video.js
similarity index 100%
rename from docs/assets/websr/worker/video.js
rename to docs/upscaler/worker/video.js
diff --git a/packages/artplayer-plugin-websr/.npmignore b/packages/artplayer-plugin-websr/.npmignore
deleted file mode 100644
index 932395606..000000000
--- a/packages/artplayer-plugin-websr/.npmignore
+++ /dev/null
@@ -1,6 +0,0 @@
-node_modules
-.DS_Store
-.parcel-cache
-yarn.lock
-package-lock.json
-src
\ No newline at end of file
diff --git a/packages/artplayer-plugin-websr/README.md b/packages/artplayer-plugin-websr/README.md
deleted file mode 100644
index 082d5e32a..000000000
--- a/packages/artplayer-plugin-websr/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# artplayer-plugin-websr
-
-websr plugin for ArtPlayer
-
-## Demo
-
-[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-websr/index.js&example=websr)
-
-## License
-
-MIT © Harvey Zhao
diff --git a/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.js b/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.js
deleted file mode 100644
index 0441ab998..000000000
--- a/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * artplayer-plugin-websr.js v1.0.1
- * Github: https://github.com/zhw2590582/ArtPlayer
- * (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?(t.artplayerPluginWebsr=t(),define(function(){return t.artplayerPluginWebsr})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginWebsr=t()}(this,function(){"use strict";var e=Object.defineProperty,t=(t,s,r)=>((t,s,r)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[s]=r)(t,"symbol"!==typeof s?s+"":s,r);const s=class e{static isSupported(){try{const e="undefined"!==typeof Worker,t="undefined"!==typeof OffscreenCanvas||"undefined"!==typeof document&&!!document.createElement("canvas").transferControlToOffscreen,s="undefined"!==typeof Blob;return e&&t&&s}catch{return!1}}static isVideoSupported(){try{return"undefined"!==typeof VideoEncoder&&"undefined"!==typeof VideoDecoder}catch{return!1}}constructor(t={}){const s=t.weightsBaseUrl||"/weights";this.networks=t.networks||{small:{name:"anime4k/cnn-2x-l",weightsUrl:`${s}/cnn-8.json`},medium:{name:"anime4k/cnn-2x-16",weightsUrl:`${s}/cnn-16.json`},large:{name:"anime4k/cnn-2x-28",weightsUrl:`${s}/cnn-28.json`}},this.weightsBaseUrl=s,this.networkSize=t.networkSize||"medium",this.workerUrl=t.workerUrl||"/worker/main.js",this.timeouts={...e.DEFAULT_TIMEOUTS,...t.timeouts||{}},this.delays={...e.DEFAULT_DELAYS,...t.delays||{}},this.imageScale="number"===typeof t.imageScale&&t.imageScale>0?t.imageScale:2,this.videoScale="number"===typeof t.videoScale&&t.videoScale>0?t.videoScale:2,this.weightsCache=new Map,this.workerInstance=null,this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.realtimeLoopId=null,this.realtimeState=null,this.init()}init({prewarm:t=!0}={}){if(!e.isSupported())throw new Error("Upscaler is not supported in this environment");return t&&this.getWorker().postMessage({cmd:"isSupported"}),this}getWorker(){return this.workerInstance||(this.workerInstance=new Worker(this.workerUrl),this.workerInstance.onmessage=e=>this.handleWorkerMessage(e)),this.workerInstance}static extractProgressValue(e){const t=e.data??e.progress??e.value??e.percentage;return"number"===typeof t?Math.min(100,Math.max(0,Math.round(t))):null}handleBlobResponse(e){const t="video"===this.processingType?"videoBlob":"imageBlob",s=this.messageHandlers[t];e.data instanceof Blob&&s&&s({[t]:e.data})}requestBlob(){const e="video"===this.processingType?"getVideoBlob":"getImageBlob";this.getWorker().postMessage({cmd:e})}handleWorkerMessage(t){const{data:s}=t;if(!s.cmd)return;const{cmd:r}=s;if("progress"===r){const t=e.extractProgressValue(s);null!==t&&this.progressCallback&&this.progressCallback(t),this.messageHandlers.progress&&this.messageHandlers.progress(s)}else"finished"===r?s.data instanceof Blob?this.handleBlobResponse(s):this.requestBlob():this.messageHandlers[r]&&this.messageHandlers[r](s)}async loadWeights(e){if(this.validateNetworkSize(e),this.weightsCache.has(e))return this.weightsCache.get(e);const t=this.networks[e],s=await fetch(t.weightsUrl);if(!s.ok)throw new Error(`Failed to fetch weights: ${s.statusText}`);const r=await s.json();return this.weightsCache.set(e,r),r}static loadImageMetadata(e,t){return new Promise((s,r)=>{const i=new Blob([e],{type:t}),a=URL.createObjectURL(i),n=new Image;n.onload=()=>{URL.revokeObjectURL(a),s({width:n.width,height:n.height})},n.onerror=()=>{URL.revokeObjectURL(a),r(new Error("Failed to load image"))},n.src=a})}static loadVideoMetadata(e,t){return new Promise((s,r)=>{const i=URL.createObjectURL(e),a=document.createElement("video");a.src=i;const n=setTimeout(()=>r(new Error("Timeout")),t);a.onloadedmetadata=()=>{clearTimeout(n),URL.revokeObjectURL(i),s({width:a.videoWidth,height:a.videoHeight,duration:a.duration})},a.onerror=()=>{clearTimeout(n),URL.revokeObjectURL(i),r(new Error("Failed to load video"))}})}createOffscreenCanvas(e,t,s=2){const r="number"===typeof s&&s>0?s:1,i=document.createElement("canvas");if(i.width=e*r,i.height=t*r,"function"!==typeof i.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");return i.transferControlToOffscreen()}createBlobPromise(e,t){return new Promise((s,r)=>{const i=setTimeout(()=>{this.messageHandlers[e]&&(delete this.messageHandlers[e],r(new Error(`${e} processing timeout`)))},t);this.messageHandlers[e]=t=>{delete this.messageHandlers[e],clearTimeout(i);const a=t[e]||t.data;a instanceof Blob?s(a):r(new Error(`Invalid ${e} format`))}})}validateNetworkSize(e){if(!this.networks[e])throw new Error(`Invalid networkSize: use one of ${Object.keys(this.networks).join(", ")}`)}async upscaleImage(t,s,r){if(!t)throw new Error("Image file is required");const i=s||this.networkSize;this.validateNetworkSize(i),this.processingType="image";const a=await t.arrayBuffer(),n=t.type||"image/jpeg",{width:o,height:l}=await e.loadImageMetadata(a,n),d=await this.loadWeights(i),c=this.networks[i],h="number"===typeof r&&r>0?r:this.imageScale,[m,u]=[this.createOffscreenCanvas(o,l,h),this.createOffscreenCanvas(o,l,1)],p=this.createBlobPromise("imageBlob",this.timeouts.IMAGE);return this.getWorker().postMessage({cmd:"init",data:{imageArrayBuffer:a,imageMimeType:n,upscaled:m,original:u,resolution:{width:o,height:l},network_name:c.name,weights:d}},[m,u]),setTimeout(()=>{this.getWorker().postMessage({cmd:"network",data:{name:c.name,imageArrayBuffer:a,imageMimeType:n,weights:d}}),setTimeout(()=>{this.getWorker().postMessage({cmd:"getImageBlob",data:{imageArrayBuffer:a,imageMimeType:n}})},this.delays.NETWORK)},this.delays.INIT),p}async upscaleVideo(t,s,r){if(!t)throw new Error("Video file is required");const i=s||this.networkSize;if(this.validateNetworkSize(i),!e.isVideoSupported())throw new Error("WebCodecs API not supported. Requires Chrome 94+");this.processingType="video";const{width:a,height:n,duration:o}=await e.loadVideoMetadata(t,this.timeouts.METADATA),l=await this.loadWeights(i),d=this.networks[i],c="number"===typeof r&&r>0?r:this.videoScale,[h,m]=[this.createOffscreenCanvas(a,n,c),this.createOffscreenCanvas(a,n,1)],u=this.createBlobPromise("videoBlob",this.timeouts.VIDEO);return this.getWorker().postMessage({cmd:"process",file:t,fileSize:t.size,duration:o,adjustedResolution:{adjustedInputWidth:a,adjustedInputHeight:n,adjustedOutputWidth:a*c,adjustedOutputHeight:n*c},upscaled:h,original:m,weights:l,network_name:d.name,skipDemuxProgress:!0},[h,m]),u}async startRealtimeUpscale(e,t,s,r){if(!e||!t)throw new Error("videoElement and canvasElement are required");const i=s||this.networkSize;this.validateNetworkSize(i);const a=this.networks[i],n=await this.loadWeights(i),o="number"===typeof r&&r>0?r:this.videoScale;await new Promise((t,s)=>{if(e.readyState>=1)return void t();let r,i;const a=()=>{e.removeEventListener("loadedmetadata",r),e.removeEventListener("error",i)};r=()=>{a(),t()},i=()=>{a(),s(new Error("Failed to load video metadata"))},e.addEventListener("loadedmetadata",r),e.addEventListener("error",i)});const l=e.videoWidth,d=e.videoHeight;if(!l||!d)throw new Error("Invalid video dimensions");const c=!t._upscalerTransferred;let h=null;if(c){if(t.width=l*o,t.height=d*o,"function"!==typeof t.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");h=t.transferControlToOffscreen(),t._upscalerTransferred=!0}const m=document.createElement("canvas");m.width=l,m.height=d;const u=m.getContext("2d");this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null);const p={cmd:"realtimeInit",data:{upscaled:c?h:null,resolution:{width:l,height:d,scale:o,outputWidth:l*o,outputHeight:d*o},network_name:a.name,weights:n}};c&&h?this.getWorker().postMessage(p,[h]):this.getWorker().postMessage(p),this.realtimeState={running:!0,video:e,canvas:t,scale:o,busy:!1,frameIndex:0,captureCanvas:m,captureCtx:u};const g=async()=>{if(!this.realtimeState||!this.realtimeState.running)return;const e=this.realtimeState,t=e.video,s=t.HAVE_CURRENT_DATA||2;if(!t.paused&&!t.ended&&!e.busy&&t.readyState>=s&&t.videoWidth>0&&t.videoHeight>0){e.busy=!0;try{const s=e.captureCanvas,r=e.captureCtx;if(!s||!r)throw new Error("Missing capture canvas");r.drawImage(t,0,0,s.width,s.height);const i=await createImageBitmap(s);e.frameIndex+=1,this.getWorker().postMessage({cmd:"realtimeFrame",frame:i},[i])}catch(r){console.warn("realtimeFrame error",r)}finally{this.realtimeState&&(this.realtimeState.busy=!1)}}this.realtimeLoopId=requestAnimationFrame(g)};this.realtimeLoopId=requestAnimationFrame(g)}stopRealtimeUpscale(){this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null),this.realtimeState&&(this.realtimeState.running=!1,this.realtimeState=null)}async downloadUpscaled(e,t,s=null){const r=e.type.startsWith("video"),i=t||this.networkSize,a=r?await this.upscaleVideo(e,i):await this.upscaleImage(e,i),n=URL.createObjectURL(a),o=document.createElement("a");return o.href=n,o.download=s||`upscaled_${Date.now()}.${r?"mp4":"png"}`,o.click(),URL.revokeObjectURL(n),a}getSupportedNetworks(){return Object.keys(this.networks)}static async getGPUCapability(){try{if(navigator.gpu){const e=await navigator.gpu.requestAdapter();if(e){const t=e.limits.maxStorageBufferBindingSize/512,s=67108864;return Math.min(t,s)}}const e=document.createElement("canvas").getContext("webgl2");return e?e.getParameter(e.MAX_TEXTURE_SIZE)**2:8388608}catch{return 8388608}}static getBackendType(){try{return navigator.gpu?"webgpu":document.createElement("canvas").getContext("webgl2")?"webgl":"unknown"}catch{return"unknown"}}getAppState(){return{backend:e.getBackendType(),isProcessing:!1,progress:0,width:0,height:0}}onProgress(e){"function"===typeof e&&(this.progressCallback=e)}dispose(){this.stopRealtimeUpscale(),this.workerInstance&&(this.workerInstance.terminate(),this.workerInstance=null),this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.weightsCache.clear()}};t(s,"DEFAULT_TIMEOUTS",{IMAGE:3e5,VIDEO:36e5,METADATA:1e4}),t(s,"DEFAULT_DELAYS",{INIT:500,NETWORK:300});let r=s;return function(e={networkSize:"medium",compare:!1,weightsBaseUrl:"/weights",workerUrl:"/worker/main.js",videoScale:2}){return t=>{const{$video:s,$player:i}=t.template,a=document.createElement("canvas");function n(){const e=t.video,s=i.offsetWidth||640,r=i.offsetHeight||360,a=(e.videoWidth||640)/(e.videoHeight||360);let n=s,o=r;return s/r>a?n=r*a:o=s/a,{displayWidth:n,displayHeight:o}}if(i.appendChild(a),a.style.position="absolute",a.style.zIndex="11",a.style.pointerEvents="none",a.style.top="50%",a.style.left="50%",a.style.transform="translate(-50%, -50%)",a.style.opacity="0",a.style.transition="opacity 0.2s ease",!r||!r.isSupported||!r.isSupported())return void console.warn("Upscaler is not supported in this environment");const o=new r({networkSize:e.networkSize||"medium",weightsBaseUrl:e.weightsBaseUrl||"/weights",workerUrl:e.workerUrl||"/worker/main.js",videoScale:e.videoScale||2});let l=!1;async function d(){if(!l){l=!0;try{await new Promise((e,t)=>{if(s.readyState>=1)return void e();let r,i;const a=()=>{s.removeEventListener("loadedmetadata",r),s.removeEventListener("error",i)};r=()=>{a(),e()},i=()=>{a(),t(new Error("Failed to load video metadata"))},s.addEventListener("loadedmetadata",r),s.addEventListener("error",i)}),await o.startRealtimeUpscale(s,a,e.networkSize||o.networkSize),requestAnimationFrame(()=>{a.style.opacity="1"})}catch(t){console.error("artplayer-plugin-upscaler: failed to start realtime",t)}}}const{displayWidth:c,displayHeight:h}=n();a.style.width=`${c}px`,a.style.height=`${h}px`;let m=50,u=!1,p=!!e.compare;const g=document.createElement("div");function w(){if(p){const{displayWidth:e,displayHeight:t}=n(),s=`linear-gradient(to right, transparent 0%, transparent ${m}%, black ${m}%, black 100%)`;a.style.maskImage=s;const r=i.offsetWidth||640,o=((i.offsetHeight||360)-t)/2,l=(r-e)/2+e*m/100;g.style.top=`${o}px`,g.style.left=`${l}px`,g.style.height=`${t}px`,g.style.transform="translateX(-50%)"}else a.style.maskImage="none"}function f(e){p&&(u=!0)}function y(e){if(p&&u){const t=i.getBoundingClientRect(),{displayWidth:s}=n(),r=((i.offsetWidth||640)-s)/2,a=e.clientX-t.left;m=(a-r)/s*100,m=Math.max(0,Math.min(100,m)),w()}}function v(e){u=!1}return g.style.position="absolute",g.style.width="3px",g.style.backgroundColor="rgba(255, 255, 255, 0.75)",g.style.cursor="ew-resize",g.style.zIndex="12",g.style.pointerEvents="auto",g.style.display=p?"block":"none",g.style.boxShadow="0 0 2px rgba(0, 0, 0, 0.1)",p&&(i.appendChild(g),g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()),t.on("destroy",()=>{o&&"function"===typeof o.dispose&&o.dispose(),g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v)}),t.on("resize",()=>{const{displayWidth:e,displayHeight:t}=n();a.style.width=`${e}px`,a.style.height=`${t}px`,w()}),t.on("play",()=>{d()}),{name:"artplayerPluginWebsr",upscaler:o,update:function(s={}){if(s&&"object"===typeof s){if(Object.prototype.hasOwnProperty.call(s,"compare")){const t=!!s.compare;e.compare=t,p=t,p?(g.parentNode||i.appendChild(g),g.style.display="block",g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()):(a.style.maskImage="none",g.style.display="none",g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v))}if(s.networkSize){const r=s.networkSize;e.networkSize=r,o&&"function"===typeof o.stopRealtimeUpscale&&o.stopRealtimeUpscale(),l=!1,t.paused||d()}}}}}}});
diff --git a/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.legacy.js b/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.legacy.js
deleted file mode 100644
index 6fd1521ab..000000000
--- a/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.legacy.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * artplayer-plugin-websr.js v1.0.1
- * Github: https://github.com/zhw2590582/ArtPlayer
- * (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?(t.artplayerPluginWebsr=t(),define(function(){return t.artplayerPluginWebsr})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginWebsr=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,i=Math.pow,n=(t,r,s)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[r]=s,o=(e,i)=>{for(var o in i||(i={}))r.call(i,o)&&n(e,o,i[o]);if(t)for(var o of t(i))s.call(i,o)&&n(e,o,i[o]);return e},a=(e,t,r)=>n(e,"symbol"!==typeof t?t+"":t,r),l=(e,t,r)=>new Promise((s,i)=>{var n=e=>{try{a(r.next(e))}catch(t){i(t)}},o=e=>{try{a(r.throw(e))}catch(t){i(t)}},a=e=>e.done?s(e.value):Promise.resolve(e.value).then(n,o);a((r=r.apply(e,t)).next())});const d=class e{static isSupported(){try{const e="undefined"!==typeof Worker,t="undefined"!==typeof OffscreenCanvas||"undefined"!==typeof document&&!!document.createElement("canvas").transferControlToOffscreen,r="undefined"!==typeof Blob;return e&&t&&r}catch(e){return!1}}static isVideoSupported(){try{return"undefined"!==typeof VideoEncoder&&"undefined"!==typeof VideoDecoder}catch(e){return!1}}constructor(t={}){const r=t.weightsBaseUrl||"/weights";this.networks=t.networks||{small:{name:"anime4k/cnn-2x-l",weightsUrl:`${r}/cnn-8.json`},medium:{name:"anime4k/cnn-2x-16",weightsUrl:`${r}/cnn-16.json`},large:{name:"anime4k/cnn-2x-28",weightsUrl:`${r}/cnn-28.json`}},this.weightsBaseUrl=r,this.networkSize=t.networkSize||"medium",this.workerUrl=t.workerUrl||"/worker/main.js",this.timeouts=o(o({},e.DEFAULT_TIMEOUTS),t.timeouts||{}),this.delays=o(o({},e.DEFAULT_DELAYS),t.delays||{}),this.imageScale="number"===typeof t.imageScale&&t.imageScale>0?t.imageScale:2,this.videoScale="number"===typeof t.videoScale&&t.videoScale>0?t.videoScale:2,this.weightsCache=new Map,this.workerInstance=null,this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.realtimeLoopId=null,this.realtimeState=null,this.init()}init({prewarm:t=!0}={}){if(!e.isSupported())throw new Error("Upscaler is not supported in this environment");return t&&this.getWorker().postMessage({cmd:"isSupported"}),this}getWorker(){return this.workerInstance||(this.workerInstance=new Worker(this.workerUrl),this.workerInstance.onmessage=e=>this.handleWorkerMessage(e)),this.workerInstance}static extractProgressValue(e){var t,r,s;const i=null!=(s=null!=(r=null!=(t=e.data)?t:e.progress)?r:e.value)?s:e.percentage;return"number"===typeof i?Math.min(100,Math.max(0,Math.round(i))):null}handleBlobResponse(e){const t="video"===this.processingType?"videoBlob":"imageBlob",r=this.messageHandlers[t];e.data instanceof Blob&&r&&r({[t]:e.data})}requestBlob(){const e="video"===this.processingType?"getVideoBlob":"getImageBlob";this.getWorker().postMessage({cmd:e})}handleWorkerMessage(t){const{data:r}=t;if(!r.cmd)return;const{cmd:s}=r;if("progress"===s){const t=e.extractProgressValue(r);null!==t&&this.progressCallback&&this.progressCallback(t),this.messageHandlers.progress&&this.messageHandlers.progress(r)}else"finished"===s?r.data instanceof Blob?this.handleBlobResponse(r):this.requestBlob():this.messageHandlers[s]&&this.messageHandlers[s](r)}loadWeights(e){return l(this,null,function*(){if(this.validateNetworkSize(e),this.weightsCache.has(e))return this.weightsCache.get(e);const t=this.networks[e],r=yield fetch(t.weightsUrl);if(!r.ok)throw new Error(`Failed to fetch weights: ${r.statusText}`);const s=yield r.json();return this.weightsCache.set(e,s),s})}static loadImageMetadata(e,t){return new Promise((r,s)=>{const i=new Blob([e],{type:t}),n=URL.createObjectURL(i),o=new Image;o.onload=()=>{URL.revokeObjectURL(n),r({width:o.width,height:o.height})},o.onerror=()=>{URL.revokeObjectURL(n),s(new Error("Failed to load image"))},o.src=n})}static loadVideoMetadata(e,t){return new Promise((r,s)=>{const i=URL.createObjectURL(e),n=document.createElement("video");n.src=i;const o=setTimeout(()=>s(new Error("Timeout")),t);n.onloadedmetadata=()=>{clearTimeout(o),URL.revokeObjectURL(i),r({width:n.videoWidth,height:n.videoHeight,duration:n.duration})},n.onerror=()=>{clearTimeout(o),URL.revokeObjectURL(i),s(new Error("Failed to load video"))}})}createOffscreenCanvas(e,t,r=2){const s="number"===typeof r&&r>0?r:1,i=document.createElement("canvas");if(i.width=e*s,i.height=t*s,"function"!==typeof i.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");return i.transferControlToOffscreen()}createBlobPromise(e,t){return new Promise((r,s)=>{const i=setTimeout(()=>{this.messageHandlers[e]&&(delete this.messageHandlers[e],s(new Error(`${e} processing timeout`)))},t);this.messageHandlers[e]=t=>{delete this.messageHandlers[e],clearTimeout(i);const n=t[e]||t.data;n instanceof Blob?r(n):s(new Error(`Invalid ${e} format`))}})}validateNetworkSize(e){if(!this.networks[e])throw new Error(`Invalid networkSize: use one of ${Object.keys(this.networks).join(", ")}`)}upscaleImage(t,r,s){return l(this,null,function*(){if(!t)throw new Error("Image file is required");const i=r||this.networkSize;this.validateNetworkSize(i),this.processingType="image";const n=yield t.arrayBuffer(),o=t.type||"image/jpeg",{width:a,height:l}=yield e.loadImageMetadata(n,o),d=yield this.loadWeights(i),c=this.networks[i],h="number"===typeof s&&s>0?s:this.imageScale,[u,m]=[this.createOffscreenCanvas(a,l,h),this.createOffscreenCanvas(a,l,1)],p=this.createBlobPromise("imageBlob",this.timeouts.IMAGE);return this.getWorker().postMessage({cmd:"init",data:{imageArrayBuffer:n,imageMimeType:o,upscaled:u,original:m,resolution:{width:a,height:l},network_name:c.name,weights:d}},[u,m]),setTimeout(()=>{this.getWorker().postMessage({cmd:"network",data:{name:c.name,imageArrayBuffer:n,imageMimeType:o,weights:d}}),setTimeout(()=>{this.getWorker().postMessage({cmd:"getImageBlob",data:{imageArrayBuffer:n,imageMimeType:o}})},this.delays.NETWORK)},this.delays.INIT),p})}upscaleVideo(t,r,s){return l(this,null,function*(){if(!t)throw new Error("Video file is required");const i=r||this.networkSize;if(this.validateNetworkSize(i),!e.isVideoSupported())throw new Error("WebCodecs API not supported. Requires Chrome 94+");this.processingType="video";const{width:n,height:o,duration:a}=yield e.loadVideoMetadata(t,this.timeouts.METADATA),l=yield this.loadWeights(i),d=this.networks[i],c="number"===typeof s&&s>0?s:this.videoScale,[h,u]=[this.createOffscreenCanvas(n,o,c),this.createOffscreenCanvas(n,o,1)],m=this.createBlobPromise("videoBlob",this.timeouts.VIDEO);return this.getWorker().postMessage({cmd:"process",file:t,fileSize:t.size,duration:a,adjustedResolution:{adjustedInputWidth:n,adjustedInputHeight:o,adjustedOutputWidth:n*c,adjustedOutputHeight:o*c},upscaled:h,original:u,weights:l,network_name:d.name,skipDemuxProgress:!0},[h,u]),m})}startRealtimeUpscale(e,t,r,s){return l(this,null,function*(){if(!e||!t)throw new Error("videoElement and canvasElement are required");const i=r||this.networkSize;this.validateNetworkSize(i);const n=this.networks[i],o=yield this.loadWeights(i),a="number"===typeof s&&s>0?s:this.videoScale;yield new Promise((t,r)=>{if(e.readyState>=1)return void t();let s,i;const n=()=>{e.removeEventListener("loadedmetadata",s),e.removeEventListener("error",i)};s=()=>{n(),t()},i=()=>{n(),r(new Error("Failed to load video metadata"))},e.addEventListener("loadedmetadata",s),e.addEventListener("error",i)});const d=e.videoWidth,c=e.videoHeight;if(!d||!c)throw new Error("Invalid video dimensions");const h=!t._upscalerTransferred;let u=null;if(h){if(t.width=d*a,t.height=c*a,"function"!==typeof t.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");u=t.transferControlToOffscreen(),t._upscalerTransferred=!0}const m=document.createElement("canvas");m.width=d,m.height=c;const p=m.getContext("2d");this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null);const g={cmd:"realtimeInit",data:{upscaled:h?u:null,resolution:{width:d,height:c,scale:a,outputWidth:d*a,outputHeight:c*a},network_name:n.name,weights:o}};h&&u?this.getWorker().postMessage(g,[u]):this.getWorker().postMessage(g),this.realtimeState={running:!0,video:e,canvas:t,scale:a,busy:!1,frameIndex:0,captureCanvas:m,captureCtx:p};const f=()=>l(this,null,function*(){if(!this.realtimeState||!this.realtimeState.running)return;const e=this.realtimeState,t=e.video,r=t.HAVE_CURRENT_DATA||2;if(!t.paused&&!t.ended&&!e.busy&&t.readyState>=r&&t.videoWidth>0&&t.videoHeight>0){e.busy=!0;try{const r=e.captureCanvas,s=e.captureCtx;if(!r||!s)throw new Error("Missing capture canvas");s.drawImage(t,0,0,r.width,r.height);const i=yield createImageBitmap(r);e.frameIndex+=1,this.getWorker().postMessage({cmd:"realtimeFrame",frame:i},[i])}catch(s){console.warn("realtimeFrame error",s)}finally{this.realtimeState&&(this.realtimeState.busy=!1)}}this.realtimeLoopId=requestAnimationFrame(f)});this.realtimeLoopId=requestAnimationFrame(f)})}stopRealtimeUpscale(){this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null),this.realtimeState&&(this.realtimeState.running=!1,this.realtimeState=null)}downloadUpscaled(e,t,r=null){return l(this,null,function*(){const s=e.type.startsWith("video"),i=t||this.networkSize,n=s?yield this.upscaleVideo(e,i):yield this.upscaleImage(e,i),o=URL.createObjectURL(n),a=document.createElement("a");return a.href=o,a.download=r||`upscaled_${Date.now()}.${s?"mp4":"png"}`,a.click(),URL.revokeObjectURL(o),n})}getSupportedNetworks(){return Object.keys(this.networks)}static getGPUCapability(){return l(this,null,function*(){try{if(navigator.gpu){const e=yield navigator.gpu.requestAdapter();if(e){const t=e.limits.maxStorageBufferBindingSize/512,r=67108864;return Math.min(t,r)}}const e=document.createElement("canvas").getContext("webgl2");return e?i(e.getParameter(e.MAX_TEXTURE_SIZE),2):8388608}catch(e){return 8388608}})}static getBackendType(){try{return navigator.gpu?"webgpu":document.createElement("canvas").getContext("webgl2")?"webgl":"unknown"}catch(e){return"unknown"}}getAppState(){return{backend:e.getBackendType(),isProcessing:!1,progress:0,width:0,height:0}}onProgress(e){"function"===typeof e&&(this.progressCallback=e)}dispose(){this.stopRealtimeUpscale(),this.workerInstance&&(this.workerInstance.terminate(),this.workerInstance=null),this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.weightsCache.clear()}};a(d,"DEFAULT_TIMEOUTS",{IMAGE:3e5,VIDEO:36e5,METADATA:1e4}),a(d,"DEFAULT_DELAYS",{INIT:500,NETWORK:300});let c=d;return function(e={networkSize:"medium",compare:!1,weightsBaseUrl:"/weights",workerUrl:"/worker/main.js",videoScale:2}){return t=>{const{$video:r,$player:s}=t.template,i=document.createElement("canvas");function n(){const e=t.video,r=s.offsetWidth||640,i=s.offsetHeight||360,n=(e.videoWidth||640)/(e.videoHeight||360);let o=r,a=i;return r/i>n?o=i*n:a=r/n,{displayWidth:o,displayHeight:a}}if(s.appendChild(i),i.style.position="absolute",i.style.zIndex="11",i.style.pointerEvents="none",i.style.top="50%",i.style.left="50%",i.style.transform="translate(-50%, -50%)",i.style.opacity="0",i.style.transition="opacity 0.2s ease",!c||!c.isSupported||!c.isSupported())return void console.warn("Upscaler is not supported in this environment");const o=new c({networkSize:e.networkSize||"medium",weightsBaseUrl:e.weightsBaseUrl||"/weights",workerUrl:e.workerUrl||"/worker/main.js",videoScale:e.videoScale||2});let a=!1;function d(){return l(this,null,function*(){if(!a){a=!0;try{yield new Promise((e,t)=>{if(r.readyState>=1)return void e();let s,i;const n=()=>{r.removeEventListener("loadedmetadata",s),r.removeEventListener("error",i)};s=()=>{n(),e()},i=()=>{n(),t(new Error("Failed to load video metadata"))},r.addEventListener("loadedmetadata",s),r.addEventListener("error",i)}),yield o.startRealtimeUpscale(r,i,e.networkSize||o.networkSize),requestAnimationFrame(()=>{i.style.opacity="1"})}catch(t){console.error("artplayer-plugin-upscaler: failed to start realtime",t)}}})}const{displayWidth:h,displayHeight:u}=n();i.style.width=`${h}px`,i.style.height=`${u}px`;let m=50,p=!1,g=!!e.compare;const f=document.createElement("div");function w(){if(g){const{displayWidth:e,displayHeight:t}=n(),r=`linear-gradient(to right, transparent 0%, transparent ${m}%, black ${m}%, black 100%)`;i.style.maskImage=r;const o=s.offsetWidth||640,a=((s.offsetHeight||360)-t)/2,l=(o-e)/2+e*m/100;f.style.top=`${a}px`,f.style.left=`${l}px`,f.style.height=`${t}px`,f.style.transform="translateX(-50%)"}else i.style.maskImage="none"}function y(e){g&&(p=!0)}function v(e){if(g&&p){const t=s.getBoundingClientRect(),{displayWidth:r}=n(),i=((s.offsetWidth||640)-r)/2,o=e.clientX-t.left;m=(o-i)/r*100,m=Math.max(0,Math.min(100,m)),w()}}function k(e){p=!1}return f.style.position="absolute",f.style.width="3px",f.style.backgroundColor="rgba(255, 255, 255, 0.75)",f.style.cursor="ew-resize",f.style.zIndex="12",f.style.pointerEvents="auto",f.style.display=g?"block":"none",f.style.boxShadow="0 0 2px rgba(0, 0, 0, 0.1)",g&&(s.appendChild(f),f.addEventListener("mousedown",y),document.addEventListener("mousemove",v),document.addEventListener("mouseup",k),w()),t.on("destroy",()=>{o&&"function"===typeof o.dispose&&o.dispose(),f.removeEventListener("mousedown",y),document.removeEventListener("mousemove",v),document.removeEventListener("mouseup",k)}),t.on("resize",()=>{const{displayWidth:e,displayHeight:t}=n();i.style.width=`${e}px`,i.style.height=`${t}px`,w()}),t.on("play",()=>{d()}),{name:"artplayerPluginWebsr",upscaler:o,update:function(r={}){if(r&&"object"===typeof r){if(Object.prototype.hasOwnProperty.call(r,"compare")){const t=!!r.compare;e.compare=t,g=t,g?(f.parentNode||s.appendChild(f),f.style.display="block",f.addEventListener("mousedown",y),document.addEventListener("mousemove",v),document.addEventListener("mouseup",k),w()):(i.style.maskImage="none",f.style.display="none",f.removeEventListener("mousedown",y),document.removeEventListener("mousemove",v),document.removeEventListener("mouseup",k))}if(r.networkSize){const s=r.networkSize;e.networkSize=s,o&&"function"===typeof o.stopRealtimeUpscale&&o.stopRealtimeUpscale(),a=!1,t.paused||d()}}}}}}});
diff --git a/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.mjs b/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.mjs
deleted file mode 100644
index 3ca71e964..000000000
--- a/packages/artplayer-plugin-websr/dist/artplayer-plugin-websr.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * artplayer-plugin-websr.js v1.0.1
- * Github: https://github.com/zhw2590582/ArtPlayer
- * (c) 2017-2026 Harvey Zhao
- * Released under the MIT License.
- */
-var e=Object.defineProperty,t=(t,s,r)=>((t,s,r)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[s]=r)(t,"symbol"!==typeof s?s+"":s,r);const s=class e{static isSupported(){try{const e="undefined"!==typeof Worker,t="undefined"!==typeof OffscreenCanvas||"undefined"!==typeof document&&!!document.createElement("canvas").transferControlToOffscreen,s="undefined"!==typeof Blob;return e&&t&&s}catch{return!1}}static isVideoSupported(){try{return"undefined"!==typeof VideoEncoder&&"undefined"!==typeof VideoDecoder}catch{return!1}}constructor(t={}){const s=t.weightsBaseUrl||"/weights";this.networks=t.networks||{small:{name:"anime4k/cnn-2x-l",weightsUrl:`${s}/cnn-8.json`},medium:{name:"anime4k/cnn-2x-16",weightsUrl:`${s}/cnn-16.json`},large:{name:"anime4k/cnn-2x-28",weightsUrl:`${s}/cnn-28.json`}},this.weightsBaseUrl=s,this.networkSize=t.networkSize||"medium",this.workerUrl=t.workerUrl||"/worker/main.js",this.timeouts={...e.DEFAULT_TIMEOUTS,...t.timeouts||{}},this.delays={...e.DEFAULT_DELAYS,...t.delays||{}},this.imageScale="number"===typeof t.imageScale&&t.imageScale>0?t.imageScale:2,this.videoScale="number"===typeof t.videoScale&&t.videoScale>0?t.videoScale:2,this.weightsCache=/* @__PURE__ */new Map,this.workerInstance=null,this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.realtimeLoopId=null,this.realtimeState=null,this.init()}init({prewarm:t=!0}={}){if(!e.isSupported())throw new Error("Upscaler is not supported in this environment");return t&&this.getWorker().postMessage({cmd:"isSupported"}),this}getWorker(){return this.workerInstance||(this.workerInstance=new Worker(this.workerUrl),this.workerInstance.onmessage=e=>this.handleWorkerMessage(e)),this.workerInstance}static extractProgressValue(e){const t=e.data??e.progress??e.value??e.percentage;return"number"===typeof t?Math.min(100,Math.max(0,Math.round(t))):null}handleBlobResponse(e){const t="video"===this.processingType?"videoBlob":"imageBlob",s=this.messageHandlers[t];e.data instanceof Blob&&s&&s({[t]:e.data})}requestBlob(){const e="video"===this.processingType?"getVideoBlob":"getImageBlob";this.getWorker().postMessage({cmd:e})}handleWorkerMessage(t){const{data:s}=t;if(!s.cmd)return;const{cmd:r}=s;if("progress"===r){const t=e.extractProgressValue(s);null!==t&&this.progressCallback&&this.progressCallback(t),this.messageHandlers.progress&&this.messageHandlers.progress(s)}else"finished"===r?s.data instanceof Blob?this.handleBlobResponse(s):this.requestBlob():this.messageHandlers[r]&&this.messageHandlers[r](s)}async loadWeights(e){if(this.validateNetworkSize(e),this.weightsCache.has(e))return this.weightsCache.get(e);const t=this.networks[e],s=await fetch(t.weightsUrl);if(!s.ok)throw new Error(`Failed to fetch weights: ${s.statusText}`);const r=await s.json();return this.weightsCache.set(e,r),r}static loadImageMetadata(e,t){return new Promise((s,r)=>{const i=new Blob([e],{type:t}),a=URL.createObjectURL(i),o=new Image;o.onload=()=>{URL.revokeObjectURL(a),s({width:o.width,height:o.height})},o.onerror=()=>{URL.revokeObjectURL(a),r(new Error("Failed to load image"))},o.src=a})}static loadVideoMetadata(e,t){return new Promise((s,r)=>{const i=URL.createObjectURL(e),a=document.createElement("video");a.src=i;const o=setTimeout(()=>r(new Error("Timeout")),t);a.onloadedmetadata=()=>{clearTimeout(o),URL.revokeObjectURL(i),s({width:a.videoWidth,height:a.videoHeight,duration:a.duration})},a.onerror=()=>{clearTimeout(o),URL.revokeObjectURL(i),r(new Error("Failed to load video"))}})}createOffscreenCanvas(e,t,s=2){const r="number"===typeof s&&s>0?s:1,i=document.createElement("canvas");if(i.width=e*r,i.height=t*r,"function"!==typeof i.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");return i.transferControlToOffscreen()}createBlobPromise(e,t){return new Promise((s,r)=>{const i=setTimeout(()=>{this.messageHandlers[e]&&(delete this.messageHandlers[e],r(new Error(`${e} processing timeout`)))},t);this.messageHandlers[e]=t=>{delete this.messageHandlers[e],clearTimeout(i);const a=t[e]||t.data;a instanceof Blob?s(a):r(new Error(`Invalid ${e} format`))}})}validateNetworkSize(e){if(!this.networks[e])throw new Error(`Invalid networkSize: use one of ${Object.keys(this.networks).join(", ")}`)}async upscaleImage(t,s,r){if(!t)throw new Error("Image file is required");const i=s||this.networkSize;this.validateNetworkSize(i),this.processingType="image";const a=await t.arrayBuffer(),o=t.type||"image/jpeg",{width:n,height:l}=await e.loadImageMetadata(a,o),d=await this.loadWeights(i),c=this.networks[i],h="number"===typeof r&&r>0?r:this.imageScale,[m,u]=[this.createOffscreenCanvas(n,l,h),this.createOffscreenCanvas(n,l,1)],p=this.createBlobPromise("imageBlob",this.timeouts.IMAGE);return this.getWorker().postMessage({cmd:"init",data:{imageArrayBuffer:a,imageMimeType:o,upscaled:m,original:u,resolution:{width:n,height:l},network_name:c.name,weights:d}},[m,u]),setTimeout(()=>{this.getWorker().postMessage({cmd:"network",data:{name:c.name,imageArrayBuffer:a,imageMimeType:o,weights:d}}),setTimeout(()=>{this.getWorker().postMessage({cmd:"getImageBlob",data:{imageArrayBuffer:a,imageMimeType:o}})},this.delays.NETWORK)},this.delays.INIT),p}async upscaleVideo(t,s,r){if(!t)throw new Error("Video file is required");const i=s||this.networkSize;if(this.validateNetworkSize(i),!e.isVideoSupported())throw new Error("WebCodecs API not supported. Requires Chrome 94+");this.processingType="video";const{width:a,height:o,duration:n}=await e.loadVideoMetadata(t,this.timeouts.METADATA),l=await this.loadWeights(i),d=this.networks[i],c="number"===typeof r&&r>0?r:this.videoScale,[h,m]=[this.createOffscreenCanvas(a,o,c),this.createOffscreenCanvas(a,o,1)],u=this.createBlobPromise("videoBlob",this.timeouts.VIDEO);return this.getWorker().postMessage({cmd:"process",file:t,fileSize:t.size,duration:n,adjustedResolution:{adjustedInputWidth:a,adjustedInputHeight:o,adjustedOutputWidth:a*c,adjustedOutputHeight:o*c},upscaled:h,original:m,weights:l,network_name:d.name,skipDemuxProgress:!0},[h,m]),u}async startRealtimeUpscale(e,t,s,r){if(!e||!t)throw new Error("videoElement and canvasElement are required");const i=s||this.networkSize;this.validateNetworkSize(i);const a=this.networks[i],o=await this.loadWeights(i),n="number"===typeof r&&r>0?r:this.videoScale;await new Promise((t,s)=>{if(e.readyState>=1)return void t();let r,i;const a=()=>{e.removeEventListener("loadedmetadata",r),e.removeEventListener("error",i)};r=()=>{a(),t()},i=()=>{a(),s(new Error("Failed to load video metadata"))},e.addEventListener("loadedmetadata",r),e.addEventListener("error",i)});const l=e.videoWidth,d=e.videoHeight;if(!l||!d)throw new Error("Invalid video dimensions");const c=!t._upscalerTransferred;let h=null;if(c){if(t.width=l*n,t.height=d*n,"function"!==typeof t.transferControlToOffscreen)throw new TypeError("OffscreenCanvas is not supported in this environment");h=t.transferControlToOffscreen(),t._upscalerTransferred=!0}const m=document.createElement("canvas");m.width=l,m.height=d;const u=m.getContext("2d");this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null);const p={cmd:"realtimeInit",data:{upscaled:c?h:null,resolution:{width:l,height:d,scale:n,outputWidth:l*n,outputHeight:d*n},network_name:a.name,weights:o}};c&&h?this.getWorker().postMessage(p,[h]):this.getWorker().postMessage(p),this.realtimeState={running:!0,video:e,canvas:t,scale:n,busy:!1,frameIndex:0,captureCanvas:m,captureCtx:u};const g=async()=>{if(!this.realtimeState||!this.realtimeState.running)return;const e=this.realtimeState,t=e.video,s=t.HAVE_CURRENT_DATA||2;if(!t.paused&&!t.ended&&!e.busy&&t.readyState>=s&&t.videoWidth>0&&t.videoHeight>0){e.busy=!0;try{const s=e.captureCanvas,r=e.captureCtx;if(!s||!r)throw new Error("Missing capture canvas");r.drawImage(t,0,0,s.width,s.height);const i=await createImageBitmap(s);e.frameIndex+=1,this.getWorker().postMessage({cmd:"realtimeFrame",frame:i},[i])}catch(r){console.warn("realtimeFrame error",r)}finally{this.realtimeState&&(this.realtimeState.busy=!1)}}this.realtimeLoopId=requestAnimationFrame(g)};this.realtimeLoopId=requestAnimationFrame(g)}stopRealtimeUpscale(){this.realtimeLoopId&&(cancelAnimationFrame(this.realtimeLoopId),this.realtimeLoopId=null),this.realtimeState&&(this.realtimeState.running=!1,this.realtimeState=null)}async downloadUpscaled(e,t,s=null){const r=e.type.startsWith("video"),i=t||this.networkSize,a=r?await this.upscaleVideo(e,i):await this.upscaleImage(e,i),o=URL.createObjectURL(a),n=document.createElement("a");return n.href=o,n.download=s||`upscaled_${Date.now()}.${r?"mp4":"png"}`,n.click(),URL.revokeObjectURL(o),a}getSupportedNetworks(){return Object.keys(this.networks)}static async getGPUCapability(){try{if(navigator.gpu){const e=await navigator.gpu.requestAdapter();if(e){const t=e.limits.maxStorageBufferBindingSize/512,s=67108864;return Math.min(t,s)}}const e=document.createElement("canvas").getContext("webgl2");return e?e.getParameter(e.MAX_TEXTURE_SIZE)**2:8388608}catch{return 8388608}}static getBackendType(){try{return navigator.gpu?"webgpu":document.createElement("canvas").getContext("webgl2")?"webgl":"unknown"}catch{return"unknown"}}getAppState(){return{backend:e.getBackendType(),isProcessing:!1,progress:0,width:0,height:0}}onProgress(e){"function"===typeof e&&(this.progressCallback=e)}dispose(){this.stopRealtimeUpscale(),this.workerInstance&&(this.workerInstance.terminate(),this.workerInstance=null),this.messageHandlers={},this.progressCallback=null,this.processingType=null,this.weightsCache.clear()}};t(s,"DEFAULT_TIMEOUTS",{IMAGE:3e5,VIDEO:36e5,METADATA:1e4}),t(s,"DEFAULT_DELAYS",{INIT:500,NETWORK:300});let r=s;function i(e={networkSize:"medium",compare:!1,weightsBaseUrl:"/weights",workerUrl:"/worker/main.js",videoScale:2}){return t=>{const{$video:s,$player:i}=t.template,a=document.createElement("canvas");function o(){const e=t.video,s=i.offsetWidth||640,r=i.offsetHeight||360,a=(e.videoWidth||640)/(e.videoHeight||360);let o=s,n=r;return s/r>a?o=r*a:n=s/a,{displayWidth:o,displayHeight:n}}if(i.appendChild(a),a.style.position="absolute",a.style.zIndex="11",a.style.pointerEvents="none",a.style.top="50%",a.style.left="50%",a.style.transform="translate(-50%, -50%)",a.style.opacity="0",a.style.transition="opacity 0.2s ease",!r||!r.isSupported||!r.isSupported())return void console.warn("Upscaler is not supported in this environment");const n=new r({networkSize:e.networkSize||"medium",weightsBaseUrl:e.weightsBaseUrl||"/weights",workerUrl:e.workerUrl||"/worker/main.js",videoScale:e.videoScale||2});let l=!1;async function d(){if(!l){l=!0;try{await new Promise((e,t)=>{if(s.readyState>=1)return void e();let r,i;const a=()=>{s.removeEventListener("loadedmetadata",r),s.removeEventListener("error",i)};r=()=>{a(),e()},i=()=>{a(),t(new Error("Failed to load video metadata"))},s.addEventListener("loadedmetadata",r),s.addEventListener("error",i)}),await n.startRealtimeUpscale(s,a,e.networkSize||n.networkSize),requestAnimationFrame(()=>{a.style.opacity="1"})}catch(t){console.error("artplayer-plugin-upscaler: failed to start realtime",t)}}}const{displayWidth:c,displayHeight:h}=o();a.style.width=`${c}px`,a.style.height=`${h}px`;let m=50,u=!1,p=!!e.compare;const g=document.createElement("div");function w(){if(p){const{displayWidth:e,displayHeight:t}=o(),s=`linear-gradient(to right, transparent 0%, transparent ${m}%, black ${m}%, black 100%)`;a.style.maskImage=s;const r=i.offsetWidth||640,n=((i.offsetHeight||360)-t)/2,l=(r-e)/2+e*m/100;g.style.top=`${n}px`,g.style.left=`${l}px`,g.style.height=`${t}px`,g.style.transform="translateX(-50%)"}else a.style.maskImage="none"}function f(e){p&&(u=!0)}function y(e){if(p&&u){const t=i.getBoundingClientRect(),{displayWidth:s}=o(),r=((i.offsetWidth||640)-s)/2,a=e.clientX-t.left;m=(a-r)/s*100,m=Math.max(0,Math.min(100,m)),w()}}function v(e){u=!1}return g.style.position="absolute",g.style.width="3px",g.style.backgroundColor="rgba(255, 255, 255, 0.75)",g.style.cursor="ew-resize",g.style.zIndex="12",g.style.pointerEvents="auto",g.style.display=p?"block":"none",g.style.boxShadow="0 0 2px rgba(0, 0, 0, 0.1)",p&&(i.appendChild(g),g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()),t.on("destroy",()=>{n&&"function"===typeof n.dispose&&n.dispose(),g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v)}),t.on("resize",()=>{const{displayWidth:e,displayHeight:t}=o();a.style.width=`${e}px`,a.style.height=`${t}px`,w()}),t.on("play",()=>{d()}),{name:"artplayerPluginWebsr",upscaler:n,update:function(s={}){if(s&&"object"===typeof s){if(Object.prototype.hasOwnProperty.call(s,"compare")){const t=!!s.compare;e.compare=t,p=t,p?(g.parentNode||i.appendChild(g),g.style.display="block",g.addEventListener("mousedown",f),document.addEventListener("mousemove",y),document.addEventListener("mouseup",v),w()):(a.style.maskImage="none",g.style.display="none",g.removeEventListener("mousedown",f),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",v))}if(s.networkSize){const r=s.networkSize;e.networkSize=r,n&&"function"===typeof n.stopRealtimeUpscale&&n.stopRealtimeUpscale(),l=!1,t.paused||d()}}}}}}export{i as default};
diff --git a/packages/artplayer-plugin-websr/package.json b/packages/artplayer-plugin-websr/package.json
deleted file mode 100644
index 15eaca46b..000000000
--- a/packages/artplayer-plugin-websr/package.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "name": "artplayer-plugin-websr",
- "version": "1.0.1",
- "description": "AI upscaling plugin for ArtPlayer using WebSR - real-time video super-resolution with WebGPU",
- "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-websr.d.ts",
- "import": "./dist/artplayer-plugin-websr.mjs",
- "require": "./dist/artplayer-plugin-websr.js"
- },
- "./legacy": {
- "types": "./types/artplayer-plugin-websr.d.ts",
- "import": "./dist/artplayer-plugin-websr.legacy.js",
- "require": "./dist/artplayer-plugin-websr.legacy.js"
- }
- },
- "main": "dist/artplayer-plugin-websr.js",
- "module": "./dist/artplayer-plugin-websr.mjs",
- "types": "types/artplayer-plugin-websr.d.ts",
- "legacy": "dist/artplayer-plugin-websr.legacy.js",
- "browserslist": "last 1 Chrome version",
- "dependencies": {
- "@websr/websr": "^0.0.14"
- }
-}
diff --git a/packages/artplayer-plugin-websr/src/Upscaler.js b/packages/artplayer-plugin-websr/src/Upscaler.js
deleted file mode 100644
index fa0831bf7..000000000
--- a/packages/artplayer-plugin-websr/src/Upscaler.js
+++ /dev/null
@@ -1,632 +0,0 @@
-export default class Upscaler {
- static DEFAULT_TIMEOUTS = { IMAGE: 300000, VIDEO: 3600000, METADATA: 10000 }
- static DEFAULT_DELAYS = { INIT: 500, NETWORK: 300 }
-
- static isSupported() {
- try {
- const hasWorker = typeof Worker !== 'undefined'
- const hasOffscreen
- = typeof OffscreenCanvas !== 'undefined'
- || (typeof document !== 'undefined'
- && !!document.createElement('canvas').transferControlToOffscreen)
- const hasBlob = typeof Blob !== 'undefined'
- return hasWorker && hasOffscreen && hasBlob
- }
- catch {
- return false
- }
- }
-
- static isVideoSupported() {
- try {
- return (
- typeof VideoEncoder !== 'undefined'
- && typeof VideoDecoder !== 'undefined'
- )
- }
- catch {
- return false
- }
- }
-
- constructor(options = {}) {
- const weightsBaseUrl = options.weightsBaseUrl || '/weights'
-
- this.networks = options.networks || {
- small: {
- name: 'anime4k/cnn-2x-l',
- weightsUrl: `${weightsBaseUrl}/cnn-8.json`,
- },
- medium: {
- name: 'anime4k/cnn-2x-16',
- weightsUrl: `${weightsBaseUrl}/cnn-16.json`,
- },
- large: {
- name: 'anime4k/cnn-2x-28',
- weightsUrl: `${weightsBaseUrl}/cnn-28.json`,
- },
- }
-
- this.weightsBaseUrl = weightsBaseUrl
- this.networkSize = options.networkSize || 'medium'
- this.workerUrl = options.workerUrl || '/worker/main.js'
-
- this.timeouts = {
- ...Upscaler.DEFAULT_TIMEOUTS,
- ...(options.timeouts || {}),
- }
- this.delays = { ...Upscaler.DEFAULT_DELAYS, ...(options.delays || {}) }
-
- this.imageScale
- = typeof options.imageScale === 'number' && options.imageScale > 0
- ? options.imageScale
- : 2
- this.videoScale
- = typeof options.videoScale === 'number' && options.videoScale > 0
- ? options.videoScale
- : 2
-
- this.weightsCache = new Map()
- this.workerInstance = null
- this.messageHandlers = {}
- this.progressCallback = null
- this.processingType = null
-
- this.realtimeLoopId = null
- this.realtimeState = null
-
- this.init()
- }
-
- init({ prewarm = true } = {}) {
- if (!Upscaler.isSupported()) {
- throw new Error('Upscaler is not supported in this environment')
- }
- if (prewarm) {
- this.getWorker().postMessage({ cmd: 'isSupported' })
- }
- return this
- }
-
- getWorker() {
- if (!this.workerInstance) {
- this.workerInstance = new Worker(this.workerUrl)
- this.workerInstance.onmessage = event =>
- this.handleWorkerMessage(event)
- }
- return this.workerInstance
- }
-
- static extractProgressValue(data) {
- const value = data.data ?? data.progress ?? data.value ?? data.percentage
- return typeof value === 'number'
- ? Math.min(100, Math.max(0, Math.round(value)))
- : null
- }
-
- handleBlobResponse(data) {
- const blobType
- = this.processingType === 'video' ? 'videoBlob' : 'imageBlob'
- const handler = this.messageHandlers[blobType]
- if (data.data instanceof Blob && handler) {
- handler({ [blobType]: data.data })
- }
- }
-
- requestBlob() {
- const cmd
- = this.processingType === 'video' ? 'getVideoBlob' : 'getImageBlob'
- this.getWorker().postMessage({ cmd })
- }
-
- handleWorkerMessage(event) {
- const { data } = event
- if (!data.cmd)
- return
-
- const { cmd } = data
-
- if (cmd === 'progress') {
- const progress = Upscaler.extractProgressValue(data)
- if (progress !== null && this.progressCallback) {
- this.progressCallback(progress)
- }
- if (this.messageHandlers.progress)
- this.messageHandlers.progress(data)
- }
- else if (cmd === 'finished') {
- if (data.data instanceof Blob) {
- this.handleBlobResponse(data)
- }
- else {
- this.requestBlob()
- }
- }
- else if (this.messageHandlers[cmd]) {
- this.messageHandlers[cmd](data)
- }
- }
-
- async loadWeights(networkSize) {
- this.validateNetworkSize(networkSize)
- if (this.weightsCache.has(networkSize)) {
- return this.weightsCache.get(networkSize)
- }
- const network = this.networks[networkSize]
- const response = await fetch(network.weightsUrl)
- if (!response.ok)
- throw new Error(`Failed to fetch weights: ${response.statusText}`)
- const weights = await response.json()
- this.weightsCache.set(networkSize, weights)
- return weights
- }
-
- static loadImageMetadata(arrayBuffer, mimeType) {
- return new Promise((resolve, reject) => {
- const blob = new Blob([arrayBuffer], { type: mimeType })
- const url = URL.createObjectURL(blob)
- const img = new Image()
-
- img.onload = () => {
- URL.revokeObjectURL(url)
- resolve({ width: img.width, height: img.height })
- }
-
- img.onerror = () => {
- URL.revokeObjectURL(url)
- reject(new Error('Failed to load image'))
- }
-
- img.src = url
- })
- }
-
- static loadVideoMetadata(file, timeoutMs) {
- return new Promise((resolve, reject) => {
- const url = URL.createObjectURL(file)
- const video = document.createElement('video')
- video.src = url
-
- const timeout = setTimeout(() => reject(new Error('Timeout')), timeoutMs)
-
- video.onloadedmetadata = () => {
- clearTimeout(timeout)
- URL.revokeObjectURL(url)
- resolve({
- width: video.videoWidth,
- height: video.videoHeight,
- duration: video.duration,
- })
- }
-
- video.onerror = () => {
- clearTimeout(timeout)
- URL.revokeObjectURL(url)
- reject(new Error('Failed to load video'))
- }
- })
- }
-
- createOffscreenCanvas(width, height, scale = 2) {
- const factor = typeof scale === 'number' && scale > 0 ? scale : 1
- const canvas = document.createElement('canvas')
- canvas.width = width * factor
- canvas.height = height * factor
- if (typeof canvas.transferControlToOffscreen !== 'function') {
- throw new TypeError('OffscreenCanvas is not supported in this environment')
- }
- return canvas.transferControlToOffscreen()
- }
-
- createBlobPromise(blobType, timeout) {
- return new Promise((resolve, reject) => {
- const timeoutId = setTimeout(() => {
- if (this.messageHandlers[blobType]) {
- delete this.messageHandlers[blobType]
- reject(new Error(`${blobType} processing timeout`))
- }
- }, timeout)
-
- this.messageHandlers[blobType] = (msg) => {
- delete this.messageHandlers[blobType]
- clearTimeout(timeoutId)
- const blob = msg[blobType] || msg.data
- if (blob instanceof Blob) {
- resolve(blob)
- }
- else {
- reject(new Error(`Invalid ${blobType} format`))
- }
- }
- })
- }
-
- validateNetworkSize(size) {
- if (!this.networks[size]) {
- throw new Error(
- `Invalid networkSize: use one of ${
- Object.keys(this.networks).join(', ')}`,
- )
- }
- }
-
- async upscaleImage(imageFile, networkSize, scaleOverride) {
- if (!imageFile)
- throw new Error('Image file is required')
- const size = networkSize || this.networkSize
- this.validateNetworkSize(size)
-
- this.processingType = 'image'
- const arrayBuffer = await imageFile.arrayBuffer()
- const mimeType = imageFile.type || 'image/jpeg'
- const { width, height } = await Upscaler.loadImageMetadata(
- arrayBuffer,
- mimeType,
- )
-
- const weights = await this.loadWeights(size)
- const network = this.networks[size]
- const scale
- = typeof scaleOverride === 'number' && scaleOverride > 0
- ? scaleOverride
- : this.imageScale
- const [canvasUp, canvasOrig] = [
- this.createOffscreenCanvas(width, height, scale),
- this.createOffscreenCanvas(width, height, 1),
- ]
-
- const blobPromise = this.createBlobPromise(
- 'imageBlob',
- this.timeouts.IMAGE,
- )
-
- this.getWorker().postMessage(
- {
- cmd: 'init',
- data: {
- imageArrayBuffer: arrayBuffer,
- imageMimeType: mimeType,
- upscaled: canvasUp,
- original: canvasOrig,
- resolution: { width, height },
- network_name: network.name,
- weights,
- },
- },
- [canvasUp, canvasOrig],
- )
-
- setTimeout(() => {
- this.getWorker().postMessage({
- cmd: 'network',
- data: {
- name: network.name,
- imageArrayBuffer: arrayBuffer,
- imageMimeType: mimeType,
- weights,
- },
- })
-
- setTimeout(() => {
- this.getWorker().postMessage({
- cmd: 'getImageBlob',
- data: { imageArrayBuffer: arrayBuffer, imageMimeType: mimeType },
- })
- }, this.delays.NETWORK)
- }, this.delays.INIT)
-
- return blobPromise
- }
-
- async upscaleVideo(videoFile, networkSize, scaleOverride) {
- if (!videoFile)
- throw new Error('Video file is required')
- const size = networkSize || this.networkSize
- this.validateNetworkSize(size)
-
- if (!Upscaler.isVideoSupported()) {
- throw new Error('WebCodecs API not supported. Requires Chrome 94+')
- }
-
- this.processingType = 'video'
- const { width, height, duration } = await Upscaler.loadVideoMetadata(
- videoFile,
- this.timeouts.METADATA,
- )
-
- const weights = await this.loadWeights(size)
- const network = this.networks[size]
- const scale
- = typeof scaleOverride === 'number' && scaleOverride > 0
- ? scaleOverride
- : this.videoScale
- const [canvasOut, canvasIn] = [
- this.createOffscreenCanvas(width, height, scale),
- this.createOffscreenCanvas(width, height, 1),
- ]
-
- const blobPromise = this.createBlobPromise(
- 'videoBlob',
- this.timeouts.VIDEO,
- )
-
- this.getWorker().postMessage(
- {
- cmd: 'process',
- file: videoFile,
- fileSize: videoFile.size,
- duration,
- adjustedResolution: {
- adjustedInputWidth: width,
- adjustedInputHeight: height,
- adjustedOutputWidth: width * scale,
- adjustedOutputHeight: height * scale,
- },
- upscaled: canvasOut,
- original: canvasIn,
- weights,
- network_name: network.name,
- skipDemuxProgress: true,
- },
- [canvasOut, canvasIn],
- )
-
- return blobPromise
- }
-
- async startRealtimeUpscale(
- videoElement,
- canvasElement,
- networkSize,
- scaleOverride,
- ) {
- if (!videoElement || !canvasElement) {
- throw new Error('videoElement and canvasElement are required')
- }
-
- const size = networkSize || this.networkSize
- this.validateNetworkSize(size)
-
- const network = this.networks[size]
- const weights = await this.loadWeights(size)
-
- const scale
- = typeof scaleOverride === 'number' && scaleOverride > 0
- ? scaleOverride
- : this.videoScale
-
- const ensureMetadata = () => {
- return new Promise((resolve, reject) => {
- if (videoElement.readyState >= 1) {
- resolve()
- return
- }
- let onLoaded, onError
- const cleanup = () => {
- videoElement.removeEventListener('loadedmetadata', onLoaded)
- videoElement.removeEventListener('error', onError)
- }
- onLoaded = () => {
- cleanup()
- resolve()
- }
- onError = () => {
- cleanup()
- reject(new Error('Failed to load video metadata'))
- }
- videoElement.addEventListener('loadedmetadata', onLoaded)
- videoElement.addEventListener('error', onError)
- })
- }
-
- await ensureMetadata()
-
- const width = videoElement.videoWidth
- const height = videoElement.videoHeight
- if (!width || !height) {
- throw new Error('Invalid video dimensions')
- }
-
- const firstInit = !canvasElement._upscalerTransferred
-
- let offscreen = null
- if (firstInit) {
- canvasElement.width = width * scale
- canvasElement.height = height * scale
-
- if (typeof canvasElement.transferControlToOffscreen !== 'function') {
- throw new TypeError('OffscreenCanvas is not supported in this environment')
- }
-
- offscreen = canvasElement.transferControlToOffscreen()
- canvasElement._upscalerTransferred = true
- }
-
- const captureCanvas = document.createElement('canvas')
- captureCanvas.width = width
- captureCanvas.height = height
- const captureCtx = captureCanvas.getContext('2d')
-
- if (this.realtimeLoopId) {
- cancelAnimationFrame(this.realtimeLoopId)
- this.realtimeLoopId = null
- }
-
- const initPayload = {
- cmd: 'realtimeInit',
- data: {
- upscaled: firstInit ? offscreen : null,
- resolution: {
- width,
- height,
- scale,
- outputWidth: width * scale,
- outputHeight: height * scale,
- },
- network_name: network.name,
- weights,
- },
- }
-
- if (firstInit && offscreen) {
- this.getWorker().postMessage(initPayload, [offscreen])
- }
- else {
- this.getWorker().postMessage(initPayload)
- }
-
- this.realtimeState = {
- running: true,
- video: videoElement,
- canvas: canvasElement,
- scale,
- busy: false,
- frameIndex: 0,
- captureCanvas,
- captureCtx,
- }
-
- const loop = async () => {
- if (!this.realtimeState || !this.realtimeState.running)
- return
- const state = this.realtimeState
- const v = state.video
-
- const haveCurrentData = v.HAVE_CURRENT_DATA || 2
-
- if (
- !v.paused
- && !v.ended
- && !state.busy
- && v.readyState >= haveCurrentData
- && v.videoWidth > 0
- && v.videoHeight > 0
- ) {
- state.busy = true
- try {
- const captureCanvas = state.captureCanvas
- const captureCtx = state.captureCtx
- if (!captureCanvas || !captureCtx) {
- throw new Error('Missing capture canvas')
- }
- captureCtx.drawImage(v, 0, 0, captureCanvas.width, captureCanvas.height)
- const frame = await createImageBitmap(captureCanvas)
- state.frameIndex += 1
-
- this.getWorker().postMessage(
- {
- cmd: 'realtimeFrame',
- frame,
- },
- [frame],
- )
- }
- catch (e) {
- console.warn('realtimeFrame error', e)
- }
- finally {
- if (this.realtimeState) {
- this.realtimeState.busy = false
- }
- }
- }
-
- this.realtimeLoopId = requestAnimationFrame(loop)
- }
-
- this.realtimeLoopId = requestAnimationFrame(loop)
- }
-
- stopRealtimeUpscale() {
- if (this.realtimeLoopId) {
- cancelAnimationFrame(this.realtimeLoopId)
- this.realtimeLoopId = null
- }
- if (this.realtimeState) {
- this.realtimeState.running = false
- this.realtimeState = null
- }
- }
-
- async downloadUpscaled(file, networkSize, filename = null) {
- const isVideo = file.type.startsWith('video')
- const size = networkSize || this.networkSize
- const blob = isVideo
- ? await this.upscaleVideo(file, size)
- : await this.upscaleImage(file, size)
- const url = URL.createObjectURL(blob)
- const link = document.createElement('a')
- link.href = url
- link.download
- = filename || `upscaled_${Date.now()}.${isVideo ? 'mp4' : 'png'}`
- link.click()
- URL.revokeObjectURL(url)
- return blob
- }
-
- getSupportedNetworks() {
- return Object.keys(this.networks)
- }
-
- static async getGPUCapability() {
- try {
- if (navigator.gpu) {
- const adapter = await navigator.gpu.requestAdapter()
- if (adapter) {
- const buffer = adapter.limits.maxStorageBufferBindingSize / 512
- const texture = 8192 * 8192
- return Math.min(buffer, texture)
- }
- }
- const canvas = document.createElement('canvas')
- const gl = canvas.getContext('webgl2')
- if (gl)
- return gl.getParameter(gl.MAX_TEXTURE_SIZE) ** 2
- return 8388608
- }
- catch {
- return 8388608
- }
- }
-
- static getBackendType() {
- try {
- if (navigator.gpu)
- return 'webgpu'
- if (document.createElement('canvas').getContext('webgl2'))
- return 'webgl'
- return 'unknown'
- }
- catch {
- return 'unknown'
- }
- }
-
- getAppState() {
- return {
- backend: Upscaler.getBackendType(),
- isProcessing: false,
- progress: 0,
- width: 0,
- height: 0,
- }
- }
-
- onProgress(callback) {
- if (typeof callback === 'function') {
- this.progressCallback = callback
- }
- }
-
- dispose() {
- this.stopRealtimeUpscale()
- if (this.workerInstance) {
- this.workerInstance.terminate()
- this.workerInstance = null
- }
- this.messageHandlers = {}
- this.progressCallback = null
- this.processingType = null
- this.weightsCache.clear()
- }
-}
diff --git a/packages/artplayer-plugin-websr/src/index.js b/packages/artplayer-plugin-websr/src/index.js
deleted file mode 100644
index 7e7dcf8ac..000000000
--- a/packages/artplayer-plugin-websr/src/index.js
+++ /dev/null
@@ -1,250 +0,0 @@
-import Upscaler from './Upscaler.js'
-
-function artplayerPluginWebsr(
- option = {
- networkSize: 'medium',
- compare: false,
- weightsBaseUrl: '/weights',
- workerUrl: '/worker/main.js',
- videoScale: 2,
- },
-) {
- return (art) => {
- const { $video, $player } = art.template
-
- const $canvas = document.createElement('canvas')
- $player.appendChild($canvas)
-
- $canvas.style.position = 'absolute'
- $canvas.style.zIndex = '11'
- $canvas.style.pointerEvents = 'none'
- $canvas.style.top = '50%'
- $canvas.style.left = '50%'
- $canvas.style.transform = 'translate(-50%, -50%)'
- $canvas.style.opacity = '0'
- $canvas.style.transition = 'opacity 0.2s ease'
-
- function calcCanvasSize() {
- const videoElement = art.video
- const containerWidth = $player.offsetWidth || 640
- const containerHeight = $player.offsetHeight || 360
- const videoWidth = videoElement.videoWidth || 640
- const videoHeight = videoElement.videoHeight || 360
- const aspectRatio = videoWidth / videoHeight
- let displayWidth = containerWidth
- let displayHeight = containerHeight
- if (containerWidth / containerHeight > aspectRatio) {
- displayWidth = containerHeight * aspectRatio
- }
- else {
- displayHeight = containerWidth / aspectRatio
- }
- return { displayWidth, displayHeight }
- }
-
- if (!Upscaler || !Upscaler.isSupported || !Upscaler.isSupported()) {
- console.warn('Upscaler is not supported in this environment')
- return
- }
-
- const upscaler = new Upscaler({
- networkSize: option.networkSize || 'medium',
- weightsBaseUrl: option.weightsBaseUrl || '/weights',
- workerUrl: option.workerUrl || '/worker/main.js',
- videoScale: option.videoScale || 2,
- })
-
- let realtimeStarted = false
-
- async function startRealtime() {
- if (realtimeStarted)
- return
- realtimeStarted = true
-
- try {
- await new Promise((resolve, reject) => {
- if ($video.readyState >= 1) {
- resolve()
- return
- }
- let onLoaded, onError
- const cleanup = () => {
- $video.removeEventListener('loadedmetadata', onLoaded)
- $video.removeEventListener('error', onError)
- }
- onLoaded = () => {
- cleanup()
- resolve()
- }
- onError = () => {
- cleanup()
- reject(new Error('Failed to load video metadata'))
- }
- $video.addEventListener('loadedmetadata', onLoaded)
- $video.addEventListener('error', onError)
- })
-
- await upscaler.startRealtimeUpscale(
- $video,
- $canvas,
- option.networkSize || upscaler.networkSize,
- )
-
- requestAnimationFrame(() => {
- $canvas.style.opacity = '1'
- })
- }
- catch (e) {
- console.error(
- 'artplayer-plugin-upscaler: failed to start realtime',
- e,
- )
- }
- }
-
- const { displayWidth, displayHeight } = calcCanvasSize()
- $canvas.style.width = `${displayWidth}px`
- $canvas.style.height = `${displayHeight}px`
-
- let comparePosition = 50
- let isDragging = false
- let compareEnabled = !!option.compare
-
- const $handler = document.createElement('div')
- $handler.style.position = 'absolute'
- $handler.style.width = '3px'
- $handler.style.backgroundColor = 'rgba(255, 255, 255, 0.75)'
- $handler.style.cursor = 'ew-resize'
- $handler.style.zIndex = '12'
- $handler.style.pointerEvents = 'auto'
- $handler.style.display = compareEnabled ? 'block' : 'none'
- $handler.style.boxShadow = '0 0 2px rgba(0, 0, 0, 0.1)'
-
- if (compareEnabled) {
- $player.appendChild($handler)
- $handler.addEventListener('mousedown', handleMouseDown)
- document.addEventListener('mousemove', handleMouseMove)
- document.addEventListener('mouseup', handleMouseUp)
- updateCompareMask()
- }
-
- function updateCompareMask() {
- if (compareEnabled) {
- const { displayWidth, displayHeight } = calcCanvasSize()
- const gradient = `linear-gradient(to right, transparent 0%, transparent ${comparePosition}%, black ${comparePosition}%, black 100%)`
- $canvas.style.maskImage = gradient
-
- const containerWidth = $player.offsetWidth || 640
- const containerHeight = $player.offsetHeight || 360
- const canvasLeft = (containerWidth - displayWidth) / 2
- const canvasTop = (containerHeight - displayHeight) / 2
- const handlerX
- = canvasLeft + (displayWidth * comparePosition) / 100
-
- $handler.style.top = `${canvasTop}px`
- $handler.style.left = `${handlerX}px`
- $handler.style.height = `${displayHeight}px`
- $handler.style.transform = 'translateX(-50%)'
- }
- else {
- $canvas.style.maskImage = 'none'
- }
- }
-
- function handleMouseDown(_e) {
- if (compareEnabled) {
- isDragging = true
- }
- }
-
- function handleMouseMove(e) {
- if (compareEnabled && isDragging) {
- const rect = $player.getBoundingClientRect()
- const { displayWidth } = calcCanvasSize()
- const containerWidth = $player.offsetWidth || 640
- const canvasLeft = (containerWidth - displayWidth) / 2
- const x = e.clientX - rect.left
- const relativeX = x - canvasLeft
- comparePosition = (relativeX / displayWidth) * 100
- comparePosition = Math.max(0, Math.min(100, comparePosition))
- updateCompareMask()
- }
- }
-
- function handleMouseUp(_e) {
- isDragging = false
- }
-
- art.on('destroy', () => {
- if (upscaler && typeof upscaler.dispose === 'function') {
- upscaler.dispose()
- }
- $handler.removeEventListener('mousedown', handleMouseDown)
- document.removeEventListener('mousemove', handleMouseMove)
- document.removeEventListener('mouseup', handleMouseUp)
- })
-
- art.on('resize', () => {
- const { displayWidth, displayHeight } = calcCanvasSize()
- $canvas.style.width = `${displayWidth}px`
- $canvas.style.height = `${displayHeight}px`
- updateCompareMask()
- })
-
- art.on('play', () => {
- startRealtime()
- })
-
- function update(newOption = {}) {
- if (!newOption || typeof newOption !== 'object')
- return
-
- if (Object.prototype.hasOwnProperty.call(newOption, 'compare')) {
- const nextCompare = !!newOption.compare
- option.compare = nextCompare
- compareEnabled = nextCompare
-
- if (compareEnabled) {
- if (!$handler.parentNode) {
- $player.appendChild($handler)
- }
- $handler.style.display = 'block'
- $handler.addEventListener('mousedown', handleMouseDown)
- document.addEventListener('mousemove', handleMouseMove)
- document.addEventListener('mouseup', handleMouseUp)
- updateCompareMask()
- }
- else {
- $canvas.style.maskImage = 'none'
- $handler.style.display = 'none'
- $handler.removeEventListener('mousedown', handleMouseDown)
- document.removeEventListener('mousemove', handleMouseMove)
- document.removeEventListener('mouseup', handleMouseUp)
- }
- }
-
- if (newOption.networkSize) {
- const nextSize = newOption.networkSize
- option.networkSize = nextSize
- if (
- upscaler
- && typeof upscaler.stopRealtimeUpscale === 'function'
- ) {
- upscaler.stopRealtimeUpscale()
- }
- realtimeStarted = false
- if (!art.paused) {
- startRealtime()
- }
- }
- }
-
- return {
- name: 'artplayerPluginWebsr',
- upscaler,
- update,
- }
- }
-}
-
-export default artplayerPluginWebsr
diff --git a/packages/artplayer-plugin-websr/types/artplayer-plugin-websr.d.ts b/packages/artplayer-plugin-websr/types/artplayer-plugin-websr.d.ts
deleted file mode 100644
index 218947d78..000000000
--- a/packages/artplayer-plugin-websr/types/artplayer-plugin-websr.d.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type Artplayer from 'artplayer'
-
-interface Option {
- compare?: boolean
- networkSize?: 'small' | 'medium' | 'large'
- weightsBaseUrl?: string
- workerUrl?: string
- videoScale?: number
-}
-
-interface Result {
- name: 'artplayerPluginWebsr'
- upscaler: any
- update: (option: Option) => void
-}
-
-declare const artplayerPluginWebsr: (option?: Option) => (art: Artplayer) => Result
-
-export default artplayerPluginWebsr