refactor(ambilight): [PKG-AMBILIGHT-04] preserve published types and dual CommonJS exports

This commit is contained in:
Harvey Zhao committed 2026-09-12 11:50:22 +08:00
1 parent 6caee44dbf
commit 959cccf515
30 files changed
+877 -69

No files matched your search

+32 -17
View File
@@ -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;
+1 -1
View File
@@ -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("<div></div>").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<t.length;r++){if(!n())return;const o=e[r],i=t[r];o&&void 0!==i&&(o.style.backgroundColor=i)}},destroy:function(){try{n.parentNode?.removeChild(n)}finally{e.length=0}}}}(h),o||i.mount(c,{blur:y,opacity:p,duration:m},()=>!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("<div></div>").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<t.length;r++){if(!n())return;const i=e[r],o=t[r];i&&void 0!==o&&(i.style.backgroundColor=o)}},destroy:function(){try{n.parentNode?.removeChild(n)}finally{e.length=0}}}}(h),i||o.mount(c,{blur:y,opacity:p,duration:m},()=>!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})});
@@ -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("<div></div>").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<t.length;r++){if(!n())return;const o=e[r],i=t[r];o&&void 0!==i&&(o.style.backgroundColor=i)}},destroy:function(){var t;try{null==(t=n.parentNode)||t.removeChild(n)}finally{e.length=0}}}}(h),o||i.mount(c,{blur:y,opacity:p,duration:m},()=>!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("<div></div>").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<t.length;r++){if(!n())return;const i=e[r],o=t[r];i&&void 0!==o&&(i.style.backgroundColor=o)}},destroy:function(){var t;try{null==(t=n.parentNode)||t.removeChild(n)}finally{e.length=0}}}}(h),i||o.mount(c,{blur:y,opacity:p,duration:m},()=>!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})});
+5 -4
View File
@@ -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
};
+2 -1
View File
@@ -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": {
@@ -2,4 +2,5 @@ node_modules
.DS_Store
yarn.lock
package-lock.json
src
src
tsconfig.json
@@ -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.
@@ -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)
@@ -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("<div></div>").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<t.length;r++){if(!n())return;const o=e[r],i=t[r];o&&void 0!==i&&(o.style.backgroundColor=i)}},destroy:function(){try{n.parentNode?.removeChild(n)}finally{e.length=0}}}}(h),o||i.mount(c,{blur:y,opacity:p,duration:m},()=>!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("<div></div>").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<t.length;r++){if(!n())return;const i=e[r],o=t[r];i&&void 0!==o&&(i.style.backgroundColor=o)}},destroy:function(){try{n.parentNode?.removeChild(n)}finally{e.length=0}}}}(h),i||o.mount(c,{blur:y,opacity:p,duration:m},()=>!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})});
@@ -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("<div></div>").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<t.length;r++){if(!n())return;const o=e[r],i=t[r];o&&void 0!==i&&(o.style.backgroundColor=i)}},destroy:function(){var t;try{null==(t=n.parentNode)||t.removeChild(n)}finally{e.length=0}}}}(h),o||i.mount(c,{blur:y,opacity:p,duration:m},()=>!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("<div></div>").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<t.length;r++){if(!n())return;const i=e[r],o=t[r];i&&void 0!==o&&(i.style.backgroundColor=o)}},destroy:function(){var t;try{null==(t=n.parentNode)||t.removeChild(n)}finally{e.length=0}}}}(h),i||o.mount(c,{blur:y,opacity:p,duration:m},()=>!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})});
@@ -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
};
@@ -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"
]
}
}
}
@@ -7,7 +7,7 @@ import { createAmbilightView } from './view'
type Option = Parameters<typeof PublicPlugin>[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 })
@@ -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
@@ -0,0 +1,2 @@
export { default } from './artplayer-plugin-ambilight.js'
export type { Callable, Factory, Option, Result } from './artplayer-plugin-ambilight.js'
@@ -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
+24
View File
@@ -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,旧阶段记录保留其当时状态。
@@ -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."
]
}
@@ -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<typeof factory>[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外部脚本例外没有用于本任务。
@@ -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<typeof ambilight>[0] = option
void [name, calls, accepted]
+3 -2
View File
@@ -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<br>核对包契约与历史用法 | BASE-05 | blur/opacity/frequency/duration、start/stop 与挂载样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | done |
| PKG-AMBILIGHT-02 | artplayer-plugin-ambilight<br>建立特有行为与错误测试 | PKG-AMBILIGHT-01, ENG-03, ENG-05 | 跨域 canvas 失败、零尺寸、暂停、重复 start/stop 和 destroy | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | M | done |
| PKG-AMBILIGHT-03 | artplayer-plugin-ambilight<br>整理内部职责与资源 | PKG-AMBILIGHT-02, CORE-03, CORE-12 | 取色计算与绘帧分离,统一 RAF 和 DOM 清理 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | done |
| PKG-AMBILIGHT-04 | artplayer-plugin-ambilight<br>迁移自有源码和公开类型 | PKG-AMBILIGHT-03, ENG-04, ENG-06, CORE-07 | canvas 上下文、参数及 start/stop 的明确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | todo |
| PKG-AMBILIGHT-04 | artplayer-plugin-ambilight<br>迁移自有源码和公开类型 | PKG-AMBILIGHT-03, ENG-04, ENG-06, CORE-07 | canvas 上下文、参数及 start/stop 的明确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | done |
| PKG-AMBILIGHT-05 | artplayer-plugin-ambilight<br>验证新旧核心和组合 | PKG-AMBILIGHT-04, CORE-22 | 原生 video、canvas proxy 的能力边界及销毁无帧循环 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
| PKG-AMBILIGHT-06 | artplayer-plugin-ambilight<br>验证分发并同步文档 | 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)
+11
View File
@@ -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模块,修复销毁残留、重入旧帧、
+2 -2
View File
@@ -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 |
+15 -5
View File
@@ -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."
}
]
}
@@ -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
})
+90
View File
@@ -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<typeof ambilight>[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)
})
+6 -2
View File
@@ -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",
+1 -1
View File
@@ -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`
+2
View File
@@ -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}`)
+40
View File
@@ -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<typeof ambilight>[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<typeof ambilight>[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<void> = result.start()
// @ts-expect-error No public plugin destroy method is introduced.
result.destroy()
void [blur, name, calls, registration, missing, promise]