mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
fix(ambilight): [PKG-AMBILIGHT-PROXY-01] sample canvas output pixels after resizing
This commit is contained in:
1 parent
959cccf515
commit
20f062bc90
19 files changed
+508
-20
No files matched your search
@@ -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",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})});
|
||||
!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="CANVAS"===n.nodeName,c=u?n.width:n.videoWidth,s=u?n.height:n.videoHeight;if(!Number.isFinite(c)||!Number.isFinite(s)||c<=0||s<=0)return null;const f=c/3,a=s/3,d=[];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*f,t*a,f,a,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;d.push(`rgb(${o}, ${u}, ${c})`)}return d}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",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})});
|
||||
!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="CANVAS"===n.nodeName,c=u?n.width:n.videoWidth,s=u?n.height:n.videoHeight;if(!Number.isFinite(c)||!Number.isFinite(s)||c<=0||s<=0)return null;const f=c/3,a=s/3,d=[];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*f,t*a,f,a,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;d.push(`rgb(${o}, ${u}, ${c})`)}return d}catch(h){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})});
|
||||
@@ -4,6 +4,9 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function isCanvas(source) {
|
||||
return source.nodeName === "CANVAS";
|
||||
}
|
||||
function createColorSampler(canvas, video, createCanvas) {
|
||||
let context;
|
||||
try {
|
||||
@@ -20,8 +23,9 @@ function createColorSampler(canvas, video, createCanvas) {
|
||||
function read(active) {
|
||||
if (destroyed || !active())
|
||||
return null;
|
||||
const width = video.videoWidth;
|
||||
const height = video.videoHeight;
|
||||
const canvasOutput = isCanvas(video);
|
||||
const width = canvasOutput ? video.width : video.videoWidth;
|
||||
const height = canvasOutput ? video.height : video.videoHeight;
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
||||
return null;
|
||||
const w = width / 3;
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/ambilight.test.js test/ambilight-lifecycle.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
|
||||
"test:unit": "node --test test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
|
||||
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
@@ -70,7 +70,7 @@
|
||||
"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-proxy.test.js",
|
||||
"test:ambilight-types-package": "node refactor/scripts/ambilight-package-types.mjs"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
|
||||
@@ -32,6 +32,13 @@ canvas could read the recovered same-origin video. Retries remain frequency-gate
|
||||
with a permanently cross-origin source this allocates at most one replacement per
|
||||
eligible sample. No bypass of canvas origin restrictions is attempted.
|
||||
|
||||
Sampling uses the actual drawable pixels: native video's videoWidth/videoHeight,
|
||||
or a canvas proxy's width/height buffer. A proxy can forward the underlying media's
|
||||
dimensions while rendering a differently sized canvas, so never use its forwarded
|
||||
videoWidth/videoHeight to crop the output. The internal sampling source union and
|
||||
nodeName guard also avoid relying on same-window instanceof checks. Zero output
|
||||
dimensions skip sampling until the canvas has a valid buffer again.
|
||||
|
||||
Compatibility constraints: keep numeric style arguments, nine row-major samples,
|
||||
blur/opacity/frequency/duration defaults, synchronous start/stop returns, option
|
||||
reading at registration, and the old frequency comparison (including NaN and
|
||||
@@ -62,3 +69,11 @@ candidate modes with interop disabled, and ten negative uses. Published 1.1.0 fa
|
||||
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.
|
||||
|
||||
`test/ambilight-proxy.test.js` and `test/browser/ambilight-proxy.spec.js` protect
|
||||
resized output geometry. Browser tests play real video with the actual workspace
|
||||
proxy and paint a uniform nine-color palette through its public post-processing
|
||||
callback; getImageData is native. Actual core 5.1.7 has no proxy option and uses
|
||||
VIDEO, so its case checks that capability boundary and native playback. Only 5.4.0
|
||||
and candidate cases validate the canvas proxy combination. These tests do not
|
||||
establish that the unrefactored proxy's own async draw loop is terminally safe.
|
||||
@@ -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",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})});
|
||||
!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="CANVAS"===n.nodeName,c=u?n.width:n.videoWidth,s=u?n.height:n.videoHeight;if(!Number.isFinite(c)||!Number.isFinite(s)||c<=0||s<=0)return null;const f=c/3,a=s/3,d=[];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*f,t*a,f,a,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;d.push(`rgb(${o}, ${u}, ${c})`)}return d}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})});
|
||||
+1
-1
@@ -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",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})});
|
||||
!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="CANVAS"===n.nodeName,c=u?n.width:n.videoWidth,s=u?n.height:n.videoHeight;if(!Number.isFinite(c)||!Number.isFinite(s)||c<=0||s<=0)return null;const f=c/3,a=s/3,d=[];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*f,t*a,f,a,0,0,1,1),i||!l())return null;const[o,u,c]=r.getImageData(0,0,1,1).data;d.push(`rgb(${o}, ${u}, ${c})`)}return d}catch(h){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})});
|
||||
@@ -4,6 +4,9 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function isCanvas(source) {
|
||||
return source.nodeName === "CANVAS";
|
||||
}
|
||||
function createColorSampler(canvas, video, createCanvas) {
|
||||
let context;
|
||||
try {
|
||||
@@ -20,8 +23,9 @@ function createColorSampler(canvas, video, createCanvas) {
|
||||
function read(active) {
|
||||
if (destroyed || !active())
|
||||
return null;
|
||||
const width = video.videoWidth;
|
||||
const height = video.videoHeight;
|
||||
const canvasOutput = isCanvas(video);
|
||||
const width = canvasOutput ? video.width : video.videoWidth;
|
||||
const height = canvasOutput ? video.height : video.videoHeight;
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
||||
return null;
|
||||
const w = width / 3;
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
export function createColorSampler(canvas: HTMLCanvasElement, video: HTMLVideoElement, createCanvas: () => HTMLCanvasElement) {
|
||||
type SamplingSource = HTMLVideoElement | HTMLCanvasElement
|
||||
|
||||
function isCanvas(source: SamplingSource): source is HTMLCanvasElement {
|
||||
return source.nodeName === 'CANVAS'
|
||||
}
|
||||
|
||||
export function createColorSampler(canvas: HTMLCanvasElement, video: SamplingSource, createCanvas: () => HTMLCanvasElement) {
|
||||
let context: CanvasRenderingContext2D | null
|
||||
try {
|
||||
context = canvas.getContext('2d')
|
||||
@@ -16,8 +22,10 @@ export function createColorSampler(canvas: HTMLCanvasElement, video: HTMLVideoEl
|
||||
function read(active: () => boolean): string[] | null {
|
||||
if (destroyed || !active())
|
||||
return null
|
||||
const width = video.videoWidth
|
||||
const height = video.videoHeight
|
||||
// A canvas proxy's output buffer can differ from its forwarded video dimensions.
|
||||
const canvasOutput = isCanvas(video)
|
||||
const width = canvasOutput ? video.width : video.videoWidth
|
||||
const height = canvasOutput ? video.height : video.videoHeight
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
||||
return null
|
||||
const w = width / 3
|
||||
|
||||
@@ -70,3 +70,25 @@ chapter,专项安装命令的存在不代表全部包已加入该门槛。
|
||||
初始失败及明确限制见 [类型验证证据](baselines/ambilight-types-validation.json) 和
|
||||
[实施记录](changes/2026-09-12-PKG-AMBILIGHT-04-types.md)。类型与两代导出具体风险已
|
||||
关闭;当前待做范围为05/06,旧阶段记录保留其当时状态。
|
||||
|
||||
## 05拆出:Canvas输出尺寸
|
||||
|
||||
PKG-AMBILIGHT-PROXY-01从组合检查中独立修复采样边界:Canvas用实际width/height,
|
||||
原生video保留videoWidth/videoHeight。`yarn test:ambilight`加入3项输出resize/零尺寸
|
||||
与正常视频测试;修改前2失败/1通过,候选加历史/契约专项56项通过。
|
||||
|
||||
`yarn test:browser test/browser/ambilight-proxy.spec.js test/browser/ambilight-lifecycle.spec.js`
|
||||
合计27项:6项真正Canvas组合(实际npm5.4.0/候选×三引擎)、3项实际npm5.1.7不支持
|
||||
proxy配置的原生播放能力对照、18项原生生命周期和跨域恢复。Canvas组合使用实际工作区
|
||||
代理源码和真实视频解码,通过公开callback绘制九色均匀区域,用原生getImageData
|
||||
检查九格颜色。不是下载媒体或模拟canvas返回值;这不是实际npm代理归档兼容证明。
|
||||
|
||||
首次把5.1.7也当作代理是错误假设,诊断显示VIDEO节点、代理drawCount=0;旧option
|
||||
声明和真实Artplayer.option都不存在proxy,最终断言明确此能力边界。初始失败及中间
|
||||
诊断保留。本子项不关闭05最终设备/代理生命周期要求;代理自身仍需PKG-CANVAS系列
|
||||
完成后回归,不能从Ambilight视图清理推导代理已无残余绘帧。见
|
||||
[实施记录](changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md)。
|
||||
|
||||
子项最终56项Node专项、27项浏览器及978项CI通过,无skip;证据见
|
||||
[代理采样验证](baselines/ambilight-proxy-validation.json)。05新增PKG-CANVAS-04依赖,
|
||||
等待最终代理实现后再执行整体组合验收,当前保留todo;下一步先核对代理发布契约。
|
||||
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-AMBILIGHT-PROXY-01",
|
||||
"status": "done",
|
||||
"parentCommit": "959cccf5151edafef16310b3ede38c3cd1614e50",
|
||||
"change": "Internal sampling source accepts native video or canvas; canvas buffer width/height replaces forwarded video dimensions for canvas only.",
|
||||
"checks": [
|
||||
{
|
||||
"command": "node --test test/ambilight-proxy.test.js (before source fix)",
|
||||
"result": "expected-negative-control",
|
||||
"tests": 3,
|
||||
"failed": 2,
|
||||
"passedNormalVideo": 1
|
||||
},
|
||||
{
|
||||
"command": "node --test test/ambilight-proxy.test.js test/ambilight-lifecycle.test.js test/ambilight.test.js refactor/scripts/ambilight-contract.test.mjs",
|
||||
"result": "pass",
|
||||
"tests": 56
|
||||
},
|
||||
{
|
||||
"command": "playwright test test/browser/ambilight-proxy.spec.js test/browser/ambilight-lifecycle.spec.js",
|
||||
"result": "pass",
|
||||
"tests": 27,
|
||||
"actualCanvasCombinations": 6,
|
||||
"oldCoreLacksProxyCapabilityObservations": 3,
|
||||
"nativeLifecycleCases": 18,
|
||||
"skipped": 0,
|
||||
"stats": {
|
||||
"startTime": "2026-09-12T03:57:46.543Z",
|
||||
"duration": 33749.014,
|
||||
"expected": 27,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"report": "refactor/.cache/ambilight-proxy01-final-results/report.json",
|
||||
"archivedResults": "refactor/.cache/ambilight-proxy01-final-results/results",
|
||||
"originalResultsPrefix": "refactor/.cache/browser/results",
|
||||
"scope": "actual workspace canvas proxy with native video decode and native Canvas nine-color post-processing callback; 5.4.0/candidate proxy, 5.1.7 native capability boundary, three engines"
|
||||
},
|
||||
{
|
||||
"command": "tsc -p packages/artplayer-plugin-ambilight/tsconfig.json and focused eslint --no-fix",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"command": "yarn build artplayer-plugin-ambilight",
|
||||
"result": "pass",
|
||||
"formats": [
|
||||
"main",
|
||||
"legacy",
|
||||
"esm",
|
||||
"docs/compiled copies"
|
||||
]
|
||||
},
|
||||
{
|
||||
"command": "yarn ci:check",
|
||||
"result": "pass",
|
||||
"tests": {
|
||||
"unit": 881,
|
||||
"engineering": 14,
|
||||
"baseline": 83,
|
||||
"mainTotal": 978,
|
||||
"repeatedContractExecutions": 44
|
||||
},
|
||||
"strictProductionTypeScriptFiles": 278
|
||||
}
|
||||
],
|
||||
"failedIterations": [
|
||||
{
|
||||
"report": "refactor/.cache/ambilight-proxy01-before-results/report.json",
|
||||
"scope": "Initial Chromium: two actual proxy color failures, one unsupported 5.1.7 proxy assumption"
|
||||
},
|
||||
{
|
||||
"report": "refactor/.cache/ambilight-proxy01-first-fixed-results/report.json",
|
||||
"scope": "Six actual proxy combinations pass, three unsupported 5.1.7 cases fail before draw"
|
||||
},
|
||||
{
|
||||
"report": "refactor/.cache/ambilight-proxy01-capability-probe-results/report.json",
|
||||
"scope": "Ready wait does not change old core VIDEO host; 24 pass, 3 unsupported-proxy failures; state attachments identify native host"
|
||||
},
|
||||
{
|
||||
"log": "refactor/.cache/ambilight-proxy01-ci.log",
|
||||
"scope": "New prerequisite left parent incorrectly doing; plan guard rejected unfinished dependency. Parent now todo until Canvas migration and this child are complete."
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ambilight/src/sampler.ts",
|
||||
"sha256LF": "e6f4ea2795b500aa2ac8ffe4b65da4269e8b3457a9912808537bdacfbe9f5328"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ambilight/src/index.ts",
|
||||
"sha256LF": "9db25144798ee667bc420b3e1f0b00ef6855185d26cefca45e05271354bf8591"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js",
|
||||
"sha256LF": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js",
|
||||
"sha256LF": "c3ed1a54e44a0202811d70378cb19edf87b1ebd010d05fe96ebe64d0b4301d5d"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs",
|
||||
"sha256LF": "ef126959819c4973da27dbb1e60ccccc54386c23f8c7750ce1ba4d38f306aaaa"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/src/index.js",
|
||||
"sha256LF": "4b5922b05b46adf0d54ae92025b5c70456a5b8c87819c797ef91eb50e0c8f4d4"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/package.json",
|
||||
"sha256LF": "4df3a0098730cfc12232fc57066fd1f7159bbd41dce87904fa51f39b82252eed"
|
||||
},
|
||||
{
|
||||
"file": "test/ambilight-proxy.test.js",
|
||||
"sha256LF": "f284ea3069d66c36b62756cef468701c6fbc91cd17343843715d59b03779b928"
|
||||
},
|
||||
{
|
||||
"file": "test/browser/ambilight-proxy.spec.js",
|
||||
"sha256LF": "048d6b12dd26d174e978fcc68abed47df17ff232cb351583e4785ebbae6e5122"
|
||||
},
|
||||
{
|
||||
"file": "test/browser/ambilight-lifecycle.spec.js",
|
||||
"sha256LF": "321693a24d33f0c7aee02fd4137b55175386562a645ba138e15c3a51d6348d2c"
|
||||
},
|
||||
{
|
||||
"file": "test/browser/server.mjs",
|
||||
"sha256LF": "af513e1e64f875668b6a7cbd060276d85d930e0cc1715577e97907cc26c3b58d"
|
||||
},
|
||||
{
|
||||
"file": "test/helpers/ambilight.js",
|
||||
"sha256LF": "8cb80dba5270860afc121e3a02336a6cdb20b0a6a41e058cde11efe9174fba4f"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/vast-core.json",
|
||||
"sha256LF": "bb703cf37591211f78719665aed0e0cb2524ec5618dad48e6b8e2dbfef132b8e"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/releases.json",
|
||||
"sha256LF": "56054ecd0b861c91a14bf83fcb4243ab6f5ddb27004ad8eb98901c1f85f9b426"
|
||||
},
|
||||
{
|
||||
"file": "package.json",
|
||||
"sha256LF": "a94c6ded6fecef73e84932eb1f7c31de42e1ca02814fcd69c807f50c52fc8924"
|
||||
}
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"file": "refactor/.cache/ambilight05-proxy-before.log",
|
||||
"sha256": "02856164dc6566f05aa2336159f06352130356a86b046774acef09c8a4017090"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-node-before.log",
|
||||
"sha256": "c43e7fd44d1227028443339300fe940c7d2e483aa6bd6740f4f084b40da9ca73"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-unit.log",
|
||||
"sha256": "dbed1c54e564b2cdbfd587eb46961bc3288eef4c249943c94e5196f9d678d496"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-browser.log",
|
||||
"sha256": "68ec5effee410003f046d78bd9fb22858a5fd999f4328c0bd26e92206dec312a"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-browser-final.log",
|
||||
"sha256": "f5ef93ef517b1f1d66152699db77734d318d4a970d5d8f8259004c494e414bc0"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-browser-corrected.log",
|
||||
"sha256": "b3702456e39ed6e8be3504a123065d4004b8e9f17ceaf6bbf53c124dd42e98da"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-build.log",
|
||||
"sha256": "74a43735bbd36337dc218a3990b9f3571bdb8cf9b1115782ff325a0b4a6530e4"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-lint-final.log",
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-ci.log",
|
||||
"sha256": "312049cd0a59b220249a033eef5589d22d9755b9c06b6988e89d4828742eb49f"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ambilight-proxy01-ci-final.log",
|
||||
"sha256": "306ccefd21e8d8d860e9b576f8b6aa00cb057dde86c21a0b583e417584aa2b16"
|
||||
}
|
||||
],
|
||||
"limitations": [
|
||||
"The actual npm core 5.1.7 option declaration and runtime option object lack proxy. It is not counted as a canvas combination.",
|
||||
"Proxy source is current workspace JS, not an immutable npm proxy release. Freeze proxy history in PKG-CANVAS-01.",
|
||||
"Proxy-owned async bitmap/RAF/terminal semantics remain unrefactored and are not validated by Ambilight grid removal.",
|
||||
"PKG-AMBILIGHT-05 depends on PKG-CANVAS-04 and remains todo for final integration/device acceptance; task 06 remains todo.",
|
||||
"Only formatting line breaks changed in the browser test after its final successful run; focused lint passed afterward. Candidate and proxy executed bundle bytes are recorded in report attachments.",
|
||||
"No public type or export changes, no dependency installation, push, version bump or publication."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# PKG-AMBILIGHT-PROXY-01 Canvas输出取色边界
|
||||
|
||||
从05组合检查中拆出可独立验证的源码修复;05仍需最终代理迁移后的组合和设备证据。
|
||||
当前实际proxy-canvas源码由本步输入哈希固定,未把工作区代理版本当作npm历史包。
|
||||
|
||||
## 复现与修改
|
||||
|
||||
原生canvas proxy在输出buffer改变尺寸后仍转发底层videoWidth/videoHeight。Ambilight
|
||||
原实现按转发媒体尺寸截取canvas,导致九宫格采样偏向左上角;浏览器真实视频解码后
|
||||
通过代理公开callback绘制九色native Canvas图案,以均匀颜色直接验证各区域取色,
|
||||
不mock像素读取、代理工厂或媒体播放。初始Chromium中5.4.0/候选核心均取错颜色。
|
||||
|
||||
采样模块内部来源类型明确为HTMLVideoElement或HTMLCanvasElement;根据nodeName
|
||||
区分Canvas输出,使用其width/height。原生video仍使用videoWidth/videoHeight,显示
|
||||
宽高不影响取色。保留频率/生命周期/公共Option/Result及所有导出;Canvas零输出
|
||||
尺寸不采样,恢复尺寸后可继续。不使用跨window容易失效的instanceof判断。
|
||||
|
||||
3项Node用例在修改前2失败/1正常视频通过,修改后与既有回归共56项通过。覆盖输出
|
||||
二次resize、原生视频显示尺寸与零Canvas输出恢复。完整三格式构建、专项lint/严格
|
||||
TS以及完整CI978项通过(881单元+14工程+83基线),另44项重复契约观察。
|
||||
|
||||
## 纠正历史能力假设
|
||||
|
||||
最初把5.1.7也当作canvas组合,读取不到代理draw回调;增加ready等待仍失败。
|
||||
诊断附件明确实际节点为VIDEO,而不是CANVAS。核对实际npm5.1.7的option声明和
|
||||
浏览器Artplayer.option均没有proxy配置,证明旧核心直接忽略该选项。这不是Canvas
|
||||
取色修复失败,也不能声称对该核心验证了代理。最终用单独能力断言记录该历史限制,
|
||||
同时确认原生Ambilight仍可播放取色;不会向旧核心注入虚构代理支持。
|
||||
|
||||
最终浏览器矩阵包含6项真正canvas组合(5.4.0/候选×三引擎)、3项5.1.7无proxy配置
|
||||
但原生播放的能力对照、18项既有原生视频生命周期/跨域恢复。失败与通过报告分开归档。
|
||||
|
||||
Canvas代理本身仍是未迁移代码,其异步绘帧/终止/事件归属继续由PKG-CANVAS-01至06
|
||||
核验;本步不能据“移除了Ambilight DOM”推断代理已无残余RAF。05增加PKG-CANVAS-04
|
||||
前置依赖,在依赖满足前保留todo并记录预检证据;06完整分发/demo也未完成。本子任务
|
||||
完成后独立本地commit,无推送/发布。下一步推进PKG-CANVAS-01发布契约,随后迁移代理。
|
||||
|
||||
## 验证收尾
|
||||
|
||||
最终27项浏览器全部通过,无skip;56项Node专项通过,278个生产TS文件严格检查。
|
||||
报告、输入哈希、初始颜色错误/中间能力诊断和完整CI日志见
|
||||
[验证证据](../baselines/ambilight-proxy-validation.json)。期间计划检查正确拒绝了新依赖
|
||||
未完成却将父任务标doing的状态;已改为依赖满足前todo,再完成本子任务。格式lint
|
||||
修正仅将对象字段换行,未修改已经通过的浏览器测试含义。
|
||||
+8
-5
@@ -2,9 +2,9 @@
|
||||
|
||||
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 220 项,范围 22 个包及工作区/示例。
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 221 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 126 / doing 6 / blocked 0 / done 88 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 126 / doing 6 / blocked 0 / done 89 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| --- | --- | --- |
|
||||
| artplayer | 5.4.1 | CORE-01, CORE-02, CORE-03, CORE-04, CORE-05, CORE-06, CORE-07, CORE-08, CORE-09, CORE-10, CORE-11, CORE-12, CORE-13, CORE-14, CORE-15, CORE-16, CORE-17, CORE-18, CORE-19, CORE-20, CORE-21, CORE-23, CORE-22, CORE-24 |
|
||||
| artplayer-plugin-ads | 2.1.0 | PKG-ADS-01, PKG-ADS-02, PKG-ADS-03, PKG-ADS-04, PKG-ADS-05, PKG-ADS-06 |
|
||||
| artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06 |
|
||||
| artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06 |
|
||||
| artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06 |
|
||||
| artplayer-plugin-audio-track | 1.1.0 | PKG-AUDIO-01, PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-04, PKG-AUDIO-05, PKG-AUDIO-06 |
|
||||
| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06 |
|
||||
@@ -31,7 +31,7 @@
|
||||
| artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06 |
|
||||
| artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06 |
|
||||
| artplayer-plugin-vtt-thumbnail | 1.1.0 | PKG-VTT-THUMB-01, PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 |
|
||||
| artplayer-proxy-canvas | 1.1.0 | PKG-CANVAS-01, PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-04, PKG-CANVAS-05, PKG-CANVAS-06 |
|
||||
| artplayer-proxy-canvas | 1.1.0 | PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-01, PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-04, PKG-CANVAS-05, PKG-CANVAS-06 |
|
||||
| artplayer-proxy-mediabunny | 1.2.0 | PKG-MB-01, PKG-MB-02, PKG-MB-03, PKG-MB-04, PKG-MB-05, PKG-MB-06, PKG-MB-07, PKG-MB-08, PKG-MB-09, PKG-MB-10 |
|
||||
| artplayer-tool-iframe | 1.1.0 | PKG-IFRAME-01, PKG-IFRAME-02, PKG-IFRAME-03, PKG-IFRAME-04, PKG-IFRAME-05, PKG-IFRAME-06 |
|
||||
| artplayer-tool-thumbnail | 4.4.0 | PKG-TOOL-THUMB-01, PKG-TOOL-THUMB-02, PKG-TOOL-THUMB-03, PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-05, PKG-TOOL-THUMB-06 |
|
||||
@@ -152,7 +152,8 @@
|
||||
| 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 | done |
|
||||
| PKG-AMBILIGHT-05 | artplayer-plugin-ambilight<br>验证新旧核心和组合 | PKG-AMBILIGHT-04, CORE-22 | 原生 video、canvas proxy 的能力边界及销毁无帧循环 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
|
||||
| PKG-AMBILIGHT-PROXY-01 | artplayer-plugin-ambilight, artplayer-proxy-canvas<br>修复Canvas代理输出尺寸与取色区域 | PKG-AMBILIGHT-04, CORE-22 | 按Canvas实际输出buffer取色,原生video保留intrinsic尺寸,明确5.1.7没有proxy配置 | 旧错误复现、三引擎实际代理九色区域对照及原生视频回归通过;不把本子项当作最终代理/设备验收 | M | done |
|
||||
| PKG-AMBILIGHT-05 | artplayer-plugin-ambilight<br>验证新旧核心和组合 | PKG-AMBILIGHT-04, CORE-22, PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-04 | 原生 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 |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-audio-track
|
||||
@@ -480,6 +481,8 @@
|
||||
- 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-AMBILIGHT-PROXY-01: [记录](changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-proxy-validation.json)
|
||||
- PKG-AMBILIGHT-05: [记录](changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md) [记录](ambilight-validation.md)
|
||||
- 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)
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:Ambilight-PROXY-01 Canvas输出区域修复
|
||||
|
||||
组合测试复现Canvas缩放后仍按底层视频尺寸截取而取错九格颜色;内部明确video/canvas
|
||||
来源类型,Canvas采用输出buffer尺寸,原生video保留intrinsic尺寸。3项新Node测试
|
||||
修改前2失败/1正常通过,专项56项、最终27项浏览器及978项完整CI通过。浏览器实际
|
||||
覆盖6项Canvas组合、3项5.1.7没有proxy配置的原生能力对照、18项既有生命周期回归,
|
||||
没有把未启用代理算作组合通过。新增独立子任务,当前221项:89 done、6 doing、
|
||||
126 todo。05增加PKG-CANVAS-04依赖,代理自身仍未迁移;下一步PKG-CANVAS-01核对
|
||||
真实发布和历史契约,再推进TS迁移。见 [实施记录](changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md)
|
||||
和 [验证证据](baselines/ambilight-proxy-validation.json)。独立本地提交,无推送/发布。
|
||||
|
||||
## 最新完成:Ambilight-04 公开类型与两代导出
|
||||
|
||||
公共声明补齐可选调用,保留1.1.0的Parameters推导;CJS直接调用与历史.default自别名
|
||||
|
||||
@@ -154,3 +154,4 @@
|
||||
| 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 | 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 |
|
||||
| AMBILIGHT-PROXY-01 | resolved / 已复现 | Canvas proxy output pixels differ from forwarded video dimensions | PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05 |
|
||||
@@ -3319,6 +3319,26 @@
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"id": "AMBILIGHT-PROXY-01",
|
||||
"title": "Canvas proxy output pixels differ from forwarded video dimensions",
|
||||
"confirmation": "reproduced",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-AMBILIGHT-PROXY-01",
|
||||
"PKG-AMBILIGHT-05"
|
||||
],
|
||||
"compatibleResolution": "Sample Canvas width/height while native video retains intrinsic videoWidth/videoHeight; preserve public calls and lifecycle.",
|
||||
"closureCriteria": "Node resize/zero-size/normal-video and actual supported-core canvas palette comparisons pass across three engines.",
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md"
|
||||
],
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md",
|
||||
"refactor/baselines/ambilight-proxy-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Node output resize/zero-size and normal video tests pass; actual 5.4.0/candidate canvas proxy nine-color regions pass across three engines. Actual 5.1.7 lacks proxy option and is recorded as a native capability observation, not a canvas pass. Proxy-owned lifecycle and final device acceptance remain tasks CANVAS and AMBILIGHT-05."
|
||||
}
|
||||
]
|
||||
}
|
||||
+29
-2
@@ -1536,6 +1536,28 @@
|
||||
"baselines/ambilight-types-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-AMBILIGHT-PROXY-01",
|
||||
"phase": "5 包迁移:artplayer-plugin-ambilight",
|
||||
"title": "修复Canvas代理输出尺寸与取色区域",
|
||||
"scope": [
|
||||
"artplayer-plugin-ambilight",
|
||||
"artplayer-proxy-canvas"
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-AMBILIGHT-04",
|
||||
"CORE-22"
|
||||
],
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "按Canvas实际输出buffer取色,原生video保留intrinsic尺寸,明确5.1.7没有proxy配置",
|
||||
"acceptance": "旧错误复现、三引擎实际代理九色区域对照及原生视频回归通过;不把本子项当作最终代理/设备验收",
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md",
|
||||
"ambilight-validation.md",
|
||||
"baselines/ambilight-proxy-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-AMBILIGHT-05",
|
||||
"phase": "5 包迁移:artplayer-plugin-ambilight",
|
||||
@@ -1545,13 +1567,18 @@
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-AMBILIGHT-04",
|
||||
"CORE-22"
|
||||
"CORE-22",
|
||||
"PKG-AMBILIGHT-PROXY-01",
|
||||
"PKG-CANVAS-04"
|
||||
],
|
||||
"status": "todo",
|
||||
"risk": "M",
|
||||
"deliverable": "原生 video、canvas proxy 的能力边界及销毁无帧循环",
|
||||
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md",
|
||||
"ambilight-validation.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-AMBILIGHT-06",
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import assert from 'node:assert/strict'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Node runtime contract runner.
|
||||
import test from 'node:test'
|
||||
import { ambilightCandidate, ambilightEnvironment } from './helpers/ambilight.js'
|
||||
|
||||
const implementation = await ambilightCandidate()
|
||||
|
||||
test('Ambilight canvas proxy sampling follows output pixels and subsequent output resize', async () => {
|
||||
const env = await ambilightEnvironment(implementation)
|
||||
env.video.nodeName = 'CANVAS'
|
||||
env.video.width = 600
|
||||
env.video.height = 300
|
||||
env.art.playing = true
|
||||
env.time(100)
|
||||
const result = env.factory()(env.art)
|
||||
result.start()
|
||||
assert.equal(env.draws.length, 9)
|
||||
assert.deepEqual(env.draws[0].slice(1), [0, 0, 200, 100, 0, 0, 1, 1])
|
||||
assert.deepEqual(env.draws[8].slice(1), [400, 200, 200, 100, 0, 0, 1, 1])
|
||||
env.video.width = 300
|
||||
env.video.height = 600
|
||||
env.frame(200)
|
||||
assert.deepEqual(env.draws[17].slice(1), [200, 400, 100, 200, 0, 0, 1, 1])
|
||||
env.art.destroy()
|
||||
})
|
||||
|
||||
test('Ambilight native video uses intrinsic pixels regardless of its display width and height', async () => {
|
||||
const env = await ambilightEnvironment(implementation)
|
||||
env.video.nodeName = 'VIDEO'
|
||||
env.video.width = 600
|
||||
env.video.height = 300
|
||||
env.art.playing = true
|
||||
env.time(100)
|
||||
env.factory()(env.art).start()
|
||||
assert.deepEqual(env.draws[8].slice(1), [200, 100, 100, 50, 0, 0, 1, 1])
|
||||
env.art.destroy()
|
||||
})
|
||||
|
||||
test('Ambilight zero-sized canvas output skips sampling despite nonzero proxy media dimensions and recovers on resize', async () => {
|
||||
const env = await ambilightEnvironment(implementation)
|
||||
env.video.nodeName = 'CANVAS'
|
||||
env.video.width = 0
|
||||
env.video.height = 300
|
||||
env.art.playing = true
|
||||
env.time(100)
|
||||
env.factory()(env.art).start()
|
||||
assert.equal(env.draws.length, 0)
|
||||
env.video.width = 600
|
||||
env.frame(200)
|
||||
assert.equal(env.draws.length, 9)
|
||||
env.art.destroy()
|
||||
assert.equal(env.frames.size, 0)
|
||||
})
|
||||
@@ -0,0 +1,78 @@
|
||||
import fs from 'node:fs'
|
||||
import { transform } from 'esbuild'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { ambilightCandidate } from '../helpers/ambilight.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementation = await ambilightCandidate()
|
||||
const proxySource = fs.readFileSync('packages/artplayer-proxy-canvas/src/index.js', 'utf8')
|
||||
const proxy = (await transform(proxySource, { format: 'cjs', target: 'es2020' })).code
|
||||
|
||||
for (const core of ['published-5.1.7', 'published', 'candidate']) {
|
||||
const scenario = core === 'published-5.1.7' ? 'historical core has no proxy option and retains native Ambilight playback' : 'Ambilight samples all nine canvas proxy regions after output resizing'
|
||||
test(`${core}: ${scenario}`, async ({ page }, testInfo) => {
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
for (const [name, source] of [['ambilightFactory', implementation.source], ['canvasProxy', proxy]])
|
||||
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${source}; window.${name} = module.exports.default || module.exports; })();` })
|
||||
await page.evaluate(() => {
|
||||
window.palette = ['rgb(255, 0, 0)', 'rgb(0, 255, 0)', 'rgb(0, 0, 255)', 'rgb(255, 255, 0)', 'rgb(0, 255, 255)', 'rgb(255, 0, 255)', 'rgb(120, 0, 0)', 'rgb(0, 120, 0)', 'rgb(0, 0, 120)']
|
||||
window.drawCount = 0
|
||||
window.proxyErrors = []
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '/test/pattern.mp4',
|
||||
muted: true,
|
||||
proxy: window.canvasProxy((context, video) => {
|
||||
window.proxyVideo = video
|
||||
// Exercise the proxy's public post-processing callback with native canvas.
|
||||
const canvas = context.canvas
|
||||
for (let index = 0; index < 9; index++) {
|
||||
context.fillStyle = window.palette[index]
|
||||
context.fillRect(index % 3 * canvas.width / 3, Math.floor(index / 3) * canvas.height / 3, canvas.width / 3, canvas.height / 3)
|
||||
}
|
||||
window.drawCount++
|
||||
}),
|
||||
plugins: [window.ambilightFactory({ frequency: Infinity })],
|
||||
})
|
||||
window.art.on('artplayerProxyCanvas:error', error => window.proxyErrors.push({ name: error.name, message: error.message }))
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.click('#play')
|
||||
if (core === 'published-5.1.7') {
|
||||
expect(await page.evaluate(() => 'proxy' in window.Artplayer.option)).toBe(false)
|
||||
expect(await page.evaluate(() => window.art.template.$video.nodeName)).toBe('VIDEO')
|
||||
await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('.artplayer-plugin-ambilight > div')].filter(node => /^rgb\(/.test(node.style.backgroundColor)).length)).toBe(9)
|
||||
expect(await page.evaluate(() => window.drawCount)).toBe(0)
|
||||
await testInfo.attach('ambilight-proxy-capability', { contentType: 'application/json', body: JSON.stringify({ core, proxyOption: false, actualHost: 'VIDEO', plugin: hash(implementation.source), proxy: hash(proxySource), scope: 'unsupported proxy option is ignored by this actual historical core; native plugin playback only' }) })
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
return
|
||||
}
|
||||
expect(await page.evaluate(() => 'proxy' in window.Artplayer.option)).toBe(true)
|
||||
await expect.poll(() => page.evaluate(() => window.drawCount)).toBeGreaterThan(0)
|
||||
await expect.poll(() => page.evaluate(() => window.proxyVideo.currentTime)).toBeGreaterThan(0)
|
||||
await page.evaluate(() => {
|
||||
window.art.template.$video.width = 600
|
||||
window.art.template.$video.height = 300
|
||||
window.drawCount = 0
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.drawCount)).toBeGreaterThan(0)
|
||||
const dimensions = await page.evaluate(() => ({ nodeName: window.art.template.$video.nodeName, canvas: [window.art.template.$video.width, window.art.template.$video.height], video: [window.proxyVideo.videoWidth, window.proxyVideo.videoHeight] }))
|
||||
expect(dimensions.nodeName).toBe('CANVAS')
|
||||
expect(dimensions.canvas).not.toEqual(dimensions.video)
|
||||
await testInfo.attach('ambilight-proxy-inputs', { contentType: 'application/json', body: JSON.stringify({ core, dimensions, plugin: hash(implementation.source), proxy: hash(proxySource), media: '/test/pattern.mp4', scope: 'real video decode and proxy post-processing callback paints a nine-cell palette using native Canvas' }) })
|
||||
await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('.artplayer-plugin-ambilight > div')].map(node => node.style.backgroundColor))).toEqual(await page.evaluate(() => window.palette))
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
expect(await page.locator('.artplayer-plugin-ambilight').count()).toBe(0)
|
||||
})
|
||||
}
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await testInfo.attach('ambilight-proxy-state', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(() => ({ ready: window.art?.isReady, nodeName: window.art?.template.$video.nodeName, readyState: window.art?.template.$video.readyState, paused: window.art?.template.$video.paused, currentTime: window.art?.template.$video.currentTime, drawCount: window.drawCount, errors: window.proxyErrors }))) })
|
||||
await page.evaluate(() => {
|
||||
window.art?.plugins.artplayerPluginAmbilight?.stop()
|
||||
window.proxyVideo?.pause()
|
||||
if (window.art && !window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user