diff --git a/docs/assets/ts/artplayer-plugin-ambilight.d.ts b/docs/assets/ts/artplayer-plugin-ambilight.d.ts
index 50abda15a..6376c64ba 100644
--- a/docs/assets/ts/artplayer-plugin-ambilight.d.ts
+++ b/docs/assets/ts/artplayer-plugin-ambilight.d.ts
@@ -1,20 +1,35 @@
-interface Option {
- blur?: string
- opacity?: number
- frequency?: number
- zIndex?: number
- duration?: number
+// Generated from the package public declaration by yarn build:ts. Do not edit.
+/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
+declare namespace artplayerPluginAmbilight {
+ interface Option {
+ /** CSS blur radius. @default '50px' */
+ blur?: string
+ /** Grid cell opacity. @default 0.5 */
+ opacity?: number
+ /** Maximum sampling frequency in frames per second. @default 10 */
+ frequency?: number
+ /** Historical input retained for compatibility; runtime uses a fixed z-index of 9. */
+ zIndex?: number
+ /** Background color transition duration in seconds. @default 0.3 */
+ duration?: number
+ }
+ interface Result {
+ name: 'artplayerPluginAmbilight'
+ /** Start sampling; does nothing after the player is destroyed. */
+ start: () => void
+ /** Stop sampling while retaining the last colors. */
+ stop: () => void
+ }
+ interface Callable {
+ (option?: Option): (art: Artplayer) => Result
+ /** Keep Parameters extraction identical to the published 1.1.0 signature. */
+ (option: Option): (art: Artplayer) => Result
+ }
+ interface Factory extends Callable {
+ /** Same function, including historical require(package).default calls. */
+ readonly default: Factory
+ }
}
-
-interface Result {
- name: 'artplayerPluginAmbilight'
- start: () => void
- stop: () => void
-}
-
-declare const artplayerPluginAmbilight: (option: Option) => (art: Artplayer) => Result
-
-export default artplayerPluginAmbilight
-
+declare const artplayerPluginAmbilight: artplayerPluginAmbilight.Factory
export = artplayerPluginAmbilight
export as namespace artplayerPluginAmbilight;
diff --git a/docs/compiled/artplayer-plugin-ambilight.js b/docs/compiled/artplayer-plugin-ambilight.js
index 8356fa320..3e58151f2 100644
--- a/docs/compiled/artplayer-plugin-ambilight.js
+++ b/docs/compiled/artplayer-plugin-ambilight.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",o=null,i=0,l=!1,u=0;function c(){const s=i,f=()=>"running"===r&&s===i;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===o&&function(){const t=i;try{o=requestAnimationFrame(()=>{"running"===r&&t===i&&(o=null,c())})}catch(n){throw r="stopped",i++,n}}()}}return{start:function(){"stopped"===r&&(r="running",i++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)}}}return function(n={}){return e=>{let r=e,o=e.isDestroy,i=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){o||u?.start()},stop(){o||u?.stop()}};if(o)return r=null,s;function f(){const t=[u?.destroy,l?.destroy,i?.destroy];u=null,l=null,i=null;const n=[];for(const r of t)try{r?.()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(o)return;o=!0;const t=r;r=null;const n=[];for(const[r,o]of c.splice(0))try{t?.off(r,o)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),r?.on(t,n)}try{if(d("destroy",a),o)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(o)return s;if(i=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("
").join("");const e=Array.from(n.children);return{mount:function(r,o,i){if(i()&&(t.addClass(n,"artplayer-plugin-ambilight"),i()&&(r.parentNode.insertBefore(n,r),i()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!i())return;t.setStyles(n,{opacity:o.opacity,filter:`blur(${o.blur})`,transition:`background-color ${o.duration}s ease`})}}},render:function(t,n){for(let r=0;r!o),o)return f(),s;const b=h.createElement("canvas");if(o)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let o=!1,i=!1;return{read:function(l){if(o||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(i){const n=e();if(o||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),o||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,i=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(o||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),o||!l())return null;const[i,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${i}, ${u}, ${c})`)}return a}catch{return o||(i=!0,t.width=0,t.height=0),null}},destroy:function(){o||(o=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),o)return f(),s;u=t(g,()=>Boolean(r?.playing),t=>{const n=l?.read(t);n&&t()&&i?.render(n,t)}),d("ready",s.start),o&&f()}catch(h){try{a()}catch{}try{f()}catch{}throw h}return s}}});
+!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",i=null,o=0,l=!1,u=0;function c(){const s=o,f=()=>"running"===r&&s===o;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===i&&function(){const t=o;try{i=requestAnimationFrame(()=>{"running"===r&&t===o&&(i=null,c())})}catch(n){throw r="stopped",o++,n}}()}}return{start:function(){"stopped"===r&&(r="running",o++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)}}}function n(n={}){return e=>{let r=e,i=e.isDestroy,o=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){i||u?.start()},stop(){i||u?.stop()}};if(i)return r=null,s;function f(){const t=[u?.destroy,l?.destroy,o?.destroy];u=null,l=null,o=null;const n=[];for(const r of t)try{r?.()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(i)return;i=!0;const t=r;r=null;const n=[];for(const[r,i]of c.splice(0))try{t?.off(r,i)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),r?.on(t,n)}try{if(d("destroy",a),i)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(i)return s;if(o=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("").join("");const e=Array.from(n.children);return{mount:function(r,i,o){if(o()&&(t.addClass(n,"artplayer-plugin-ambilight"),o()&&(r.parentNode.insertBefore(n,r),o()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!o())return;t.setStyles(n,{opacity:i.opacity,filter:`blur(${i.blur})`,transition:`background-color ${i.duration}s ease`})}}},render:function(t,n){for(let r=0;r!i),i)return f(),s;const b=h.createElement("canvas");if(i)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let i=!1,o=!1;return{read:function(l){if(i||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(o){const n=e();if(i||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),i||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,o=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(i||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${o}, ${u}, ${c})`)}return a}catch{return i||(o=!0,t.width=0,t.height=0),null}},destroy:function(){i||(i=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),i)return f(),s;u=t(g,()=>Boolean(r?.playing),t=>{const n=l?.read(t);n&&t()&&o?.render(n,t)}),d("ready",s.start),i&&f()}catch(h){try{a()}catch{}try{f()}catch{}throw h}return s}}return Object.assign(n,{default:n})});
diff --git a/docs/compiled/artplayer-plugin-ambilight.legacy.js b/docs/compiled/artplayer-plugin-ambilight.legacy.js
index c6c89eca5..b37c2611b 100644
--- a/docs/compiled/artplayer-plugin-ambilight.legacy.js
+++ b/docs/compiled/artplayer-plugin-ambilight.legacy.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",o=null,i=0,l=!1,u=0;function c(){const s=i,f=()=>"running"===r&&s===i;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===o&&function(){const t=i;try{o=requestAnimationFrame(()=>{"running"===r&&t===i&&(o=null,c())})}catch(n){throw r="stopped",i++,n}}()}}return{start:function(){"stopped"===r&&(r="running",i++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)}}}return function(n={}){return e=>{let r=e,o=e.isDestroy,i=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){o||null==u||u.start()},stop(){o||null==u||u.stop()}};if(o)return r=null,s;function f(){const t=[null==u?void 0:u.destroy,null==l?void 0:l.destroy,null==i?void 0:i.destroy];u=null,l=null,i=null;const n=[];for(const r of t)try{null==r||r()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(o)return;o=!0;const t=r;r=null;const n=[];for(const[r,o]of c.splice(0))try{null==t||t.off(r,o)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),null==r||r.on(t,n)}try{if(d("destroy",a),o)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(o)return s;if(i=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("").join("");const e=Array.from(n.children);return{mount:function(r,o,i){if(i()&&(t.addClass(n,"artplayer-plugin-ambilight"),i()&&(r.parentNode.insertBefore(n,r),i()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!i())return;t.setStyles(n,{opacity:o.opacity,filter:`blur(${o.blur})`,transition:`background-color ${o.duration}s ease`})}}},render:function(t,n){for(let r=0;r!o),o)return f(),s;const b=h.createElement("canvas");if(o)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let o=!1,i=!1;return{read:function(l){if(o||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(i){const n=e();if(o||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),o||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,i=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(o||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),o||!l())return null;const[i,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${i}, ${u}, ${c})`)}return a}catch(d){return o||(i=!0,t.width=0,t.height=0),null}},destroy:function(){o||(o=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),o)return f(),s;u=t(g,()=>Boolean(null==r?void 0:r.playing),t=>{const n=null==l?void 0:l.read(t);n&&t()&&(null==i||i.render(n,t))}),d("ready",s.start),o&&f()}catch(h){try{a()}catch(y){}try{f()}catch(y){}throw h}return s}}});
+!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",i=null,o=0,l=!1,u=0;function c(){const s=o,f=()=>"running"===r&&s===o;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===i&&function(){const t=o;try{i=requestAnimationFrame(()=>{"running"===r&&t===o&&(i=null,c())})}catch(n){throw r="stopped",o++,n}}()}}return{start:function(){"stopped"===r&&(r="running",o++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)}}}function n(n={}){return e=>{let r=e,i=e.isDestroy,o=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){i||null==u||u.start()},stop(){i||null==u||u.stop()}};if(i)return r=null,s;function f(){const t=[null==u?void 0:u.destroy,null==l?void 0:l.destroy,null==o?void 0:o.destroy];u=null,l=null,o=null;const n=[];for(const r of t)try{null==r||r()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(i)return;i=!0;const t=r;r=null;const n=[];for(const[r,i]of c.splice(0))try{null==t||t.off(r,i)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),null==r||r.on(t,n)}try{if(d("destroy",a),i)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(i)return s;if(o=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("").join("");const e=Array.from(n.children);return{mount:function(r,i,o){if(o()&&(t.addClass(n,"artplayer-plugin-ambilight"),o()&&(r.parentNode.insertBefore(n,r),o()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!o())return;t.setStyles(n,{opacity:i.opacity,filter:`blur(${i.blur})`,transition:`background-color ${i.duration}s ease`})}}},render:function(t,n){for(let r=0;r!i),i)return f(),s;const b=h.createElement("canvas");if(i)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let i=!1,o=!1;return{read:function(l){if(i||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(o){const n=e();if(i||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),i||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,o=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(i||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${o}, ${u}, ${c})`)}return a}catch(d){return i||(o=!0,t.width=0,t.height=0),null}},destroy:function(){i||(i=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),i)return f(),s;u=t(g,()=>Boolean(null==r?void 0:r.playing),t=>{const n=null==l?void 0:l.read(t);n&&t()&&(null==o||o.render(n,t))}),d("ready",s.start),i&&f()}catch(h){try{a()}catch(y){}try{f()}catch(y){}throw h}return s}}return Object.assign(n,{default:n})});
diff --git a/docs/compiled/artplayer-plugin-ambilight.mjs b/docs/compiled/artplayer-plugin-ambilight.mjs
index 15089cf5d..be39b2134 100644
--- a/docs/compiled/artplayer-plugin-ambilight.mjs
+++ b/docs/compiled/artplayer-plugin-ambilight.mjs
@@ -167,11 +167,11 @@ function createAmbilightView(utils) {
}
}
function render(colors, active) {
- for (let index = 0; index < colors.length; index++) {
+ for (let index2 = 0; index2 < colors.length; index2++) {
if (!active())
return;
- const item = items[index];
- const color = colors[index];
+ const item = items[index2];
+ const color = colors[index2];
if (item && color !== void 0)
item.style.backgroundColor = color;
}
@@ -299,6 +299,7 @@ function artplayerPluginAmbilight(option = {}) {
return result;
};
}
+const index = Object.assign(artplayerPluginAmbilight, { default: artplayerPluginAmbilight });
export {
- artplayerPluginAmbilight as default
+ index as default
};
diff --git a/package.json b/package.json
index 09621b2db..fdebb9791 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,8 @@
"check:impact": "node refactor/scripts/impact.mjs",
"check:contracts": "node refactor/scripts/contracts.mjs",
"test:contracts": "node refactor/scripts/run-contracts.mjs",
- "test:ambilight": "node --test test/ambilight.test.js test/ambilight-lifecycle.test.js"
+ "test:ambilight": "node --test test/ambilight.test.js test/ambilight-lifecycle.test.js",
+ "test:ambilight-types-package": "node refactor/scripts/ambilight-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
diff --git a/packages/artplayer-plugin-ambilight/.npmignore b/packages/artplayer-plugin-ambilight/.npmignore
index 4439c6874..2b272d565 100644
--- a/packages/artplayer-plugin-ambilight/.npmignore
+++ b/packages/artplayer-plugin-ambilight/.npmignore
@@ -2,4 +2,5 @@ node_modules
.DS_Store
yarn.lock
package-lock.json
-src
\ No newline at end of file
+src
+tsconfig.json
diff --git a/packages/artplayer-plugin-ambilight/ARCHITECTURE.md b/packages/artplayer-plugin-ambilight/ARCHITECTURE.md
index 88becd27a..4eb99a30b 100644
--- a/packages/artplayer-plugin-ambilight/ARCHITECTURE.md
+++ b/packages/artplayer-plugin-ambilight/ARCHITECTURE.md
@@ -2,7 +2,8 @@
Read the frozen published contracts in `refactor/baselines/ambilight-contract.md`
before changing the public factory or distribution. Source is strict TypeScript;
-the existing public declaration remains the input contract until task 04.
+`types/artplayer-plugin-ambilight.d.ts` owns the public options and result contract.
+The runtime derives its option type from that declaration; do not duplicate it.
| Module | Responsibility |
| --- | --- |
@@ -42,5 +43,22 @@ and `yarn test:browser test/browser/ambilight-lifecycle.spec.js`. Historical tes
in `test/ambilight.test.js` and `test/browser/ambilight.spec.js` intentionally retain
the old failures. Candidate tests must remain separate. The browser suite runs real
video/canvas with a lexical native RAF observer across published cores 5.1.7/5.4.0
-and candidate core in Chromium/Firefox/WebKit. Proxy/device, packed consumer and
-public type acceptance remain tasks 04-06; see `refactor/ambilight-validation.md`.
+and candidate core in Chromium/Firefox/WebKit. Proxy/device and complete packed
+browser/demo acceptance remain tasks 05-06; see `refactor/ambilight-validation.md`.
+
+Public types merge a callable value with a type-only namespace. An optional first
+overload permits omitted/undefined options; the required final overload preserves
+the published 1.1.0 `Parameters` extraction. Do not reorder these signatures. The
+runtime factory has a self-referencing `default` property to support both historical
+CommonJS forms. `.d.mts`/`.d.cts` forward to the same declaration for the actual ESM/
+CommonJS entry kinds; `typesVersions` supplies `/legacy` to older resolvers. The
+editor global is generated by `yarn build:ts` through the semantic plugin generator.
+
+Run `node --test refactor/scripts/ambilight-types.test.mjs` for public/editor/format
+contracts and `yarn test:ambilight-types-package` for offline tarball installations
+outside the workspace. The latter verifies two actual published plugin archives and
+candidate against the same packed candidate core, five compiler modes plus two
+candidate modes with interop disabled, and ten negative uses. Published 1.1.0 fails
+NodeNext ESM with its old declarations; candidate must not inherit that failure.
+Published 1.0.0 required all option fields; 1.1.0 already made them optional. Current
+inference follows 1.1.0 while retaining valid calls from both generations.
diff --git a/packages/artplayer-plugin-ambilight/README.md b/packages/artplayer-plugin-ambilight/README.md
index d8be996ba..0f0d65af7 100644
--- a/packages/artplayer-plugin-ambilight/README.md
+++ b/packages/artplayer-plugin-ambilight/README.md
@@ -9,6 +9,23 @@ failures are skipped and retried, allowing a later readable source to recover.
Internal modules and compatibility tests are documented in [ARCHITECTURE.md](ARCHITECTURE.md).
+## Usage and TypeScript
+
+```ts
+import type { Option } from 'artplayer-plugin-ambilight'
+import artplayerPluginAmbilight from 'artplayer-plugin-ambilight'
+
+const option: Option = { blur: '50px', opacity: 0.5, frequency: 10, duration: 0.3 }
+// Add to ArtPlayer's plugins option:
+const plugins = [artplayerPluginAmbilight(option)]
+```
+
+Options can be omitted. `require('artplayer-plugin-ambilight')` and the historical
+`require('artplayer-plugin-ambilight').default` refer to the same factory. The
+`/legacy` entry remains available. The historical `zIndex` input is accepted but
+ignored; the grid uses 9. Option fields stay optional as in version 1.1.0; code
+extracting their types should account for `undefined`.
+
## Demo
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-ambilight/index.js&example=ambilight)
diff --git a/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js b/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js
index 8356fa320..3e58151f2 100644
--- a/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js
+++ b/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",o=null,i=0,l=!1,u=0;function c(){const s=i,f=()=>"running"===r&&s===i;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===o&&function(){const t=i;try{o=requestAnimationFrame(()=>{"running"===r&&t===i&&(o=null,c())})}catch(n){throw r="stopped",i++,n}}()}}return{start:function(){"stopped"===r&&(r="running",i++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)}}}return function(n={}){return e=>{let r=e,o=e.isDestroy,i=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){o||u?.start()},stop(){o||u?.stop()}};if(o)return r=null,s;function f(){const t=[u?.destroy,l?.destroy,i?.destroy];u=null,l=null,i=null;const n=[];for(const r of t)try{r?.()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(o)return;o=!0;const t=r;r=null;const n=[];for(const[r,o]of c.splice(0))try{t?.off(r,o)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),r?.on(t,n)}try{if(d("destroy",a),o)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(o)return s;if(i=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("").join("");const e=Array.from(n.children);return{mount:function(r,o,i){if(i()&&(t.addClass(n,"artplayer-plugin-ambilight"),i()&&(r.parentNode.insertBefore(n,r),i()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!i())return;t.setStyles(n,{opacity:o.opacity,filter:`blur(${o.blur})`,transition:`background-color ${o.duration}s ease`})}}},render:function(t,n){for(let r=0;r!o),o)return f(),s;const b=h.createElement("canvas");if(o)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let o=!1,i=!1;return{read:function(l){if(o||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(i){const n=e();if(o||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),o||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,i=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(o||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),o||!l())return null;const[i,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${i}, ${u}, ${c})`)}return a}catch{return o||(i=!0,t.width=0,t.height=0),null}},destroy:function(){o||(o=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),o)return f(),s;u=t(g,()=>Boolean(r?.playing),t=>{const n=l?.read(t);n&&t()&&i?.render(n,t)}),d("ready",s.start),o&&f()}catch(h){try{a()}catch{}try{f()}catch{}throw h}return s}}});
+!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",i=null,o=0,l=!1,u=0;function c(){const s=o,f=()=>"running"===r&&s===o;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===i&&function(){const t=o;try{i=requestAnimationFrame(()=>{"running"===r&&t===o&&(i=null,c())})}catch(n){throw r="stopped",o++,n}}()}}return{start:function(){"stopped"===r&&(r="running",o++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)}}}function n(n={}){return e=>{let r=e,i=e.isDestroy,o=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){i||u?.start()},stop(){i||u?.stop()}};if(i)return r=null,s;function f(){const t=[u?.destroy,l?.destroy,o?.destroy];u=null,l=null,o=null;const n=[];for(const r of t)try{r?.()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(i)return;i=!0;const t=r;r=null;const n=[];for(const[r,i]of c.splice(0))try{t?.off(r,i)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),r?.on(t,n)}try{if(d("destroy",a),i)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(i)return s;if(o=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("").join("");const e=Array.from(n.children);return{mount:function(r,i,o){if(o()&&(t.addClass(n,"artplayer-plugin-ambilight"),o()&&(r.parentNode.insertBefore(n,r),o()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!o())return;t.setStyles(n,{opacity:i.opacity,filter:`blur(${i.blur})`,transition:`background-color ${i.duration}s ease`})}}},render:function(t,n){for(let r=0;r!i),i)return f(),s;const b=h.createElement("canvas");if(i)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let i=!1,o=!1;return{read:function(l){if(i||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(o){const n=e();if(i||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),i||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,o=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(i||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${o}, ${u}, ${c})`)}return a}catch{return i||(o=!0,t.width=0,t.height=0),null}},destroy:function(){i||(i=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),i)return f(),s;u=t(g,()=>Boolean(r?.playing),t=>{const n=l?.read(t);n&&t()&&o?.render(n,t)}),d("ready",s.start),i&&f()}catch(h){try{a()}catch{}try{f()}catch{}throw h}return s}}return Object.assign(n,{default:n})});
diff --git a/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js b/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js
index c6c89eca5..b37c2611b 100644
--- a/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js
+++ b/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",o=null,i=0,l=!1,u=0;function c(){const s=i,f=()=>"running"===r&&s===i;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===o&&function(){const t=i;try{o=requestAnimationFrame(()=>{"running"===r&&t===i&&(o=null,c())})}catch(n){throw r="stopped",i++,n}}()}}return{start:function(){"stopped"===r&&(r="running",i++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",i++;const t=o;o=null,null!==t&&cancelAnimationFrame(t)}}}return function(n={}){return e=>{let r=e,o=e.isDestroy,i=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){o||null==u||u.start()},stop(){o||null==u||u.stop()}};if(o)return r=null,s;function f(){const t=[null==u?void 0:u.destroy,null==l?void 0:l.destroy,null==i?void 0:i.destroy];u=null,l=null,i=null;const n=[];for(const r of t)try{null==r||r()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(o)return;o=!0;const t=r;r=null;const n=[];for(const[r,o]of c.splice(0))try{null==t||t.off(r,o)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),null==r||r.on(t,n)}try{if(d("destroy",a),o)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(o)return s;if(i=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("").join("");const e=Array.from(n.children);return{mount:function(r,o,i){if(i()&&(t.addClass(n,"artplayer-plugin-ambilight"),i()&&(r.parentNode.insertBefore(n,r),i()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!i())return;t.setStyles(n,{opacity:o.opacity,filter:`blur(${o.blur})`,transition:`background-color ${o.duration}s ease`})}}},render:function(t,n){for(let r=0;r!o),o)return f(),s;const b=h.createElement("canvas");if(o)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let o=!1,i=!1;return{read:function(l){if(o||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(i){const n=e();if(o||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),o||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,i=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(o||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),o||!l())return null;const[i,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${i}, ${u}, ${c})`)}return a}catch(d){return o||(i=!0,t.width=0,t.height=0),null}},destroy:function(){o||(o=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),o)return f(),s;u=t(g,()=>Boolean(null==r?void 0:r.playing),t=>{const n=null==l?void 0:l.read(t);n&&t()&&(null==i||i.render(n,t))}),d("ready",s.start),o&&f()}catch(h){try{a()}catch(y){}try{f()}catch(y){}throw h}return s}}});
+!function(t,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(t.artplayerPluginAmbilight=n(),define(function(){return t.artplayerPluginAmbilight})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginAmbilight=n()}(this,function(){"use strict";function t(t,n,e){let r="stopped",i=null,o=0,l=!1,u=0;function c(){const s=o,f=()=>"running"===r&&s===o;l=!0;try{const r=performance.now();if(r-u<1e3/t||!n()||!f())return;u=r,e(f)}finally{l=!1,"running"===r&&null===i&&function(){const t=o;try{i=requestAnimationFrame(()=>{"running"===r&&t===o&&(i=null,c())})}catch(n){throw r="stopped",o++,n}}()}}return{start:function(){"stopped"===r&&(r="running",o++,l||c())},stop:function(){if("destroyed"===r)return;r="stopped",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)},destroy:function(){if("destroyed"===r)return;r="destroyed",o++;const t=i;i=null,null!==t&&cancelAnimationFrame(t)}}}function n(n={}){return e=>{let r=e,i=e.isDestroy,o=null,l=null,u=null;const c=[],s={name:"artplayerPluginAmbilight",start(){i||null==u||u.start()},stop(){i||null==u||u.stop()}};if(i)return r=null,s;function f(){const t=[null==u?void 0:u.destroy,null==l?void 0:l.destroy,null==o?void 0:o.destroy];u=null,l=null,o=null;const n=[];for(const r of t)try{null==r||r()}catch(e){n.push(e)}if(n.length)throw n[0]}function a(){if(i)return;i=!0;const t=r;r=null;const n=[];for(const[r,i]of c.splice(0))try{null==t||t.off(r,i)}catch(e){n.push(e)}try{f()}catch(e){n.push(e)}if(n.length)throw n[0]}function d(t,n){c.push([t,n]),null==r||r.on(t,n)}try{if(d("destroy",a),i)return s;const{$video:c}=e.template,h=e.constructor.utils,{blur:y="50px",opacity:p=.5,frequency:g=10,duration:m=.3}=n;if(i)return s;if(o=function(t){const n=t.createElement("div");n.innerHTML=Array.from({length:9}).fill("").join("");const e=Array.from(n.children);return{mount:function(r,i,o){if(o()&&(t.addClass(n,"artplayer-plugin-ambilight"),o()&&(r.parentNode.insertBefore(n,r),o()))){t.setStyles(n,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"});for(const n of e){if(!o())return;t.setStyles(n,{opacity:i.opacity,filter:`blur(${i.blur})`,transition:`background-color ${i.duration}s ease`})}}},render:function(t,n){for(let r=0;r!i),i)return f(),s;const b=h.createElement("canvas");if(i)return b.width=0,b.height=0,s;if(l=function(t,n,e){let r;try{r=t.getContext("2d"),t.width=3,t.height=3}catch(l){throw t.width=0,t.height=0,l}let i=!1,o=!1;return{read:function(l){if(i||!l())return null;const u=n.videoWidth,c=n.videoHeight;if(!Number.isFinite(u)||!Number.isFinite(c)||u<=0||c<=0)return null;const s=u/3,f=c/3,a=[];try{if(o){const n=e();if(i||!l())return n.width=0,n.height=0,null;if(r=null,r=(t=n).getContext("2d"),i||!l())return t.width=0,t.height=0,null;t.width=3,t.height=3,o=!1}if(!r)return null;for(let t=0;t<3;t++)for(let e=0;e<3;e++){if(i||!l())return null;if(r.drawImage(n,e*s,t*f,s,f,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;a.push(`rgb(${o}, ${u}, ${c})`)}return a}catch(d){return i||(o=!0,t.width=0,t.height=0),null}},destroy:function(){i||(i=!0,t.width=0,t.height=0)}}}(b,c,()=>h.createElement("canvas")),i)return f(),s;u=t(g,()=>Boolean(null==r?void 0:r.playing),t=>{const n=null==l?void 0:l.read(t);n&&t()&&(null==o||o.render(n,t))}),d("ready",s.start),i&&f()}catch(h){try{a()}catch(y){}try{f()}catch(y){}throw h}return s}}return Object.assign(n,{default:n})});
diff --git a/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs b/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs
index 15089cf5d..be39b2134 100644
--- a/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs
+++ b/packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs
@@ -167,11 +167,11 @@ function createAmbilightView(utils) {
}
}
function render(colors, active) {
- for (let index = 0; index < colors.length; index++) {
+ for (let index2 = 0; index2 < colors.length; index2++) {
if (!active())
return;
- const item = items[index];
- const color = colors[index];
+ const item = items[index2];
+ const color = colors[index2];
if (item && color !== void 0)
item.style.backgroundColor = color;
}
@@ -299,6 +299,7 @@ function artplayerPluginAmbilight(option = {}) {
return result;
};
}
+const index = Object.assign(artplayerPluginAmbilight, { default: artplayerPluginAmbilight });
export {
- artplayerPluginAmbilight as default
+ index as default
};
diff --git a/packages/artplayer-plugin-ambilight/package.json b/packages/artplayer-plugin-ambilight/package.json
index 9e7ae2d7c..7e5f66722 100644
--- a/packages/artplayer-plugin-ambilight/package.json
+++ b/packages/artplayer-plugin-ambilight/package.json
@@ -19,19 +19,36 @@
],
"exports": {
".": {
- "types": "./types/artplayer-plugin-ambilight.d.ts",
- "import": "./dist/artplayer-plugin-ambilight.mjs",
- "require": "./dist/artplayer-plugin-ambilight.js"
+ "import": {
+ "types": "./types/artplayer-plugin-ambilight.d.mts",
+ "default": "./dist/artplayer-plugin-ambilight.mjs"
+ },
+ "require": {
+ "types": "./types/artplayer-plugin-ambilight.d.cts",
+ "default": "./dist/artplayer-plugin-ambilight.js"
+ }
},
"./legacy": {
- "types": "./types/artplayer-plugin-ambilight.d.ts",
- "import": "./dist/artplayer-plugin-ambilight.legacy.js",
- "require": "./dist/artplayer-plugin-ambilight.legacy.js"
+ "import": {
+ "types": "./types/artplayer-plugin-ambilight.d.cts",
+ "default": "./dist/artplayer-plugin-ambilight.legacy.js"
+ },
+ "require": {
+ "types": "./types/artplayer-plugin-ambilight.d.cts",
+ "default": "./dist/artplayer-plugin-ambilight.legacy.js"
+ }
}
},
"main": "./dist/artplayer-plugin-ambilight.js",
"module": "./dist/artplayer-plugin-ambilight.mjs",
"types": "./types/artplayer-plugin-ambilight.d.ts",
"legacy": "./dist/artplayer-plugin-ambilight.legacy.js",
- "browserslist": "last 1 Chrome version"
+ "browserslist": "last 1 Chrome version",
+ "typesVersions": {
+ "*": {
+ "legacy": [
+ "types/artplayer-plugin-ambilight.d.ts"
+ ]
+ }
+ }
}
diff --git a/packages/artplayer-plugin-ambilight/src/index.ts b/packages/artplayer-plugin-ambilight/src/index.ts
index 8c4bad1af..4f45976ab 100644
--- a/packages/artplayer-plugin-ambilight/src/index.ts
+++ b/packages/artplayer-plugin-ambilight/src/index.ts
@@ -7,7 +7,7 @@ import { createAmbilightView } from './view'
type Option = Parameters[0]
type Subscription = ['ready' | 'destroy', () => void]
-export default function artplayerPluginAmbilight(option: Option = {}) {
+function artplayerPluginAmbilight(option: Option = {}) {
return (art: Artplayer) => {
let host: Artplayer | null = art
let closed = art.isDestroy
@@ -123,3 +123,5 @@ export default function artplayerPluginAmbilight(option: Option = {}) {
return result
}
}
+
+export default Object.assign(artplayerPluginAmbilight, { default: artplayerPluginAmbilight })
diff --git a/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.cts b/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.cts
new file mode 100644
index 000000000..be3120dce
--- /dev/null
+++ b/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.cts
@@ -0,0 +1,4 @@
+// eslint-disable-next-line ts/no-require-imports -- Preserve the CommonJS export-assignment namespace.
+import artplayerPluginAmbilight = require('./artplayer-plugin-ambilight.js')
+
+export = artplayerPluginAmbilight
diff --git a/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.mts b/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.mts
new file mode 100644
index 000000000..5cf1ff44c
--- /dev/null
+++ b/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.mts
@@ -0,0 +1,2 @@
+export { default } from './artplayer-plugin-ambilight.js'
+export type { Callable, Factory, Option, Result } from './artplayer-plugin-ambilight.js'
diff --git a/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.ts b/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.ts
index 7258dab8e..89b5aa78a 100644
--- a/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.ts
+++ b/packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.ts
@@ -1,19 +1,40 @@
+/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
import type Artplayer from 'artplayer'
-interface Option {
- blur?: string
- opacity?: number
- frequency?: number
- zIndex?: number
- duration?: number
+declare namespace artplayerPluginAmbilight {
+ interface Option {
+ /** CSS blur radius. @default '50px' */
+ blur?: string
+ /** Grid cell opacity. @default 0.5 */
+ opacity?: number
+ /** Maximum sampling frequency in frames per second. @default 10 */
+ frequency?: number
+ /** Historical input retained for compatibility; runtime uses a fixed z-index of 9. */
+ zIndex?: number
+ /** Background color transition duration in seconds. @default 0.3 */
+ duration?: number
+ }
+
+ interface Result {
+ name: 'artplayerPluginAmbilight'
+ /** Start sampling; does nothing after the player is destroyed. */
+ start: () => void
+ /** Stop sampling while retaining the last colors. */
+ stop: () => void
+ }
+
+ interface Callable {
+ (option?: Option): (art: Artplayer) => Result
+ /** Keep Parameters extraction identical to the published 1.1.0 signature. */
+ (option: Option): (art: Artplayer) => Result
+ }
+
+ interface Factory extends Callable {
+ /** Same function, including historical require(package).default calls. */
+ readonly default: Factory
+ }
}
-interface Result {
- name: 'artplayerPluginAmbilight'
- start: () => void
- stop: () => void
-}
-
-declare const artplayerPluginAmbilight: (option: Option) => (art: Artplayer) => Result
-
-export default artplayerPluginAmbilight
+declare const artplayerPluginAmbilight: artplayerPluginAmbilight.Factory
+export = artplayerPluginAmbilight
+export as namespace artplayerPluginAmbilight
diff --git a/refactor/ambilight-validation.md b/refactor/ambilight-validation.md
index 8c45920d6..f53d2fa87 100644
--- a/refactor/ambilight-validation.md
+++ b/refactor/ambilight-validation.md
@@ -46,3 +46,27 @@ candidate是当前源码;以browser-evidence的manifest和实际version为准
03证据见 [生命周期验证](baselines/ambilight-lifecycle-validation.json) 和
[实施记录](changes/2026-09-12-PKG-AMBILIGHT-03-lifecycle.md)。04公开声明及CJS.default、
05代理/实际设备、06安装包和8082 demo仍未完成;三浏览器通过不能代替这些验收。
+
+## 04公开类型与安装消费者
+
+`node --test refactor/scripts/ambilight-types.test.mjs`覆盖五种TS模式、10项非法用法、
+实际1.0.0/1.1.0声明对照、编辑器语义生成及正式main/legacy/global/ESM导出。已接入
+test:baseline和根typecheck。CommonJS自default别名兼容两代调用;可选参数重载放在前,
+必填Option签名必须放最后以保护Parameters提取。新的d.mts/d.cts为相同公共声明提供
+正确模块身份,typesVersions补齐旧解析器的legacy子路径。
+
+`yarn test:ambilight-types-package`打包当前核心和插件,在仓库外分别安装两个真实
+发布插件和候选,离线/frozen安装及逐文件哈希检查防止误用workspace链接。共17个
+编译场景:候选7个(含TS5.9.3/4.3.5关闭interop)全部零诊断且各拒绝10项非法用法;
+旧包10个场景中1.1.0 NodeNext ESM精确保留4个既有错误,其余零诊断。已发表的类型
+不一致与候选失败严格分开,不把旧错误写成候选白名单。
+
+1.0.0字段必填变为1.1.0可选是历史差异,本候选没有继续扩大推导。新测试在03提交上
+3失败/1通过,实装额外复现旧TS legacy缺映射、tsconfig打包泄漏和旧NodeNext问题。
+05代理/设备、06完整分发和8082 demo继续;当前统一test:package默认scope仍仅core/
+chapter,专项安装命令的存在不代表全部包已加入该门槛。
+
+04最终四组专项、975项完整CI及18项三引擎媒体回归通过;安装消费者证据、包哈希、
+初始失败及明确限制见 [类型验证证据](baselines/ambilight-types-validation.json) 和
+[实施记录](changes/2026-09-12-PKG-AMBILIGHT-04-types.md)。类型与两代导出具体风险已
+关闭;当前待做范围为05/06,旧阶段记录保留其当时状态。
diff --git a/refactor/baselines/ambilight-types-validation.json b/refactor/baselines/ambilight-types-validation.json
new file mode 100644
index 000000000..b669a354e
--- /dev/null
+++ b/refactor/baselines/ambilight-types-validation.json
@@ -0,0 +1,367 @@
+{
+ "schemaVersion": 1,
+ "task": "PKG-AMBILIGHT-04",
+ "status": "done",
+ "parentCommit": "6caee44dbf23073b5b307fe083633172679a4e07",
+ "checks": [
+ {
+ "command": "node --test refactor/scripts/ambilight-types.test.mjs",
+ "result": "pass in final ci:check",
+ "tests": 4,
+ "scope": "five compiler modes, ten invalid uses, two frozen declarations, editor globals, actual production CJS/global/ESM"
+ },
+ {
+ "command": "yarn test:ambilight-types-package",
+ "result": "pass with exact historical failure observation",
+ "cases": 17,
+ "candidateZeroDiagnosticCases": 7,
+ "historicalZeroDiagnosticCases": 9,
+ "historicalExpectedFailureCases": 1,
+ "invalidUsesRejectedPerCandidateCase": 10,
+ "report": "refactor/.cache/ambilight-package-types-NRiHcI/report.json",
+ "sha256": "77d85ca46927b85e801747cfbd3e905604c31f21414b54ee8b7eda6849f74300",
+ "packages": [
+ {
+ "name": "artplayer",
+ "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\ambilight-package-types-NRiHcI\\artplayer.tgz",
+ "sha256": "2139af017670a4f30f667bf5fdc1aa680f3e03b9c8d3cb767fc92d6345aedff5"
+ },
+ {
+ "name": "artplayer-plugin-ambilight",
+ "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\ambilight-package-types-NRiHcI\\artplayer-plugin-ambilight.tgz",
+ "sha256": "f79ad847970a8e4e46ab254d6654ce500ceed799eb8a4004bea5fa4ff7ff243a"
+ }
+ ],
+ "published": [
+ {
+ "label": "published-1.0.0",
+ "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-ambilight-1.0.0.tgz",
+ "sha256": "a95aa8c232ab48493e6b4d46b36edc8091230cbde5d18d7ec41912a4fa2b8956"
+ },
+ {
+ "label": "published-1.1.0",
+ "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-ambilight-1.1.0.tgz",
+ "sha256": "e95bd60fd7bce52142cf0041bad811452305029cbe1129c7d89246eb260c5c86"
+ }
+ ],
+ "matrix": [
+ {
+ "plugin": "published-1.0.0",
+ "compiler": "5.9.3",
+ "mode": "node10-commonjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.0.0",
+ "compiler": "5.9.3",
+ "mode": "nodenext-cjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.0.0",
+ "compiler": "5.9.3",
+ "mode": "nodenext-esm",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.0.0",
+ "compiler": "5.9.3",
+ "mode": "bundler-esm",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.0.0",
+ "compiler": "4.3.5",
+ "mode": "node10-commonjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.1.0",
+ "compiler": "5.9.3",
+ "mode": "node10-commonjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.1.0",
+ "compiler": "5.9.3",
+ "mode": "nodenext-cjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.1.0",
+ "compiler": "5.9.3",
+ "mode": "nodenext-esm",
+ "historicalFailure": true,
+ "diagnosticCodes": [
+ 2349,
+ 2349,
+ 2322,
+ 2344
+ ],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.1.0",
+ "compiler": "5.9.3",
+ "mode": "bundler-esm",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "published-1.1.0",
+ "compiler": "4.3.5",
+ "mode": "node10-commonjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 0
+ },
+ {
+ "plugin": "candidate",
+ "compiler": "5.9.3",
+ "mode": "node10-commonjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 10
+ },
+ {
+ "plugin": "candidate",
+ "compiler": "5.9.3",
+ "mode": "nodenext-cjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 10
+ },
+ {
+ "plugin": "candidate",
+ "compiler": "5.9.3",
+ "mode": "nodenext-esm",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 10
+ },
+ {
+ "plugin": "candidate",
+ "compiler": "5.9.3",
+ "mode": "bundler-esm",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 10
+ },
+ {
+ "plugin": "candidate",
+ "compiler": "4.3.5",
+ "mode": "node10-commonjs",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 10
+ },
+ {
+ "plugin": "candidate",
+ "compiler": "5.9.3",
+ "mode": "node10-commonjs-no-interop",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 10
+ },
+ {
+ "plugin": "candidate",
+ "compiler": "4.3.5",
+ "mode": "node10-commonjs-no-interop",
+ "historicalFailure": false,
+ "diagnosticCodes": [],
+ "invalidCount": 10
+ }
+ ]
+ },
+ {
+ "command": "yarn build artplayer-plugin-ambilight && yarn build:ts",
+ "result": "pass",
+ "scope": "main/legacy/ESM, docs/compiled copies and generated editor declarations"
+ },
+ {
+ "command": "focused eslint --no-fix",
+ "result": "pass"
+ },
+ {
+ "command": "yarn ci:check",
+ "result": "pass",
+ "tests": {
+ "unit": 878,
+ "engineering": 14,
+ "baseline": 83,
+ "mainTotal": 975,
+ "repeatedContractExecutions": 44
+ },
+ "strictProductionTypeScriptFiles": 278
+ },
+ {
+ "command": "playwright test test/browser/ambilight-lifecycle.spec.js",
+ "result": "pass",
+ "tests": 18,
+ "stats": {
+ "startTime": "2026-09-12T03:46:06.215Z",
+ "duration": 29911.739,
+ "expected": 18,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ },
+ "report": "refactor/.cache/ambilight04-final-results/report.json",
+ "archivedResults": "refactor/.cache/ambilight04-final-results/results",
+ "originalResultsPrefix": "refactor/.cache/browser/results",
+ "scope": "same native canvas/media candidate suite, npm cores 5.1.7/5.4.0 and candidate, Chromium/Firefox/WebKit"
+ }
+ ],
+ "reproducedThenFixed": [
+ "New tests on task 03 state: three failures, one historical-call test passes.",
+ "Old Node10 resolver could not find /legacy declarations: added typesVersions.",
+ "Real pack included implementation tsconfig: excluded it via package .npmignore.",
+ "Actual npm 1.1.0 NodeNext ESM declaration errors 2349,2349,2322,2344 retained as historical evidence; candidate proper declaration entry modes pass."
+ ],
+ "inferenceBoundary": "Candidate preserves published 1.1.0 optional fields and required final Parameters input. Required fields in 1.0.0 had already become optional in 1.1.0; no new widening or borrowed Ads approval.",
+ "inputs": [
+ {
+ "file": "packages/artplayer-plugin-ambilight/src/index.ts",
+ "sha256LF": "9db25144798ee667bc420b3e1f0b00ef6855185d26cefca45e05271354bf8591"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.cts",
+ "sha256LF": "4e3e0ed19f41bf27afa1a8abc9fad55169080c0c0f514171446881e87d36941d"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.mts",
+ "sha256LF": "0454444c80bedb1b32df54642256440329f78a748a811296420e05f6ae1d6563"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.ts",
+ "sha256LF": "c4a42edb8e30ad851febd15d1e61012019dd9aeb774c3b5d9bf5c59cbec9a671"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js",
+ "sha256LF": "e0487dabba876392c76e91864857fd3fe7d62f50d227f3ca0b4cc1361b34d9ea"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js",
+ "sha256LF": "f8f2909dbb63a6b8fa8297295f8084528c336fed10dbad3d6a676fd1c629a909"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs",
+ "sha256LF": "b40ed26e30d8215b4bad7eb209fd89201b4e87baca291b899cb60598ad9e38d3"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/package.json",
+ "sha256LF": "f7d9c4d7e22d5b72db159342f954cf3becda33376d12130fbd15526da40296cb"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/.npmignore",
+ "sha256LF": "145c5fa0341195e21cf13df3423a057282f8ea374e9a1b3065e2280e1ce1c5c1"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/README.md",
+ "sha256LF": "d7933f5d5d4ef7815d14d39878968680a6a623417140a7cb8c8bc864d0e69e76"
+ },
+ {
+ "file": "packages/artplayer-plugin-ambilight/ARCHITECTURE.md",
+ "sha256LF": "2a0ef231cfe5d87888a4fc6f6c38ae038e1e049189ca0d8bbfae5bb62f109a50"
+ },
+ {
+ "file": "scripts/build-ts.js",
+ "sha256LF": "1cb6badbce9b974cca0ad7d307aae7bf0008369de78f429590dea4e0323d69e6"
+ },
+ {
+ "file": "scripts/typecheck.mjs",
+ "sha256LF": "0bef8f8c6a1cdf5082fdcc3f4bfdd54f0d8bc30954a0e798db3f27b069ba9295"
+ },
+ {
+ "file": "refactor/scripts/ambilight-types.test.mjs",
+ "sha256LF": "7abfe50e32b899fd15ee248ac07878211533c569afe85ae26a6555bca19d0665"
+ },
+ {
+ "file": "refactor/scripts/ambilight-package-types.mjs",
+ "sha256LF": "8df883909fb4db92e59043265a92e4c7d01c2408b91c2a5da6e8abdccd963e3f"
+ },
+ {
+ "file": "refactor/fixtures/consumers/ambilight-published.ts",
+ "sha256LF": "fb440616e3eec4b3bfd0f6063b63fc9590a46ca471c5f1ffefacf97cff3c547a"
+ },
+ {
+ "file": "test/types/ambilight.ts",
+ "sha256LF": "aa2e4ebf4eb54a3713acf0c7a89be5eca88f7b54f75615435bd9cac664a0574a"
+ },
+ {
+ "file": "test/browser/ambilight-lifecycle.spec.js",
+ "sha256LF": "321693a24d33f0c7aee02fd4137b55175386562a645ba138e15c3a51d6348d2c"
+ },
+ {
+ "file": "docs/assets/ts/artplayer-plugin-ambilight.d.ts",
+ "sha256LF": "162bf7139e8c1e453b0aaf7290eb08ff698d3db19fae8594e9787b07efa40612"
+ },
+ {
+ "file": "package.json",
+ "sha256LF": "1c0a6f5b206cb4ecdaec8025369099b4ae8df9d7b18d987e03f91164ed96fcae"
+ }
+ ],
+ "logs": [
+ {
+ "file": "refactor/.cache/ambilight04-before.log",
+ "sha256": "c2d01f5ac9fbfd9be61187b0eaf5d27fc61e0b5019828df621bad745ce5d17f0"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-types.log",
+ "sha256": "cf385247c78a3e95acb5738356fbdb456f993c16c0e963fd5b532573efaf00ab"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-package.log",
+ "sha256": "68abe529928ae4138be893c1a5b3ef5ffa24c70a404f99d833b405b2246733bd"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-package-second.log",
+ "sha256": "b29dca1e79f9549f3fa7ef05700f246ee7a6e006476f1b5e02f6b28897b2ffd0"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-package-final.log",
+ "sha256": "ef927b871eb509e9c62b305539dfca8b35035a504fe6a3cf0073e61a7f542093"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-build.log",
+ "sha256": "2c8fa2a92b824975142d6506c13ef626d8b1fa2b97571a924e0b4e2c8658c2ff"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-editor-build.log",
+ "sha256": "f3304ba150de716716b67d4ca7d9995826b48ae2eee15169e79c764d7942de05"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-lint-final.log",
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-ci.log",
+ "sha256": "6b2bbfe06464a281b83c9d877b486f02a5520f9b99a89352e400d766b86d6a90"
+ },
+ {
+ "file": "refactor/.cache/ambilight04-browser.log",
+ "sha256": "e7688c4721ba67d0fb1b99350571b3679c04b4f85f823d730b314d1f4ff47147"
+ }
+ ],
+ "limitations": [
+ "Installed matrix uses the same packed candidate core for all plugin declaration versions; it does not establish a historical runtime core support range.",
+ "Proxy and device validation is task 05. Complete distribution, actual installed-browser and 8082 demo/editor interaction remain task 06.",
+ "The generic test:package still covers core/chapter; the dedicated Ambilight installation script does not imply whole-ecosystem acceptance.",
+ "No push, publish, release version change or remote workflow run."
+ ]
+}
diff --git a/refactor/changes/2026-09-12-PKG-AMBILIGHT-04-types.md b/refactor/changes/2026-09-12-PKG-AMBILIGHT-04-types.md
new file mode 100644
index 000000000..1108c5604
--- /dev/null
+++ b/refactor/changes/2026-09-12-PKG-AMBILIGHT-04-types.md
@@ -0,0 +1,46 @@
+# PKG-AMBILIGHT-04 公开类型与历史导出兼容
+
+03已完成自有生产源码的严格TS拆分;04补齐公共声明、Node解析模式和真实安装消费者。
+最终验证结果见 [类型与安装证据](../baselines/ambilight-types-validation.json)。
+
+## 兼容门面
+
+工厂增加指向自身的default属性:同时支持1.0.0的require(pkg).default、1.1.0的直接
+require调用和ESM默认导入。公共声明使用callable与类型namespace合并;Option/Result/
+Callable/Factory可显式导入,start/stop仍为void、name仍是原字面量。可选调用重载补齐
+省略参数及undefined,最后一个必填Option重载保持Parameters[0]不增加
+顶层undefined。zIndex保留但清楚标注运行时忽略。
+
+1.0.0所有字段必填,1.1.0已经改成可选;旧的“读取字段一定是string/number”推导差异
+是已发布历史变化。本候选保持1.1.0推导,不能声称恢复1.0.0的必填字段推导,也没有
+套用Ads的专属批准。两版实际声明和合法调用分别编译对照,精确断言已有差异。
+
+main/module/legacy/types旧路径保留;import和require分别增加d.mts/d.cts转发,旧TS
+通过typesVersions解析legacy。编辑器改用已有语义生成器,重新运行build:ts,不手改
+生成声明。包排除tsconfig.json,修复真实pack发现的实现配置泄漏。
+
+## 证据范围
+
+新增类型/产物测试先在03状态运行:4组中3失败,历史调用对照通过。工作区测试覆盖
+TS5.9.3的Node10/NodeNext CJS、ESM/Bundler及TS4.3.5,10项非法用法去掉expect-error后
+必须逐项报错。编辑器消费亦有负向断言;Node执行正式main/legacy/global/ESM产物。
+
+新脚本test:ambilight-types-package在仓库外安装两个真实npm发布及候选tarball,搭配
+同一打包候选核心;核对每个已安装文件哈希、非workspace链接、离线及frozen二次安装。
+候选额外验证两个TS版本关闭esModuleInterop仍可默认导入。旧npm1.1.0在NodeNext ESM
+实装中暴露默认导出不可调用的既有错误,按精确4个诊断保存,候选该模式必须零诊断。
+不修改历史包或把该错误当作候选容许失败。
+
+本步没有新依赖;只添加专项脚本和消费者。代理/实际设备归05,完整分发/8082 demo及
+安装包浏览器归06;本步包内README/架构说明同步更新,未推送或发布。
+
+## 最终验收
+
+四组专项检查通过;真实安装17个场景中候选7个、历史9个零诊断,另1个历史场景按
+固定4条诊断核验。候选每个场景去掉expect-error后均拒绝10项非法输入。正式三格式
+与编辑器生成通过;完整CI975项通过(878单元+14工程+83基线),另44项重复契约观察,
+278个生产TS文件严格检查。18项三核心/三引擎原生Canvas媒体回归通过,无skip。
+
+AMBILIGHT-TYPE-01及AMBILIGHT-DIST-01对应的已复现类型/导出问题关闭,本任务独立
+本地提交并审计自身。关闭这两条具体问题不等于05/06或npm发布验收完成;VAST专属
+VPN外部脚本例外没有用于本任务。
diff --git a/refactor/fixtures/consumers/ambilight-published.ts b/refactor/fixtures/consumers/ambilight-published.ts
new file mode 100644
index 000000000..0036f2fec
--- /dev/null
+++ b/refactor/fixtures/consumers/ambilight-published.ts
@@ -0,0 +1,10 @@
+import Artplayer from 'artplayer'
+import ambilight from 'artplayer-plugin-ambilight'
+
+const option = { blur: '50px', opacity: 0.5, frequency: 10, zIndex: 9, duration: 0.3 }
+const art = new Artplayer({ container: '#player', url: 'video.mp4', plugins: [ambilight(option)] })
+const result = ambilight(option)(art)
+const name: 'artplayerPluginAmbilight' = result.name
+const calls: void[] = [result.start(), result.stop()]
+const accepted: Parameters[0] = option
+void [name, calls, accepted]
diff --git a/refactor/plan.md b/refactor/plan.md
index 6d82b8872..376ddfa5f 100644
--- a/refactor/plan.md
+++ b/refactor/plan.md
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 220 项,范围 22 个包及工作区/示例。
-状态:todo 127 / doing 6 / blocked 0 / done 87 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
+状态:todo 126 / doing 6 / blocked 0 / done 88 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -151,7 +151,7 @@
| PKG-AMBILIGHT-01 | artplayer-plugin-ambilight
核对包契约与历史用法 | BASE-05 | blur/opacity/frequency/duration、start/stop 与挂载样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | done |
| PKG-AMBILIGHT-02 | artplayer-plugin-ambilight
建立特有行为与错误测试 | PKG-AMBILIGHT-01, ENG-03, ENG-05 | 跨域 canvas 失败、零尺寸、暂停、重复 start/stop 和 destroy | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | M | done |
| PKG-AMBILIGHT-03 | artplayer-plugin-ambilight
整理内部职责与资源 | PKG-AMBILIGHT-02, CORE-03, CORE-12 | 取色计算与绘帧分离,统一 RAF 和 DOM 清理 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | done |
-| PKG-AMBILIGHT-04 | artplayer-plugin-ambilight
迁移自有源码和公开类型 | PKG-AMBILIGHT-03, ENG-04, ENG-06, CORE-07 | canvas 上下文、参数及 start/stop 的明确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | todo |
+| PKG-AMBILIGHT-04 | artplayer-plugin-ambilight
迁移自有源码和公开类型 | PKG-AMBILIGHT-03, ENG-04, ENG-06, CORE-07 | canvas 上下文、参数及 start/stop 的明确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | done |
| PKG-AMBILIGHT-05 | artplayer-plugin-ambilight
验证新旧核心和组合 | PKG-AMBILIGHT-04, CORE-22 | 原生 video、canvas proxy 的能力边界及销毁无帧循环 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
| PKG-AMBILIGHT-06 | artplayer-plugin-ambilight
验证分发并同步文档 | PKG-AMBILIGHT-05, ENG-07 | ambilight.js 示例及样式/分发验证 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
@@ -479,6 +479,7 @@
- PKG-AMBILIGHT-01: [记录](changes/2026-09-12-PKG-AMBILIGHT-01-contract.md) [记录](baselines/ambilight-contract.md) [记录](baselines/ambilight-release.json) [记录](baselines/ambilight-contract-validation.json)
- PKG-AMBILIGHT-02: [记录](changes/2026-09-12-PKG-AMBILIGHT-02-tests.md) [记录](baselines/ambilight-behavior-validation.json) [记录](ambilight-validation.md)
- PKG-AMBILIGHT-03: [记录](changes/2026-09-12-PKG-AMBILIGHT-03-lifecycle.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-lifecycle-validation.json)
+- PKG-AMBILIGHT-04: [记录](changes/2026-09-12-PKG-AMBILIGHT-04-types.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-types-validation.json)
- PKG-AUDIO-01: [记录](changes/2026-09-12-PKG-AUDIO-01-contract.md) [记录](baselines/audio-track-contract.md) [记录](baselines/audio-track-release.json)
- PKG-AUDIO-02: [记录](changes/2026-09-12-PKG-AUDIO-02-tests.md) [记录](audio-validation.md) [记录](baselines/audio-validation.json)
- PKG-AUDIO-03: [记录](changes/2026-09-12-PKG-AUDIO-03-lifecycle.md) [记录](baselines/audio-lifecycle-validation.json)
diff --git a/refactor/progress.md b/refactor/progress.md
index 8f00dd9f7..83264cce5 100644
--- a/refactor/progress.md
+++ b/refactor/progress.md
@@ -1,5 +1,16 @@
# 进度与证据
+## 最新完成:Ambilight-04 公开类型与两代导出
+
+公共声明补齐可选调用,保留1.1.0的Parameters推导;CJS直接调用与历史.default自别名
+同时可用,ESM/CJS声明身份、旧TS legacy映射、编辑器生成和打包排除已同步。
+仓库外安装两版真实发布及候选,共17个场景(候选7个/历史9个零诊断,另1个历史
+NodeNext ESM场景按4个精确诊断保存),候选各拒绝10项非法用法。四组专项、完整
+CI975项及18项三核心/三引擎浏览器通过,278个生产TS文件严格检查;类型/两代导出
+具体风险关闭。当前220项:88 done、6 doing、126 todo。下一步05代理/设备组合,
+06完整分发/demo仍未完成。见 [实施记录](changes/2026-09-12-PKG-AMBILIGHT-04-types.md)
+和 [验证证据](baselines/ambilight-types-validation.json)。独立本地提交,无推送/发布。
+
## 最新完成:Ambilight-03 TS模块与生命周期修复
生产源码已拆成入口、视图、取色、帧调度4个严格TS模块,修复销毁残留、重入旧帧、
diff --git a/refactor/risk-table.md b/refactor/risk-table.md
index ad5b1bdab..adb53f81f 100644
--- a/refactor/risk-table.md
+++ b/refactor/risk-table.md
@@ -152,5 +152,5 @@
| VAST-LIFE-01 | open / 已复现 | VAST SDK and container lack core destruction ownership and pending work cancellation | PKG-VAST-02, PKG-VAST-03, PKG-VAST-05 |
| VAST-DIST-01 | open / 已复现 | Published VAST CommonJS default namespace differs from the current callable distribution | PKG-VAST-04, PKG-VAST-06 |
| AMBILIGHT-LIFE-01 | open / 已复现 | Ambilight sampling and RAF/DOM ownership lack terminal/error handling | PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-05 |
-| AMBILIGHT-TYPE-01 | open / 源码/产物事实 | Ambilight declarations disagree with optional factory argument and differ across published generations | PKG-AMBILIGHT-04, PKG-AMBILIGHT-06 |
-| AMBILIGHT-DIST-01 | open / 已复现 | Ambilight 1.0.0 CommonJS default namespace differs from callable 1.1.0 | PKG-AMBILIGHT-04, PKG-AMBILIGHT-06 |
+| AMBILIGHT-TYPE-01 | resolved / 已复现 | Ambilight declarations disagree with optional factory argument and differ across published generations | PKG-AMBILIGHT-04, PKG-AMBILIGHT-06 |
+| AMBILIGHT-DIST-01 | resolved / 已复现 | Ambilight 1.0.0 CommonJS default namespace differs from callable 1.1.0 | PKG-AMBILIGHT-04, PKG-AMBILIGHT-06 |
diff --git a/refactor/risks.json b/refactor/risks.json
index 459b5ad20..94dc0494d 100644
--- a/refactor/risks.json
+++ b/refactor/risks.json
@@ -3279,19 +3279,24 @@
{
"id": "AMBILIGHT-TYPE-01",
"title": "Ambilight declarations disagree with optional factory argument and differ across published generations",
- "confirmation": "source-observed",
+ "confirmation": "reproduced",
"owners": [
"PKG-AMBILIGHT-04",
"PKG-AMBILIGHT-06"
],
"compatibleResolution": "Preserve legal old/new calls and ignored zIndex, validate export= and default consumers without narrowing inferred options.",
"closureCriteria": "Installed old/current compiler consumers and documented historical limitations verified.",
- "status": "open",
+ "status": "resolved",
"evidence": [
"refactor/baselines/ambilight-contract.md",
"refactor/baselines/ambilight-release.json",
"refactor/changes/2026-09-12-PKG-AMBILIGHT-01-contract.md"
- ]
+ ],
+ "resolutionEvidence": [
+ "refactor/changes/2026-09-12-PKG-AMBILIGHT-04-types.md",
+ "refactor/baselines/ambilight-types-validation.json"
+ ],
+ "resolutionRationale": "Packed candidate passes seven old/current compiler configurations, including no-interop default imports; optional factory overload preserves published 1.1.0 Parameters input. Both exact published declarations and prior 1.0.0-to-1.1.0 field optionality difference are verified. Candidate corrects the actual 1.1.0 NodeNext ESM failure; this is not full device or distribution acceptance."
},
{
"id": "AMBILIGHT-DIST-01",
@@ -3303,12 +3308,17 @@
],
"compatibleResolution": "Keep callable factory with compatible self default alias.",
"closureCriteria": "Packed root/legacy/global/ESM and old namespace consumers pass.",
- "status": "open",
+ "status": "resolved",
"evidence": [
"refactor/baselines/ambilight-contract.md",
"refactor/baselines/ambilight-release.json",
"refactor/changes/2026-09-12-PKG-AMBILIGHT-01-contract.md"
- ]
+ ],
+ "resolutionEvidence": [
+ "refactor/changes/2026-09-12-PKG-AMBILIGHT-04-types.md",
+ "refactor/baselines/ambilight-types-validation.json"
+ ],
+ "resolutionRationale": "Actual production and installed root/legacy callable factories have self default identity, native ESM default is callable and script globals match. Public default/import= consumers pass. Broader deep-import/demo/package-browser review remains task 06."
}
]
}
diff --git a/refactor/scripts/ambilight-package-types.mjs b/refactor/scripts/ambilight-package-types.mjs
new file mode 100644
index 000000000..b70ab7f5d
--- /dev/null
+++ b/refactor/scripts/ambilight-package-types.mjs
@@ -0,0 +1,103 @@
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import path from 'node:path'
+import process from 'node:process'
+import ts from 'typescript'
+import compat from 'typescript-compat'
+import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
+import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
+import { verifyAmbilightContract } from './ambilight-contract.mjs'
+import { hash, readMember } from './releases.mjs'
+
+async function main() {
+ assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:ambilight-types-package')
+ const yarn = process.env.npm_execpath
+ assert(yarn && fs.existsSync(yarn))
+ const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/ambilight-package-types-'))
+ console.log(`Ambilight isolated package evidence: ${output}`)
+ const contract = await verifyAmbilightContract()
+ const packages = []
+ for (const name of ['artplayer', 'artplayer-plugin-ambilight']) {
+ const archive = path.join(output, `${name}.tgz`)
+ fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
+ const manifest = JSON.parse(readMember(archive, 'package/package.json'))
+ const files = packedFiles(archive)
+ checkFiles(manifest, files, name === contract.baseline.release.name ? Object.keys(contract.baseline.release.files) : [])
+ packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
+ }
+ const matrix = []
+ const core = packages[0]
+ const candidates = [
+ ...[contract.baseline.previous[0], contract.baseline.release].map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
+ { ...packages[1], label: 'candidate' },
+ ]
+ for (const plugin of candidates) {
+ const consumer = consumerDirectory()
+ try {
+ writeJson(path.join(consumer, 'package.json'), { name: 'ambilight-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
+ fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
+ fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
+ const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
+ fs.writeFileSync(path.join(output, `${plugin.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer))
+ assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
+ fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
+ for (const pkg of [core, plugin]) {
+ const root = path.join(consumer, 'node_modules', pkg.name)
+ assert.equal(fs.realpathSync(root), root, 'Installed package must not be a workspace link')
+ for (const [member, expected] of Object.entries(pkg.files))
+ assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
+ }
+ const runtimeFile = path.join(consumer, 'exports.cjs')
+ fs.writeFileSync(runtimeFile, plugin.label === 'candidate'
+ ? `const assert = require('node:assert/strict');
+for (const name of ['artplayer-plugin-ambilight', 'artplayer-plugin-ambilight/legacy']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory(), 'function'); }
+import('artplayer-plugin-ambilight').then(module => { assert.deepEqual(Object.keys(module), ['default']); assert.equal(module.default.default, module.default); }).catch(error => { console.error(error); process.exitCode = 1; });`
+ : `const assert = require('node:assert/strict'); const value = require('artplayer-plugin-ambilight'); assert.equal(typeof value, '${plugin.version === '1.0.0' ? 'object' : 'function'}'); assert.equal(typeof (value.default || value)({}), 'function');`)
+ fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
+ const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
+ if (plugin.label === 'candidate')
+ modes.push([ts, 'node10-commonjs-no-interop'], [compat, 'node10-commonjs-no-interop'])
+ for (const [compiler, mode] of modes) {
+ const next = mode.startsWith('nodenext')
+ const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
+ const source = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/ambilight.ts' : 'refactor/fixtures/consumers/ambilight-published.ts'), 'utf8')
+ const filename = path.join(consumer, `consumer.${extension}`)
+ const historical = path.join(consumer, `historical.${extension}`)
+ fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/ambilight-published.ts'), historical)
+ const files = plugin.label === 'candidate' ? [filename, historical] : [filename]
+ if (mode === 'nodenext-cjs' && plugin.label !== 'published-1.1.0') {
+ const commonjs = path.join(consumer, 'commonjs.cts')
+ fs.writeFileSync(commonjs, `import ambilight = require('artplayer-plugin-ambilight'); ambilight({ blur: '50px', opacity: 0.5, frequency: 10, zIndex: 9, duration: 0.3 }); ${plugin.label === 'candidate' ? 'ambilight.default();' : ''}`)
+ files.push(commonjs)
+ }
+ const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !mode.endsWith('-no-interop'), target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
+ function compile(code) {
+ fs.writeFileSync(filename, code)
+ const program = compiler.createProgram(files, options)
+ for (const file of program.getSourceFiles()) {
+ const actual = fs.realpathSync(file.fileName)
+ assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Types escaped installed consumer: ${actual}`)
+ }
+ return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
+ }
+ const diagnostics = compile(source)
+ const historicalFailure = plugin.label === 'published-1.1.0' && mode === 'nodenext-esm'
+ assert.deepEqual(diagnostics.map(item => item.code), historicalFailure ? [2349, 2349, 2322, 2344] : [], `${plugin.label} ${compiler.version} ${mode}`)
+ const invalid = plugin.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
+ if (plugin.label === 'candidate')
+ assert.equal(invalid.length, 10, 'Installed declarations must reject all invalid uses')
+ matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid })
+ }
+ }
+ finally {
+ removeConsumer(consumer)
+ }
+ }
+ writeJson(path.join(output, 'report.json'), { suite: 'ambilight-isolated-package-types', introducedBy: 'PKG-AMBILIGHT-04', scope: 'Two actual published plugins and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, compiler/default/import= contracts. Published 1.0.0 required option fields became optional in 1.1.0; candidate retains 1.1.0 extraction. This is not proxy, device or complete distribution acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: pkg.sha256 })), matrix })
+ console.log(`Ambilight installed types passed: ${matrix.length} cases; ${output}`)
+}
+
+main().catch((error) => {
+ console.error(error)
+ process.exitCode = 1
+})
diff --git a/refactor/scripts/ambilight-types.test.mjs b/refactor/scripts/ambilight-types.test.mjs
new file mode 100644
index 000000000..fa9993df5
--- /dev/null
+++ b/refactor/scripts/ambilight-types.test.mjs
@@ -0,0 +1,90 @@
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import path from 'node:path'
+// eslint-disable-next-line test/no-import-node-test -- Node baseline runner.
+import test from 'node:test'
+import vm from 'node:vm'
+import { ESLint } from 'eslint'
+import ts from 'typescript'
+import compat from 'typescript-compat'
+import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
+import { checkConsumer } from '../../scripts/typecheck.mjs'
+import { verifyAmbilightContract } from './ambilight-contract.mjs'
+import { readMember } from './releases.mjs'
+
+const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
+
+test('Ambilight current types retain inference, optional runtime arguments and ten invalid-use rejections', () => {
+ const source = fs.readFileSync('test/types/ambilight.ts', 'utf8')
+ for (const [compiler, mode] of modes) {
+ assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
+ assert.equal(checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 10)
+ }
+ assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import ambilight = require('artplayer-plugin-ambilight'); import legacy = require('artplayer-plugin-ambilight/legacy');
+const option: ambilight.Option = {}; ambilight(option); ambilight.default(option); legacy(); legacy.default(option);`), [])
+})
+
+test('Ambilight published calls compile against both exact historical declarations and candidate without new inference widening', async () => {
+ const contract = await verifyAmbilightContract()
+ const source = fs.readFileSync('refactor/fixtures/consumers/ambilight-published.ts', 'utf8')
+ for (const version of ['1.0.0', '1.1.0']) {
+ const old = readMember(contract.archives.get(version), 'package/types/artplayer-plugin-ambilight.d.ts').toString()
+ const required = `declare const input: Parameters[0]; const blur: string = input.blur; const opacity: number = input.opacity; void [blur, opacity];`
+ for (const compiler of [ts, compat]) {
+ const filename = path.resolve('test/types/ambilight-history.ts')
+ const declaration = path.resolve('test/types/ambilight-history.d.ts')
+ function historical(code) {
+ const host = compiler.createCompilerHost({})
+ const original = host.getSourceFile.bind(host)
+ host.getSourceFile = (file, language, ...rest) => path.resolve(file) === filename
+ ? compiler.createSourceFile(file, code.replace('from \'artplayer-plugin-ambilight\'', 'from \'./ambilight-history\''), language, true)
+ : path.resolve(file) === declaration ? compiler.createSourceFile(file, old, language, true) : original(file, language, ...rest)
+ const exists = host.fileExists.bind(host)
+ host.fileExists = file => path.resolve(file) === declaration || exists(file)
+ const program = compiler.createProgram([filename, declaration], { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] }, host)
+ assert(program.getSourceFile(declaration))
+ return compiler.getPreEmitDiagnostics(program).map(item => item.code)
+ }
+ assert.deepEqual(historical(source), [])
+ assert.deepEqual(historical(`${source}\n${required}`), version === '1.0.0' ? [] : [2322, 2322])
+ assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source), [])
+ assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', `${source}\n${required}`).map(item => item.code), [2322, 2322], 'Optional fields were introduced in published 1.1.0; no new difference from that baseline')
+ }
+ }
+})
+
+test('Ambilight editor globals are generated from the public namespace and retain negative checks', async () => {
+ const source = fs.readFileSync('packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.ts', 'utf8')
+ const generated = generatePluginEditorDeclaration(source, 'artplayerPluginAmbilight')
+ const file = 'docs/assets/ts/artplayer-plugin-ambilight.d.ts'
+ const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
+ assert.equal(formatted.errorCount, 0)
+ assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
+ const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
+ const consumer = `const option: artplayerPluginAmbilight.Option = {}; artplayerPluginAmbilight(); artplayerPluginAmbilight.default(option);
+// @ts-expect-error Invalid blur.
+artplayerPluginAmbilight({ blur: 2 });`
+ for (const compiler of [ts, compat]) {
+ assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
+ assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 1)
+ }
+})
+
+test('Ambilight production main, legacy, global and native ESM preserve callable and default identities', async () => {
+ for (const suffix of ['js', 'legacy.js']) {
+ const source = fs.readFileSync(`packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.${suffix}`, 'utf8')
+ const module = { exports: {} }
+ vm.runInNewContext(source, { module, exports: module.exports, window: {} })
+ assert.equal(typeof module.exports, 'function')
+ assert.equal(module.exports.default, module.exports)
+ assert.equal(typeof module.exports.default(), 'function')
+ const global = { window: {} }
+ vm.runInNewContext(source, global)
+ const factory = global.artplayerPluginAmbilight || global.window.artplayerPluginAmbilight
+ assert.equal(factory.default, factory)
+ }
+ const source = fs.readFileSync('packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs', 'utf8')
+ const module = await import(`data:text/javascript,${encodeURIComponent(source)}`)
+ assert.deepEqual(Object.keys(module), ['default'])
+ assert.equal(module.default.default, module.default)
+})
diff --git a/refactor/tasks.json b/refactor/tasks.json
index 19ffa1765..1202806a1 100644
--- a/refactor/tasks.json
+++ b/refactor/tasks.json
@@ -1526,11 +1526,15 @@
"ENG-06",
"CORE-07"
],
- "status": "todo",
+ "status": "done",
"risk": "M",
"deliverable": "canvas 上下文、参数及 start/stop 的明确类型",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
- "evidence": []
+ "evidence": [
+ "changes/2026-09-12-PKG-AMBILIGHT-04-types.md",
+ "ambilight-validation.md",
+ "baselines/ambilight-types-validation.json"
+ ]
},
{
"id": "PKG-AMBILIGHT-05",
diff --git a/scripts/build-ts.js b/scripts/build-ts.js
index faf8fddd4..eef29aee2 100644
--- a/scripts/build-ts.js
+++ b/scripts/build-ts.js
@@ -43,7 +43,7 @@ console.log(`✨ Built ${artplayerTSoutput}`);
const type = pluginsTS[index]
const { name, file } = parsePluginInfo(type)
const source = String(fs.readFileSync(type))
- const semanticPlugin = ['artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds'].includes(name)
+ const semanticPlugin = ['artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight'].includes(name)
const code = semanticPlugin
? generatePluginEditorDeclaration(source, name)
: `${source.replace(reg, '')}\nexport = ${name};\nexport as namespace ${name};\n`
diff --git a/scripts/typecheck.mjs b/scripts/typecheck.mjs
index db34843f2..08b004cb7 100644
--- a/scripts/typecheck.mjs
+++ b/scripts/typecheck.mjs
@@ -76,6 +76,8 @@ export function runTypechecks() {
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/hls-control.ts', 'test/types/dash-control.ts', 'test/types/ads.ts', 'refactor/fixtures/consumers/ads-published.ts', 'refactor/fixtures/consumers/ads-workspace.ts', 'refactor/fixtures/consumers/dash-control-legacy.ts', 'test/types/audio-track.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
}
+ for (const fixture of ['test/types/ambilight.ts', 'refactor/fixtures/consumers/ambilight-published.ts'])
+ assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
if (mode === 'nodenext-cjs')
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, 'test/types/commonjs.cts'), 'utf8')), [], 'CommonJS export assignment consumer failed')
console.log(`Consumers passed: TS ${compiler.version} ${mode}`)
diff --git a/test/types/ambilight.ts b/test/types/ambilight.ts
new file mode 100644
index 000000000..7404b890a
--- /dev/null
+++ b/test/types/ambilight.ts
@@ -0,0 +1,40 @@
+import type { Factory, Option, Result } from 'artplayer-plugin-ambilight'
+import Artplayer from 'artplayer'
+import ambilight from 'artplayer-plugin-ambilight'
+import legacy from 'artplayer-plugin-ambilight/legacy'
+
+const option: Option = { blur: '40px', opacity: 0.6, frequency: 10, duration: 0.2, zIndex: 3 }
+const art = new Artplayer({ container: '#player', url: 'video.mp4', plugins: [ambilight(), ambilight(option), legacy({})] })
+ambilight(undefined)
+ambilight.default(option)
+legacy.default()
+declare const optional: Option | undefined
+ambilight(optional)
+const inferred: Parameters[0] = option
+const blur: string | undefined = inferred.blur
+const factory: Factory = ambilight
+const result: Result = factory(option)(art)
+const name: 'artplayerPluginAmbilight' = result.name
+const calls: void[] = [result.start(), result.stop()]
+const registration: (art: Artplayer) => Result = ambilight()
+// @ts-expect-error Keep required last overload for existing Parameters extraction.
+const missing: Parameters[0] = undefined
+// @ts-expect-error Blur is a CSS string, not a number.
+ambilight({ blur: 50 })
+// @ts-expect-error Opacity remains numeric.
+ambilight({ opacity: '0.5' })
+// @ts-expect-error Frequency remains numeric.
+ambilight({ frequency: '10' })
+// @ts-expect-error Duration remains numeric.
+ambilight({ duration: false })
+// @ts-expect-error The ignored historical zIndex input still has its declared type.
+ambilight({ zIndex: '9' })
+// @ts-expect-error Null was never accepted by the public types.
+ambilight(null)
+// @ts-expect-error Unknown options do not silently enter the contract.
+ambilight({ speed: 10 })
+// @ts-expect-error Methods are synchronous.
+const promise: Promise = result.start()
+// @ts-expect-error No public plugin destroy method is introduced.
+result.destroy()
+void [blur, name, calls, registration, missing, promise]