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]