From 876fa4c32882c8ae27ed15d1bc8507ae75d55eae Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Tue, 15 Sep 2026 19:16:22 +0800 Subject: [PATCH] fix(auto-thumbnail): [PKG-AUTO-THUMB-13] ignore registration after destroy --- .../artplayer-plugin-auto-thumbnail.js | 2 +- .../artplayer-plugin-auto-thumbnail.legacy.js | 2 +- .../artplayer-plugin-auto-thumbnail.mjs | 2 + package.json | 4 +- .../ARCHITECTURE.md | 14 + .../artplayer-plugin-auto-thumbnail/README.md | 3 + .../dist/artplayer-plugin-auto-thumbnail.js | 2 +- .../artplayer-plugin-auto-thumbnail.legacy.js | 2 +- .../dist/artplayer-plugin-auto-thumbnail.mjs | 2 + .../src/index.ts | 2 + .../src/types.ts | 1 + ...uto-thumbnail-registration-validation.json | 3824 +++++++++++++++++ ...26-09-15-PKG-AUTO-THUMB-13-registration.md | 68 + refactor/plan.md | 10 +- refactor/progress.md | 14 + refactor/risk-table.md | 1 + refactor/risks.json | 21 + refactor/tasks.json | 23 +- scripts/browser-validation/scope.ts | 1 + test/auto-thumbnail-registration.test.js | 50 + test/browser/README.md | 8 + .../auto-thumbnail-registration.spec.js | 39 + 22 files changed, 4084 insertions(+), 11 deletions(-) create mode 100644 refactor/baselines/auto-thumbnail-registration-validation.json create mode 100644 refactor/changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md create mode 100644 test/auto-thumbnail-registration.test.js create mode 100644 test/browser/auto-thumbnail-registration.spec.js diff --git a/docs/compiled/artplayer-plugin-auto-thumbnail.js b/docs/compiled/artplayer-plugin-auto-thumbnail.js index 5d5c3359c..775593a8e 100644 --- a/docs/compiled/artplayer-plugin-auto-thumbnail.js +++ b/docs/compiled/artplayer-plugin-auto-thumbnail.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const i=e([()=>{n.onloadeddata=null},()=>{n.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&n.cancelVideoFrameCallback(t.frame)}]);if(i.failed)throw i.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!i,retries:0,sequence:0,started:!1};r=u;const d=()=>t.active()&&r===u,c=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),l=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,c())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");l()}else u.sought=!0,c()}),l())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?s():n.onloadeddata=s)}}function n(e,n){const i=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const o=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(o),i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{o(),i.onloadedmetadata=null;const r=i.duration,a=i.videoHeight,u=i.videoWidth,{height:d,canvasWidth:c,canvasHeight:l}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),i=Math.trunc(10*e.width),r=Math.trunc(n*Math.ceil(e.number/10));if(![n,i,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:i,canvasHeight:r}}(n,{duration:r,videoHeight:a,videoWidth:u});if(!e.active())return;i.width=u,i.height=a,i.style.width=`${u}px`,i.style.height=`${a}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=c,!e.active())return;if(s.height=l,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const h=t(e,i),m=function(e,t){let n;function i(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(i),r=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(i(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=n.number?e.dispose():h(r*v/n.number,e.guard(()=>{f.drawImage(i,v%10*n.width,Math.floor(v/10)*d,n.width,d),e.active()&&m(t=>{e.publish(t,{height:d,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?i.src=n.url:o())}function i(t){return async i=>{const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,n){let i,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=i;i=void 0,e?.dispose()}return{start:function(){if(o)return;const d=i,c=[];let l=!1;const s={active:()=>!o&&!l&&i===s,own(t){if(l){const i=e([t]);i.failed&&n(i.failure)}else c.push(t)},dispose(){if(l)return;l=!0,i===s&&(i=void 0);const t=e(c.splice(0).reverse());t.failed&&n(t.failure)},fail(e){s.active()&&(s.dispose(),n(e))},guard:e=>(...t)=>{if(s.active())try{return e(...t)}catch(n){s.fail(n)}},publish(i,o){if(!s.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!s.active())return void u(d);const c=r;r=d;try{t({url:d,...o})}catch(l){r===d&&(r=void 0!==c&&a.has(c)?c:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),l}u(c)}};return i=s,d?.dispose(),s},cancel:d,destroy:function(){if(o)return;o=!0;const t=e([d,...[...a].map(e=>()=>u(e))]);r=void 0,t.failed&&n(t.failure)},get closed(){return o}}}(e=>{i.thumbnails=e},r),a=[],u=()=>{const e=o.start();e&&e.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(t,()=>i.option.url);e.active()&&n(e,r)})()},d=()=>{const t=a.splice(0),n=e([o.destroy,...t]);n.failed&&r(n.failure)};try{const e=[["destroy",d],["restart",o.cancel],["video:loadedmetadata",u]];for(const[t,n]of e){if(o.closed)break;a.push(()=>i.off(t,n)),i.on(t,n),o.closed&&i.off(t,n)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}}}return i.default=i,i}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const i=e([()=>{n.onloadeddata=null},()=>{n.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&n.cancelVideoFrameCallback(t.frame)}]);if(i.failed)throw i.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!i,retries:0,sequence:0,started:!1};r=u;const d=()=>t.active()&&r===u,l=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,l()}),c())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?s():n.onloadeddata=s)}}function n(e,n){const i=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const o=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(o),i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{o(),i.onloadedmetadata=null;const r=i.duration,a=i.videoHeight,u=i.videoWidth,{height:d,canvasWidth:l,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),i=Math.trunc(10*e.width),r=Math.trunc(n*Math.ceil(e.number/10));if(![n,i,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:i,canvasHeight:r}}(n,{duration:r,videoHeight:a,videoWidth:u});if(!e.active())return;i.width=u,i.height=a,i.style.width=`${u}px`,i.style.height=`${a}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=l,!e.active())return;if(s.height=c,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const m=t(e,i),h=function(e,t){let n;function i(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(i),r=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(i(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=n.number?e.dispose():m(r*v/n.number,e.guard(()=>{f.drawImage(i,v%10*n.width,Math.floor(v/10)*d,n.width,d),e.active()&&h(t=>{e.publish(t,{height:d,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?i.src=n.url:o())}function i(t){return async i=>{if(i.isDestroy)return{name:"artplayerPluginAutoThumbnail"};const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,n){let i,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=i;i=void 0,e?.dispose()}return{start:function(){if(o)return;const d=i,l=[];let c=!1;const s={active:()=>!o&&!c&&i===s,own(t){if(c){const i=e([t]);i.failed&&n(i.failure)}else l.push(t)},dispose(){if(c)return;c=!0,i===s&&(i=void 0);const t=e(l.splice(0).reverse());t.failed&&n(t.failure)},fail(e){s.active()&&(s.dispose(),n(e))},guard:e=>(...t)=>{if(s.active())try{return e(...t)}catch(n){s.fail(n)}},publish(i,o){if(!s.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!s.active())return void u(d);const l=r;r=d;try{t({url:d,...o})}catch(c){r===d&&(r=void 0!==l&&a.has(l)?l:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),c}u(l)}};return i=s,d?.dispose(),s},cancel:d,destroy:function(){if(o)return;o=!0;const t=e([d,...[...a].map(e=>()=>u(e))]);r=void 0,t.failed&&n(t.failure)},get closed(){return o}}}(e=>{i.thumbnails=e},r),a=[],u=()=>{const e=o.start();e&&e.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(t,()=>i.option.url);e.active()&&n(e,r)})()},d=()=>{const t=a.splice(0),n=e([o.destroy,...t]);n.failed&&r(n.failure)};try{const e=[["destroy",d],["restart",o.cancel],["video:loadedmetadata",u]];for(const[t,n]of e){if(o.closed)break;a.push(()=>i.off(t,n)),i.on(t,n),o.closed&&i.off(t,n)}}catch(l){throw d(),l}return{name:"artplayerPluginAutoThumbnail"}}}return i.default=i,i}); diff --git a/docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js b/docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js index a63ec8319..e0069461a 100644 --- a/docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js +++ b/docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,l,d=!1;const c=new Set;function s(e){void 0!==e&&c.delete(e)&&URL.revokeObjectURL(e)}function f(){const e=u;u=void 0,null==e||e.dispose()}return{start:function(){if(d)return;const f=u,h=[];let m=!1;const v={active:()=>!d&&!m&&u===v,own(e){if(m){const t=o([e]);t.failed&&a(t.failure)}else h.push(e)},dispose(){if(m)return;m=!0,u===v&&(u=void 0);const e=o(h.splice(0).reverse());e.failed&&a(e.failure)},fail(e){v.active()&&(v.dispose(),a(e))},guard:e=>(...t)=>{if(v.active())try{return e(...t)}catch(n){v.fail(n)}},publish(u,d){if(!v.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!v.active())return void s(f);const h=l;l=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&i(e,a,o[a]);if(t)for(var a of t(o))r.call(o,a)&&i(e,a,o[a]);return e})({url:f},d))}catch(m){l===f&&(l=void 0!==h&&c.has(h)?h:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),m}s(h)}};return u=v,null==f||f.dispose(),v},cancel:f,destroy:function(){if(d)return;d=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);l=void 0,e.failed&&a(e.failure)},get closed(){return d}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let r;function i(){const e=r;r=void 0;const n=o([()=>{t.onloadeddata=null},()=>{t.onseeked=null},()=>{e&&null!==e.timer&&clearTimeout(e.timer)},()=>{e&&null!==e.frame&&t.cancelVideoFrameCallback(e.frame)}]);if(n.failed)throw n.failure}return e.own(i),(o,a)=>{if(i(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};r=u;const l=()=>e.active()&&r===u,d=e.guard(()=>{l()&&u.sought&&u.presented&&(i(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const r=++u.sequence;u.presented=!1;const i=u.frame;if(u.frame=null,null!==i&&t.cancelVideoFrameCallback(i),!l())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{l()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,d())}));l()&&r===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),l()&&(t.currentTime=o)},s=e.guard(()=>{l()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!l()||t.seeking)return;const e=t.currentTime;if(l())if(!Number.isFinite(e)||Math.abs(e-o)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,d()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(l())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!l())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;l()&&(f?s():t.onloadeddata=s)}}function l(e,t){const n=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const i=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(i),n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{i(),n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:l,canvasWidth:d,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),r=Math.trunc(10*e.width),i=Math.trunc(n*Math.ceil(e.number/10));if(![n,r,i].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:r,canvasHeight:i}}(t,{duration:r,videoHeight:o,videoWidth:a});if(!e.active())return;n.width=a,n.height=o,n.style.width=`${a}px`,n.style.height=`${o}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=d,!e.active())return;if(s.height=c,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const h=u(e,n),m=function(e,t){let n;function r(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(r),i=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(r(),e.active()&&i(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=t.number?e.dispose():h(r*v/t.number,e.guard(()=>{f.drawImage(n,v%10*t.width,Math.floor(v/10)*l,t.width,l),e.active()&&m(n=>{e.publish(n,{height:l,column:10,number:t.number,width:t.width,scale:t.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?n.src=t.url:i())}function d(e){return t=>{return n=null,r=null,i=function*(){const n=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),r=a(e=>{t.thumbnails=e},n),i=[],u=()=>{const n=r.start();n&&n.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(e,()=>t.option.url);n.active()&&l(n,r)})()},d=()=>{const e=i.splice(0),t=o([r.destroy,...e]);t.failed&&n(t.failure)};try{const e=[["destroy",d],["restart",r.cancel],["video:loadedmetadata",u]];for(const[n,o]of e){if(r.closed)break;i.push(()=>t.off(n,o)),t.on(n,o),r.closed&&t.off(n,o)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}},new Promise((e,t)=>{var o=e=>{try{u(i.next(e))}catch(n){t(n)}},a=e=>{try{u(i.throw(e))}catch(n){t(n)}},u=t=>t.done?e(t.value):Promise.resolve(t.value).then(o,a);u((i=i.apply(n,r)).next())});var n,r,i}}return d.default=d,d}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,l,d=!1;const c=new Set;function s(e){void 0!==e&&c.delete(e)&&URL.revokeObjectURL(e)}function f(){const e=u;u=void 0,null==e||e.dispose()}return{start:function(){if(d)return;const f=u,m=[];let h=!1;const v={active:()=>!d&&!h&&u===v,own(e){if(h){const t=o([e]);t.failed&&a(t.failure)}else m.push(e)},dispose(){if(h)return;h=!0,u===v&&(u=void 0);const e=o(m.splice(0).reverse());e.failed&&a(e.failure)},fail(e){v.active()&&(v.dispose(),a(e))},guard:e=>(...t)=>{if(v.active())try{return e(...t)}catch(n){v.fail(n)}},publish(u,d){if(!v.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!v.active())return void s(f);const m=l;l=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&i(e,a,o[a]);if(t)for(var a of t(o))r.call(o,a)&&i(e,a,o[a]);return e})({url:f},d))}catch(h){l===f&&(l=void 0!==m&&c.has(m)?m:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),h}s(m)}};return u=v,null==f||f.dispose(),v},cancel:f,destroy:function(){if(d)return;d=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);l=void 0,e.failed&&a(e.failure)},get closed(){return d}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let r;function i(){const e=r;r=void 0;const n=o([()=>{t.onloadeddata=null},()=>{t.onseeked=null},()=>{e&&null!==e.timer&&clearTimeout(e.timer)},()=>{e&&null!==e.frame&&t.cancelVideoFrameCallback(e.frame)}]);if(n.failed)throw n.failure}return e.own(i),(o,a)=>{if(i(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};r=u;const l=()=>e.active()&&r===u,d=e.guard(()=>{l()&&u.sought&&u.presented&&(i(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const r=++u.sequence;u.presented=!1;const i=u.frame;if(u.frame=null,null!==i&&t.cancelVideoFrameCallback(i),!l())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{l()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,d())}));l()&&r===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),l()&&(t.currentTime=o)},s=e.guard(()=>{l()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!l()||t.seeking)return;const e=t.currentTime;if(l())if(!Number.isFinite(e)||Math.abs(e-o)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,d()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(l())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!l())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;l()&&(f?s():t.onloadeddata=s)}}function l(e,t){const n=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const i=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(i),n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{i(),n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:l,canvasWidth:d,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),r=Math.trunc(10*e.width),i=Math.trunc(n*Math.ceil(e.number/10));if(![n,r,i].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:r,canvasHeight:i}}(t,{duration:r,videoHeight:o,videoWidth:a});if(!e.active())return;n.width=a,n.height=o,n.style.width=`${a}px`,n.style.height=`${o}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=d,!e.active())return;if(s.height=c,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const m=u(e,n),h=function(e,t){let n;function r(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(r),i=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(r(),e.active()&&i(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=t.number?e.dispose():m(r*v/t.number,e.guard(()=>{f.drawImage(n,v%10*t.width,Math.floor(v/10)*l,t.width,l),e.active()&&h(n=>{e.publish(n,{height:l,column:10,number:t.number,width:t.width,scale:t.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?n.src=t.url:i())}function d(e){return t=>{return n=null,r=null,i=function*(){if(t.isDestroy)return{name:"artplayerPluginAutoThumbnail"};const n=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),r=a(e=>{t.thumbnails=e},n),i=[],u=()=>{const n=r.start();n&&n.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(e,()=>t.option.url);n.active()&&l(n,r)})()},d=()=>{const e=i.splice(0),t=o([r.destroy,...e]);t.failed&&n(t.failure)};try{const e=[["destroy",d],["restart",r.cancel],["video:loadedmetadata",u]];for(const[n,o]of e){if(r.closed)break;i.push(()=>t.off(n,o)),t.on(n,o),r.closed&&t.off(n,o)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}},new Promise((e,t)=>{var o=e=>{try{u(i.next(e))}catch(n){t(n)}},a=e=>{try{u(i.throw(e))}catch(n){t(n)}},u=t=>t.done?e(t.value):Promise.resolve(t.value).then(o,a);u((i=i.apply(n,r)).next())});var n,r,i}}return d.default=d,d}); diff --git a/docs/compiled/artplayer-plugin-auto-thumbnail.mjs b/docs/compiled/artplayer-plugin-auto-thumbnail.mjs index 77c5afd91..0539d23b8 100644 --- a/docs/compiled/artplayer-plugin-auto-thumbnail.mjs +++ b/docs/compiled/artplayer-plugin-auto-thumbnail.mjs @@ -402,6 +402,8 @@ function extract(job, config) { } function artplayerPluginAutoThumbnail(option) { return async (art) => { + if (art.isDestroy) + return { name: "artplayerPluginAutoThumbnail" }; const report = (error) => console.warn("ArtPlayer auto-thumbnail failed:", error); const session = createSession((config) => { art.thumbnails = config; diff --git a/package.json b/package.json index b8bbf3a9b..43d76e0c8 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:vconsole && yarn build:console && yarn build:site-notices && yarn build:test && yarn build:docs && yarn test:imports", "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js", "typecheck": "node scripts/typecheck.mjs", - "test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.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/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js test/asr-media-error.test.js test/thumbnail-cleanup-errors.test.js test/dash-cleanup-errors.test.js test/hls-example.test.js test/dash-example.test.js", + "test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail-registration.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.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/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js test/asr-media-error.test.js test/thumbnail-cleanup-errors.test.js test/dash-cleanup-errors.test.js test/hls-example.test.js test/dash-example.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", @@ -87,7 +87,7 @@ "test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs", "test:thumbnail": "node --test test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs refactor/scripts/thumbnail-types.test.mjs test/thumbnail-cleanup-errors.test.js", "test:thumbnail-types-package": "node refactor/scripts/thumbnail-package-types.mjs", - "test:auto-thumbnail": "node --test test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs refactor/scripts/auto-thumbnail-types.test.mjs", + "test:auto-thumbnail": "node --test test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail-registration.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs refactor/scripts/auto-thumbnail-types.test.mjs", "test:vtt-thumbnail": "node --test test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js refactor/scripts/vtt-thumbnail-contract.test.mjs refactor/scripts/vtt-thumbnail-types.test.mjs", "test:vtt-thumbnail-types-package": "node refactor/scripts/vtt-thumbnail-package-types.mjs", "test:multiple-subtitles": "node --test test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs", diff --git a/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md b/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md index a17b62460..e2c49ff18 100644 --- a/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md +++ b/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md @@ -11,6 +11,11 @@ for video processing. read at each `video:loadedmetadata` event, including subsequent changes to the original options object. `restart` cancels the old decoder immediately. `destroy` closes the session and removes subscriptions. Partial registration is rolled back. + Before accessing host subscriptions, a truthy public `isDestroy` makes a delayed + registrar resolve with its unchanged name result and no resources. The internal + host flag is optional, preserving structural hosts without that property. Core + plugin-manager rejection policy is unchanged; this protects direct retained + registrars such as callbacks delivered after an asynchronous module load. - `src/options.ts` preserves truthy defaults and computes the ten-column layout. Coercible numeric JS inputs and fractional frame counts retain their previous behavior; raw option values are forwarded to the thumbnail configuration. @@ -240,6 +245,15 @@ require the host's existing getter to have every generated-sheet field. ## Shared installed browser scope +`test/auto-thumbnail-registration.test.js` covers destroyed hosts, unavailable +destroyed-host members and successful extraction with false/absent flags. The +`auto-thumbnail-registration.spec.js` browser regression installs a retained +registrar after actual published/candidate core destruction, records subscriptions, +and sends a late metadata event to prove no native video is allocated. It is in +both the source collection and the installed roster; configuration alone does +not prove a fresh installed run. Main/legacy regression evidence is tracked by +PKG-AUTO-THUMB-13. This fix does not resolve AUTO-THUMB-PIXEL-01. + autoThumbnailCandidate reads verified installed bytes through browser-candidate.js. Native video/JPEG lifecycle and pixel files retain their stub player host and exact presentation-callback/fallback limits. The historical file still loads its frozen old artifacts. Installed mode rejects frozen-workspace and explicit-artifact overrides. These are native extraction checks, not full core integration or first-frame acceptance on every engine. Use `yarn test:package --browser` and the shared scope rules in diff --git a/packages/artplayer-plugin-auto-thumbnail/README.md b/packages/artplayer-plugin-auto-thumbnail/README.md index 7666fe1da..7e8d70d1c 100644 --- a/packages/artplayer-plugin-auto-thumbnail/README.md +++ b/packages/artplayer-plugin-auto-thumbnail/README.md @@ -34,6 +34,9 @@ The registrar has type `(art: Artplayer) => Promise`. It resolves with `{ name: 'artplayerPluginAutoThumbnail' }` after subscribing to player events, before extraction completes. Generated sheets arrive progressively through the player's thumbnails configuration. Awaiting registration does not wait for them. +If an application invokes a retained registrar after the player is destroyed, +it resolves with the same name without subscribing or allocating a decoder. +This does not override the core's own `plugins.add()` destruction checks. The options object is required; use `{}` for defaults. `height` is accepted by the runtime type for historical callers but remains ignored: the video aspect ratio determines sheet height. Numeric strings retain their old JavaScript diff --git a/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js b/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js index 5d5c3359c..775593a8e 100644 --- a/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js +++ b/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const i=e([()=>{n.onloadeddata=null},()=>{n.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&n.cancelVideoFrameCallback(t.frame)}]);if(i.failed)throw i.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!i,retries:0,sequence:0,started:!1};r=u;const d=()=>t.active()&&r===u,c=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),l=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,c())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");l()}else u.sought=!0,c()}),l())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?s():n.onloadeddata=s)}}function n(e,n){const i=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const o=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(o),i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{o(),i.onloadedmetadata=null;const r=i.duration,a=i.videoHeight,u=i.videoWidth,{height:d,canvasWidth:c,canvasHeight:l}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),i=Math.trunc(10*e.width),r=Math.trunc(n*Math.ceil(e.number/10));if(![n,i,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:i,canvasHeight:r}}(n,{duration:r,videoHeight:a,videoWidth:u});if(!e.active())return;i.width=u,i.height=a,i.style.width=`${u}px`,i.style.height=`${a}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=c,!e.active())return;if(s.height=l,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const h=t(e,i),m=function(e,t){let n;function i(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(i),r=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(i(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=n.number?e.dispose():h(r*v/n.number,e.guard(()=>{f.drawImage(i,v%10*n.width,Math.floor(v/10)*d,n.width,d),e.active()&&m(t=>{e.publish(t,{height:d,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?i.src=n.url:o())}function i(t){return async i=>{const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,n){let i,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=i;i=void 0,e?.dispose()}return{start:function(){if(o)return;const d=i,c=[];let l=!1;const s={active:()=>!o&&!l&&i===s,own(t){if(l){const i=e([t]);i.failed&&n(i.failure)}else c.push(t)},dispose(){if(l)return;l=!0,i===s&&(i=void 0);const t=e(c.splice(0).reverse());t.failed&&n(t.failure)},fail(e){s.active()&&(s.dispose(),n(e))},guard:e=>(...t)=>{if(s.active())try{return e(...t)}catch(n){s.fail(n)}},publish(i,o){if(!s.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!s.active())return void u(d);const c=r;r=d;try{t({url:d,...o})}catch(l){r===d&&(r=void 0!==c&&a.has(c)?c:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),l}u(c)}};return i=s,d?.dispose(),s},cancel:d,destroy:function(){if(o)return;o=!0;const t=e([d,...[...a].map(e=>()=>u(e))]);r=void 0,t.failed&&n(t.failure)},get closed(){return o}}}(e=>{i.thumbnails=e},r),a=[],u=()=>{const e=o.start();e&&e.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(t,()=>i.option.url);e.active()&&n(e,r)})()},d=()=>{const t=a.splice(0),n=e([o.destroy,...t]);n.failed&&r(n.failure)};try{const e=[["destroy",d],["restart",o.cancel],["video:loadedmetadata",u]];for(const[t,n]of e){if(o.closed)break;a.push(()=>i.off(t,n)),i.on(t,n),o.closed&&i.off(t,n)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}}}return i.default=i,i}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const i=e([()=>{n.onloadeddata=null},()=>{n.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&n.cancelVideoFrameCallback(t.frame)}]);if(i.failed)throw i.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!i,retries:0,sequence:0,started:!1};r=u;const d=()=>t.active()&&r===u,l=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,l()}),c())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?s():n.onloadeddata=s)}}function n(e,n){const i=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const o=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(o),i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{o(),i.onloadedmetadata=null;const r=i.duration,a=i.videoHeight,u=i.videoWidth,{height:d,canvasWidth:l,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),i=Math.trunc(10*e.width),r=Math.trunc(n*Math.ceil(e.number/10));if(![n,i,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:i,canvasHeight:r}}(n,{duration:r,videoHeight:a,videoWidth:u});if(!e.active())return;i.width=u,i.height=a,i.style.width=`${u}px`,i.style.height=`${a}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=l,!e.active())return;if(s.height=c,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const m=t(e,i),h=function(e,t){let n;function i(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(i),r=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(i(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=n.number?e.dispose():m(r*v/n.number,e.guard(()=>{f.drawImage(i,v%10*n.width,Math.floor(v/10)*d,n.width,d),e.active()&&h(t=>{e.publish(t,{height:d,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?i.src=n.url:o())}function i(t){return async i=>{if(i.isDestroy)return{name:"artplayerPluginAutoThumbnail"};const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,n){let i,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=i;i=void 0,e?.dispose()}return{start:function(){if(o)return;const d=i,l=[];let c=!1;const s={active:()=>!o&&!c&&i===s,own(t){if(c){const i=e([t]);i.failed&&n(i.failure)}else l.push(t)},dispose(){if(c)return;c=!0,i===s&&(i=void 0);const t=e(l.splice(0).reverse());t.failed&&n(t.failure)},fail(e){s.active()&&(s.dispose(),n(e))},guard:e=>(...t)=>{if(s.active())try{return e(...t)}catch(n){s.fail(n)}},publish(i,o){if(!s.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!s.active())return void u(d);const l=r;r=d;try{t({url:d,...o})}catch(c){r===d&&(r=void 0!==l&&a.has(l)?l:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),c}u(l)}};return i=s,d?.dispose(),s},cancel:d,destroy:function(){if(o)return;o=!0;const t=e([d,...[...a].map(e=>()=>u(e))]);r=void 0,t.failed&&n(t.failure)},get closed(){return o}}}(e=>{i.thumbnails=e},r),a=[],u=()=>{const e=o.start();e&&e.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(t,()=>i.option.url);e.active()&&n(e,r)})()},d=()=>{const t=a.splice(0),n=e([o.destroy,...t]);n.failed&&r(n.failure)};try{const e=[["destroy",d],["restart",o.cancel],["video:loadedmetadata",u]];for(const[t,n]of e){if(o.closed)break;a.push(()=>i.off(t,n)),i.on(t,n),o.closed&&i.off(t,n)}}catch(l){throw d(),l}return{name:"artplayerPluginAutoThumbnail"}}}return i.default=i,i}); diff --git a/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js b/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js index a63ec8319..e0069461a 100644 --- a/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js +++ b/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,l,d=!1;const c=new Set;function s(e){void 0!==e&&c.delete(e)&&URL.revokeObjectURL(e)}function f(){const e=u;u=void 0,null==e||e.dispose()}return{start:function(){if(d)return;const f=u,h=[];let m=!1;const v={active:()=>!d&&!m&&u===v,own(e){if(m){const t=o([e]);t.failed&&a(t.failure)}else h.push(e)},dispose(){if(m)return;m=!0,u===v&&(u=void 0);const e=o(h.splice(0).reverse());e.failed&&a(e.failure)},fail(e){v.active()&&(v.dispose(),a(e))},guard:e=>(...t)=>{if(v.active())try{return e(...t)}catch(n){v.fail(n)}},publish(u,d){if(!v.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!v.active())return void s(f);const h=l;l=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&i(e,a,o[a]);if(t)for(var a of t(o))r.call(o,a)&&i(e,a,o[a]);return e})({url:f},d))}catch(m){l===f&&(l=void 0!==h&&c.has(h)?h:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),m}s(h)}};return u=v,null==f||f.dispose(),v},cancel:f,destroy:function(){if(d)return;d=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);l=void 0,e.failed&&a(e.failure)},get closed(){return d}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let r;function i(){const e=r;r=void 0;const n=o([()=>{t.onloadeddata=null},()=>{t.onseeked=null},()=>{e&&null!==e.timer&&clearTimeout(e.timer)},()=>{e&&null!==e.frame&&t.cancelVideoFrameCallback(e.frame)}]);if(n.failed)throw n.failure}return e.own(i),(o,a)=>{if(i(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};r=u;const l=()=>e.active()&&r===u,d=e.guard(()=>{l()&&u.sought&&u.presented&&(i(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const r=++u.sequence;u.presented=!1;const i=u.frame;if(u.frame=null,null!==i&&t.cancelVideoFrameCallback(i),!l())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{l()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,d())}));l()&&r===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),l()&&(t.currentTime=o)},s=e.guard(()=>{l()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!l()||t.seeking)return;const e=t.currentTime;if(l())if(!Number.isFinite(e)||Math.abs(e-o)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,d()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(l())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!l())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;l()&&(f?s():t.onloadeddata=s)}}function l(e,t){const n=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const i=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(i),n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{i(),n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:l,canvasWidth:d,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),r=Math.trunc(10*e.width),i=Math.trunc(n*Math.ceil(e.number/10));if(![n,r,i].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:r,canvasHeight:i}}(t,{duration:r,videoHeight:o,videoWidth:a});if(!e.active())return;n.width=a,n.height=o,n.style.width=`${a}px`,n.style.height=`${o}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=d,!e.active())return;if(s.height=c,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const h=u(e,n),m=function(e,t){let n;function r(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(r),i=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(r(),e.active()&&i(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=t.number?e.dispose():h(r*v/t.number,e.guard(()=>{f.drawImage(n,v%10*t.width,Math.floor(v/10)*l,t.width,l),e.active()&&m(n=>{e.publish(n,{height:l,column:10,number:t.number,width:t.width,scale:t.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?n.src=t.url:i())}function d(e){return t=>{return n=null,r=null,i=function*(){const n=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),r=a(e=>{t.thumbnails=e},n),i=[],u=()=>{const n=r.start();n&&n.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(e,()=>t.option.url);n.active()&&l(n,r)})()},d=()=>{const e=i.splice(0),t=o([r.destroy,...e]);t.failed&&n(t.failure)};try{const e=[["destroy",d],["restart",r.cancel],["video:loadedmetadata",u]];for(const[n,o]of e){if(r.closed)break;i.push(()=>t.off(n,o)),t.on(n,o),r.closed&&t.off(n,o)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}},new Promise((e,t)=>{var o=e=>{try{u(i.next(e))}catch(n){t(n)}},a=e=>{try{u(i.throw(e))}catch(n){t(n)}},u=t=>t.done?e(t.value):Promise.resolve(t.value).then(o,a);u((i=i.apply(n,r)).next())});var n,r,i}}return d.default=d,d}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,l,d=!1;const c=new Set;function s(e){void 0!==e&&c.delete(e)&&URL.revokeObjectURL(e)}function f(){const e=u;u=void 0,null==e||e.dispose()}return{start:function(){if(d)return;const f=u,m=[];let h=!1;const v={active:()=>!d&&!h&&u===v,own(e){if(h){const t=o([e]);t.failed&&a(t.failure)}else m.push(e)},dispose(){if(h)return;h=!0,u===v&&(u=void 0);const e=o(m.splice(0).reverse());e.failed&&a(e.failure)},fail(e){v.active()&&(v.dispose(),a(e))},guard:e=>(...t)=>{if(v.active())try{return e(...t)}catch(n){v.fail(n)}},publish(u,d){if(!v.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!v.active())return void s(f);const m=l;l=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&i(e,a,o[a]);if(t)for(var a of t(o))r.call(o,a)&&i(e,a,o[a]);return e})({url:f},d))}catch(h){l===f&&(l=void 0!==m&&c.has(m)?m:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),h}s(m)}};return u=v,null==f||f.dispose(),v},cancel:f,destroy:function(){if(d)return;d=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);l=void 0,e.failed&&a(e.failure)},get closed(){return d}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let r;function i(){const e=r;r=void 0;const n=o([()=>{t.onloadeddata=null},()=>{t.onseeked=null},()=>{e&&null!==e.timer&&clearTimeout(e.timer)},()=>{e&&null!==e.frame&&t.cancelVideoFrameCallback(e.frame)}]);if(n.failed)throw n.failure}return e.own(i),(o,a)=>{if(i(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};r=u;const l=()=>e.active()&&r===u,d=e.guard(()=>{l()&&u.sought&&u.presented&&(i(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const r=++u.sequence;u.presented=!1;const i=u.frame;if(u.frame=null,null!==i&&t.cancelVideoFrameCallback(i),!l())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{l()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,d())}));l()&&r===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),l()&&(t.currentTime=o)},s=e.guard(()=>{l()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!l()||t.seeking)return;const e=t.currentTime;if(l())if(!Number.isFinite(e)||Math.abs(e-o)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,d()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(l())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!l())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;l()&&(f?s():t.onloadeddata=s)}}function l(e,t){const n=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const i=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(i),n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{i(),n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:l,canvasWidth:d,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),r=Math.trunc(10*e.width),i=Math.trunc(n*Math.ceil(e.number/10));if(![n,r,i].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:r,canvasHeight:i}}(t,{duration:r,videoHeight:o,videoWidth:a});if(!e.active())return;n.width=a,n.height=o,n.style.width=`${a}px`,n.style.height=`${o}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=d,!e.active())return;if(s.height=c,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const m=u(e,n),h=function(e,t){let n;function r(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(r),i=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(r(),e.active()&&i(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=t.number?e.dispose():m(r*v/t.number,e.guard(()=>{f.drawImage(n,v%10*t.width,Math.floor(v/10)*l,t.width,l),e.active()&&h(n=>{e.publish(n,{height:l,column:10,number:t.number,width:t.width,scale:t.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?n.src=t.url:i())}function d(e){return t=>{return n=null,r=null,i=function*(){if(t.isDestroy)return{name:"artplayerPluginAutoThumbnail"};const n=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),r=a(e=>{t.thumbnails=e},n),i=[],u=()=>{const n=r.start();n&&n.guard(()=>{const r=function(e,t){const n={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(n.width))||n.width<=0||!Number.isFinite(Number(n.number))||n.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return n}(e,()=>t.option.url);n.active()&&l(n,r)})()},d=()=>{const e=i.splice(0),t=o([r.destroy,...e]);t.failed&&n(t.failure)};try{const e=[["destroy",d],["restart",r.cancel],["video:loadedmetadata",u]];for(const[n,o]of e){if(r.closed)break;i.push(()=>t.off(n,o)),t.on(n,o),r.closed&&t.off(n,o)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}},new Promise((e,t)=>{var o=e=>{try{u(i.next(e))}catch(n){t(n)}},a=e=>{try{u(i.throw(e))}catch(n){t(n)}},u=t=>t.done?e(t.value):Promise.resolve(t.value).then(o,a);u((i=i.apply(n,r)).next())});var n,r,i}}return d.default=d,d}); diff --git a/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs b/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs index 77c5afd91..0539d23b8 100644 --- a/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs +++ b/packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs @@ -402,6 +402,8 @@ function extract(job, config) { } function artplayerPluginAutoThumbnail(option) { return async (art) => { + if (art.isDestroy) + return { name: "artplayerPluginAutoThumbnail" }; const report = (error) => console.warn("ArtPlayer auto-thumbnail failed:", error); const session = createSession((config) => { art.thumbnails = config; diff --git a/packages/artplayer-plugin-auto-thumbnail/src/index.ts b/packages/artplayer-plugin-auto-thumbnail/src/index.ts index 3ee4f19c7..84c840eac 100644 --- a/packages/artplayer-plugin-auto-thumbnail/src/index.ts +++ b/packages/artplayer-plugin-auto-thumbnail/src/index.ts @@ -6,6 +6,8 @@ import createSession, { cleanupAll } from './session' function artplayerPluginAutoThumbnail(option: ThumbnailOptions) { return async (art: ThumbnailHost): Promise => { + if (art.isDestroy) + return { name: 'artplayerPluginAutoThumbnail' } const report = (error: unknown) => console.warn('ArtPlayer auto-thumbnail failed:', error) const session = createSession((config) => { art.thumbnails = config diff --git a/packages/artplayer-plugin-auto-thumbnail/src/types.ts b/packages/artplayer-plugin-auto-thumbnail/src/types.ts index be815127a..5d8ad9f11 100644 --- a/packages/artplayer-plugin-auto-thumbnail/src/types.ts +++ b/packages/artplayer-plugin-auto-thumbnail/src/types.ts @@ -29,6 +29,7 @@ export interface ExtractionJob { } export interface ThumbnailHost { + isDestroy?: boolean option: { url: string } // A host may already hold a partial user configuration; generated sheets are complete. thumbnails: { url: string } & Partial diff --git a/refactor/baselines/auto-thumbnail-registration-validation.json b/refactor/baselines/auto-thumbnail-registration-validation.json new file mode 100644 index 000000000..5b3cbc6cb --- /dev/null +++ b/refactor/baselines/auto-thumbnail-registration-validation.json @@ -0,0 +1,3824 @@ +{ + "task": "PKG-AUTO-THUMB-13", + "baselineCommit": "9ba3c82f99e6bad0e858f6c2e1d8624dfb725c8c", + "environment": { + "node": "24.21.0", + "yarn": "1.22.22", + "typescript": "5.9.3", + "platform": "Windows" + }, + "files": [ + { + "path": "packages/artplayer-plugin-auto-thumbnail/src/index.ts", + "sha256": "fb5dc77f0be97156b89b2538df4473045002a5c67af3d66e41b073b8634ca1af" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/src/types.ts", + "sha256": "5eebf6faa2c5fe3b78b1df128cc29d9be9681997d4d309118bb13d7dcb1c2545" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/README.md", + "sha256": "bb9c18ea93428b28e84f061240508d2fed1e8c31a4a189d06a0e4711c49e86b5" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md", + "sha256": "006c82cb1e7cb15847322dfe61531fbb7bfd27e19e45b175147248908e94901d" + }, + { + "path": "test/auto-thumbnail-registration.test.js", + "sha256": "74876c5b65bc015c1a312c204dd410eee506a8dd7747e66c48a6785679853264" + }, + { + "path": "test/browser/auto-thumbnail-registration.spec.js", + "sha256": "59b756ec28a9c389fd8fe76b946b12a5a419e202a323c2c62fc98ce524e568fe" + }, + { + "path": "scripts/browser-validation/scope.ts", + "sha256": "7494832f51d6efef9427ab121d834aab48679379c8ca303181c92e56590a04e4" + }, + { + "path": "package.json", + "sha256": "9c081bfb0d25c8039c1e31077ae9e06ac6573b3db68dc60b235a839003269d40" + } + ], + "artifacts": [ + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "copy": { + "path": "docs/compiled/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + } + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "copy": { + "path": "docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + } + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs", + "sha256": "920dc9ecde4e518927394a6a140c1aefeef3fd323883c02cdda90413d78c7e0e", + "copy": { + "path": "docs/compiled/artplayer-plugin-auto-thumbnail.mjs", + "sha256": "920dc9ecde4e518927394a6a140c1aefeef3fd323883c02cdda90413d78c7e0e" + } + } + ], + "logs": [ + { + "path": "refactor/.cache/auto-thumbnail-registration-before.log", + "sha256": "6c0c210629b55a213f62bcd745f50e73bd5e022dd37311ccc3325831f9332c8e" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-source.log", + "sha256": "426bb5795480f16efb6bed79d1691ad01b83ba1f67d259e53b834b6d6ef36033" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-main.log", + "sha256": "4e5c18c05c8271531c1221bdd34219e459257aa71e6f7a75ad1f763846b3f692" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-legacy.log", + "sha256": "38f3ec40926478e8d9e9819cd5f3feae35b3b7009342aa8b89602587584f3c06" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-build.log", + "sha256": "ce5c2ce046914f246ffe02986dd0329fae81019e2cf8e9e43715b77101a2a4ef" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-types.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-lint.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-runner.log", + "sha256": "e3d4b80700999fe6466a6f58123d4bf885acdb47e1dc832b2fab5e2050e3f76a" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-runner-types.log", + "sha256": "46b454a723db167a65fc9890cc93118919ea211c7fb9c536e9a58196cca8c10c" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-toolchain.log", + "sha256": "611c06d5d23faae2d8af4bd44a49c597e23f03e9b443aa0e470d8fbe9f3ceba6" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-browser-before.log", + "sha256": "24f7493bf1c92934926923831a170ba64780471658fc41b296a87672ff789fa9" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-browser-main.log", + "sha256": "03631d7f0ea496eb00f4b8286f6411b6609347d9261ca9bdefbfcf58171b431d" + }, + { + "path": "refactor/.cache/auto-thumbnail-registration-browser-legacy.log", + "sha256": "353f51311f21a1c44ce55af03ea062eb286b165f950932c3ded5fbeb8ae436fc" + } + ], + "before": { + "node": { + "passed": 2, + "failed": 2 + }, + "browser": { + "path": "refactor/.cache/auto-thumbnail-registration-browser-before.json", + "sha256": "d43eb0455b0830ec9d79f49109ee03f5d15a3c90af8a24f83074c4e05bdc5faa", + "stats": { + "startTime": "2026-09-15T11:10:12.273Z", + "duration": 4993.18, + "expected": 0, + "skipped": 0, + "unexpected": 2, + "flaky": 0 + }, + "cases": [ + { + "title": "Auto-thumbnail delayed factory installation stays inert after published core destruction", + "project": "chromium", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-registration.spec.js", + "column": 22, + "line": 37 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 6\u001b[39m\n\n\u001b[33m@@ -3,8 +3,12 @@\u001b[39m\n\u001b[2m \"canvases\": 0,\u001b[22m\n\u001b[2m \"destroyed\": true,\u001b[22m\n\u001b[2m \"result\": Object {\u001b[22m\n\u001b[2m \"name\": \"artplayerPluginAutoThumbnail\",\u001b[22m\n\u001b[2m },\u001b[22m\n\u001b[32m- \"subscriptions\": Array [],\u001b[39m\n\u001b[32m- \"videos\": 0,\u001b[39m\n\u001b[31m+ \"subscriptions\": Array [\u001b[39m\n\u001b[31m+ \"destroy\",\u001b[39m\n\u001b[31m+ \"restart\",\u001b[39m\n\u001b[31m+ \"video:loadedmetadata\",\u001b[39m\n\u001b[31m+ ],\u001b[39m\n\u001b[31m+ \"videos\": 1,\u001b[39m\n\u001b[2m }\u001b[22m\n\n 35 | })\n 36 | await testInfo.attach('auto-thumbnail-late-registration', { contentType: 'application/json', body: JSON.stringify({ core, sha256: hash(implementation.code), provenance: implementation.provenance, ...evidence }) })\n> 37 | expect(evidence).toEqual({ destroyed: true, asynchronous: true, result: { name: 'artplayerPluginAutoThumbnail' }, subscriptions: [], videos: 0, canvases: 0 })\n | ^\n 38 | })\n 39 | }\n 40 |\n at D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-registration.spec.js:37:22" + } + ], + "registration": { + "core": "published", + "sha256": "54cd06f1b269519e41dc0a82623eba9141cb97468a3b6480ba6648cf307dbe28", + "provenance": { + "kind": "source-build", + "file": null, + "sha256": "54cd06f1b269519e41dc0a82623eba9141cb97468a3b6480ba6648cf307dbe28" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [ + "destroy", + "restart", + "video:loadedmetadata" + ], + "videos": 1, + "canvases": 0 + }, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after candidate core destruction", + "project": "chromium", + "status": "failed", + "retry": 0, + "errors": [ + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-registration.spec.js", + "column": 22, + "line": 37 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 2\u001b[39m\n\u001b[31m+ Received + 6\u001b[39m\n\n\u001b[33m@@ -3,8 +3,12 @@\u001b[39m\n\u001b[2m \"canvases\": 0,\u001b[22m\n\u001b[2m \"destroyed\": true,\u001b[22m\n\u001b[2m \"result\": Object {\u001b[22m\n\u001b[2m \"name\": \"artplayerPluginAutoThumbnail\",\u001b[22m\n\u001b[2m },\u001b[22m\n\u001b[32m- \"subscriptions\": Array [],\u001b[39m\n\u001b[32m- \"videos\": 0,\u001b[39m\n\u001b[31m+ \"subscriptions\": Array [\u001b[39m\n\u001b[31m+ \"destroy\",\u001b[39m\n\u001b[31m+ \"restart\",\u001b[39m\n\u001b[31m+ \"video:loadedmetadata\",\u001b[39m\n\u001b[31m+ ],\u001b[39m\n\u001b[31m+ \"videos\": 1,\u001b[39m\n\u001b[2m }\u001b[22m\n\n 35 | })\n 36 | await testInfo.attach('auto-thumbnail-late-registration', { contentType: 'application/json', body: JSON.stringify({ core, sha256: hash(implementation.code), provenance: implementation.provenance, ...evidence }) })\n> 37 | expect(evidence).toEqual({ destroyed: true, asynchronous: true, result: { name: 'artplayerPluginAutoThumbnail' }, subscriptions: [], videos: 0, canvases: 0 })\n | ^\n 38 | })\n 39 | }\n 40 |\n at D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-registration.spec.js:37:22" + } + ], + "registration": { + "core": "candidate", + "sha256": "54cd06f1b269519e41dc0a82623eba9141cb97468a3b6480ba6648cf307dbe28", + "provenance": { + "kind": "source-build", + "file": null, + "sha256": "54cd06f1b269519e41dc0a82623eba9141cb97468a3b6480ba6648cf307dbe28" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [ + "destroy", + "restart", + "video:loadedmetadata" + ], + "videos": 1, + "canvases": 0 + }, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + } + ], + "results": "refactor/.cache/auto-thumbnail-registration-browser-before-results" + } + }, + "after": { + "source": { + "passed": 193, + "failed": 0 + }, + "mainUnits": { + "passed": 88, + "failed": 0 + }, + "legacyUnits": { + "passed": 88, + "failed": 0 + }, + "runner": { + "passed": 18, + "failed": 0 + }, + "browserMain": { + "path": "refactor/.cache/auto-thumbnail-registration-browser-main.json", + "sha256": "c66936f1fc30bc7e7541ab0432456e6ac8cfed5074c239b7f16a2fd3abe4a9f0", + "stats": { + "startTime": "2026-09-15T11:12:15.583Z", + "duration": 37408.712, + "expected": 42, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fd6199113f507b4a5687-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-db1643714b42d7109817-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fc9f1151d1a7f7955d37-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-dbb5fa535b67bd4f5ae4-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-c064c2f74bff1d14fb6a-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1118a10c61b3609f4370-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "media": "auto-thumbnail-timeline", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 126, + 0, + 126, + 255 + ], + [ + 254, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 1, + 252, + 255 + ], + [ + 255, + 255, + 1, + 255 + ] + ], + "spatial": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 127, + 0, + 126, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 254, + 0, + 0, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 255, + 2, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.6, + "currentTime": 1.6 + }, + { + "mediaTime": 3.2, + "currentTime": 3.2 + }, + { + "mediaTime": 4.8, + "currentTime": 4.8 + }, + { + "mediaTime": 6.4, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "media": "pattern", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 15, + 13, + 138, + 255 + ], + [ + 15, + 15, + 139, + 255 + ], + [ + 15, + 15, + 139, + 255 + ], + [ + 14, + 15, + 141, + 255 + ], + [ + 213, + 61, + 47, + 255 + ] + ], + "spatial": [ + [ + 244, + 1, + 0, + 255 + ], + [ + 5, + 251, + 2, + 255 + ], + [ + 15, + 15, + 139, + 255 + ], + [ + 255, + 0, + 253, + 255 + ], + [ + 2, + 255, + 253, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 253, + 52, + 0, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.583333, + "currentTime": 1.6 + }, + { + "mediaTime": 3.166667, + "currentTime": 3.2 + }, + { + "mediaTime": 4.791667, + "currentTime": 4.8 + }, + { + "mediaTime": 6.375, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "media": "auto-thumbnail-timeline", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 136, + 19, + 126, + 255 + ], + [ + 254, + 24, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 254, + 241, + 2, + 255 + ] + ], + "spatial": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 137, + 19, + 126, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 24, + 0, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 241, + 2, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.6, + "currentTime": 1.6 + }, + { + "mediaTime": 3.2, + "currentTime": 3.2 + }, + { + "mediaTime": 4.8, + "currentTime": 4.8 + }, + { + "mediaTime": 6.4, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "media": "pattern", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 5, + 15, + 250, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 247, + 64, + 0, + 255 + ] + ], + "spatial": [ + [ + 255, + 22, + 9, + 255 + ], + [ + 0, + 216, + 3, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 255, + 36, + 254, + 255 + ], + [ + 1, + 229, + 254, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 69, + 1, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.6, + "currentTime": 1.6 + }, + { + "mediaTime": 3.2, + "currentTime": 3.2 + }, + { + "mediaTime": 4.8, + "currentTime": 4.8 + }, + { + "mediaTime": 6.4, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "media": "auto-thumbnail-timeline", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 238, + 14, + 14, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 15, + 239, + 255 + ], + [ + 235, + 236, + 12, + 255 + ] + ], + "spatial": [ + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 0 + ] + }, + { + "time": 1.600903, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 238, + 14, + 14, + 255 + ] + }, + { + "time": 3.2021603, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 16, + 16, + 255 + ] + }, + { + "time": 4.8016818, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4013816, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 235, + 236, + 12, + 255 + ] + } + ], + "presented": [], + "supportsPresentation": false, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "media": "pattern", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 217, + 65, + 51, + 255 + ] + ], + "spatial": [ + [ + 238, + 14, + 15, + 255 + ], + [ + 14, + 238, + 14, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 238, + 13, + 237, + 255 + ], + [ + 14, + 237, + 237, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 0 + ] + }, + { + "time": 1.6009505, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 3.2014012, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 4.8004872, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4046706, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 239, + 62, + 14, + 255 + ] + } + ], + "presented": [], + "supportsPresentation": false, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after published core destruction", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "published", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after candidate core destruction", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "candidate", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after published core destruction", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "published", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after candidate core destruction", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "candidate", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after published core destruction", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "published", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after candidate core destruction", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "candidate", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "22f3985cb4692ed864b274763995046e73c760543dcdf1524d33ad3f3bbed8a9" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + } + ], + "results": "refactor/.cache/auto-thumbnail-registration-browser-main-results" + }, + "browserLegacy": { + "path": "refactor/.cache/auto-thumbnail-registration-browser-legacy.json", + "sha256": "8f0443a7b8c14e685d8f64081fd61319f84afdc0bd97c52d0fad5fe1bc3fe18e", + "stats": { + "startTime": "2026-09-15T11:13:25.352Z", + "duration": 31525.207, + "expected": 42, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fd6199113f507b4a5687-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-db1643714b42d7109817-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fc9f1151d1a7f7955d37-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-dbb5fa535b67bd4f5ae4-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-c064c2f74bff1d14fb6a-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1118a10c61b3609f4370-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "media": "auto-thumbnail-timeline", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 126, + 0, + 126, + 255 + ], + [ + 254, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 1, + 252, + 255 + ], + [ + 255, + 255, + 1, + 255 + ] + ], + "spatial": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 127, + 0, + 126, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 254, + 0, + 0, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 255, + 2, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.6, + "currentTime": 1.6 + }, + { + "mediaTime": 3.2, + "currentTime": 3.2 + }, + { + "mediaTime": 4.8, + "currentTime": 4.8 + }, + { + "mediaTime": 6.4, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "media": "pattern", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 15, + 13, + 138, + 255 + ], + [ + 15, + 15, + 139, + 255 + ], + [ + 15, + 15, + 139, + 255 + ], + [ + 14, + 15, + 141, + 255 + ], + [ + 213, + 61, + 47, + 255 + ] + ], + "spatial": [ + [ + 244, + 1, + 0, + 255 + ], + [ + 5, + 251, + 2, + 255 + ], + [ + 15, + 15, + 139, + 255 + ], + [ + 255, + 0, + 253, + 255 + ], + [ + 2, + 255, + 253, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 253, + 52, + 0, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.583333, + "currentTime": 1.6 + }, + { + "mediaTime": 3.166667, + "currentTime": 3.2 + }, + { + "mediaTime": 4.791667, + "currentTime": 4.8 + }, + { + "mediaTime": 6.375, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "media": "auto-thumbnail-timeline", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 136, + 19, + 126, + 255 + ], + [ + 254, + 24, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 254, + 241, + 2, + 255 + ] + ], + "spatial": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ], + [ + 0, + 0, + 0, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 137, + 19, + 126, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 24, + 0, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 241, + 2, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.6, + "currentTime": 1.6 + }, + { + "mediaTime": 3.2, + "currentTime": 3.2 + }, + { + "mediaTime": 4.8, + "currentTime": 4.8 + }, + { + "mediaTime": 6.4, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "media": "pattern", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 5, + 15, + 250, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 247, + 64, + 0, + 255 + ] + ], + "spatial": [ + [ + 255, + 22, + 9, + 255 + ], + [ + 0, + 216, + 3, + 255 + ], + [ + 1, + 14, + 253, + 255 + ], + [ + 255, + 36, + 254, + 255 + ], + [ + 1, + 229, + 254, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 1.6, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 3.2, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 4.8, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 14, + 253, + 255 + ] + }, + { + "time": 6.4, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 255, + 69, + 1, + 255 + ] + } + ], + "presented": [ + { + "mediaTime": 0, + "currentTime": 0 + }, + { + "mediaTime": 1.6, + "currentTime": 1.6 + }, + { + "mediaTime": 3.2, + "currentTime": 3.2 + }, + { + "mediaTime": 4.8, + "currentTime": 4.8 + }, + { + "mediaTime": 6.4, + "currentTime": 6.4 + } + ], + "supportsPresentation": true, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "media": "auto-thumbnail-timeline", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 238, + 14, + 14, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 15, + 239, + 255 + ], + [ + 235, + 236, + 12, + 255 + ] + ], + "spatial": [ + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ], + [ + 16, + 16, + 16, + 255 + ] + ], + "draws": [ + { + "time": 0.0014156, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 0 + ] + }, + { + "time": 1.602524, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 238, + 14, + 14, + 255 + ] + }, + { + "time": 3.202443, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 16, + 16, + 255 + ] + }, + { + "time": 4.8017271, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4161973, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 235, + 236, + 12, + 255 + ] + } + ], + "presented": [], + "supportsPresentation": false, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": null, + "pixels": { + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "media": "pattern", + "scope": "All five cells are asserted where native frame presentation callbacks are available. Without that capability only cells 2-4 are asserted, with cells 0-1 recorded as unresolved diagnostics under AUTO-THUMB-PIXEL-01. Native HTTP/JPEG and a stub player host, not a core integration.", + "cells": [ + [ + 0, + 0, + 0, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 217, + 65, + 51, + 255 + ] + ], + "spatial": [ + [ + 238, + 14, + 15, + 255 + ], + [ + 14, + 238, + 14, + 255 + ], + [ + 28, + 28, + 140, + 255 + ], + [ + 238, + 13, + 237, + 255 + ], + [ + 14, + 237, + 237, + 255 + ] + ], + "draws": [ + { + "time": 0, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 0 + ] + }, + { + "time": 1.600548, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 3.2023682, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 4.8005469, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4017108, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 239, + 62, + 14, + 255 + ] + } + ], + "presented": [], + "supportsPresentation": false, + "final": [ + { + "connected": false, + "src": null, + "paused": true + } + ], + "playing": 0, + "warnings": [] + }, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after published core destruction", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "published", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after candidate core destruction", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "candidate", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "153.0.8010.12", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after published core destruction", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "published", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after candidate core destruction", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "candidate", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "155.0", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after published core destruction", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "published", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail delayed factory installation stays inert after candidate core destruction", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "registration": { + "core": "candidate", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "5eb99f9fef01f68be91dcd2dce812008805e44534908d167901cf28fd855e9ba" + }, + "destroyed": true, + "asynchronous": true, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "subscriptions": [], + "videos": 0, + "canvases": 0 + }, + "pixels": null, + "browser": "26.6", + "unhandledErrors": [], + "failedRequests": [] + } + ], + "results": "refactor/.cache/auto-thumbnail-registration-browser-legacy-results" + } + }, + "publicTypesChanged": false, + "dependenciesChanged": false, + "versionsChanged": false, + "limitations": [ + "Direct retained registrar is protected; core plugins.add destruction rejection policy is unchanged.", + "Per artifact: six real-core registration tests and 36 native resource/pixel tests with controlled hosts.", + "Existing callback-less Windows WebKit first two pixel cells remain diagnostic; no full pixel or physical device acceptance.", + "New test is wired into installed roster, but this turn used rebuilt bundles rather than a new installed tarball. Older installed maps are stale after this source change." + ] +} diff --git a/refactor/changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md b/refactor/changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md new file mode 100644 index 000000000..1b166a5ec --- /dev/null +++ b/refactor/changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md @@ -0,0 +1,68 @@ +# PKG-AUTO-THUMB-13:已销毁宿主的迟到注册 + +基线 `9ba3c82f99e6bad0e858f6c2e1d8624dfb725c8c`。 + +## 问题与实现 + +应用保留插件 registrar(例如异步加载模块后的回调),在播放器已经 destroy 后 +直接调用它,旧实现仍订阅 destroy、restart、video:loadedmetadata。旧 destroy +事件不会再次自然发生,因此新订阅没有可到达的正常销毁通知;之后若投递迟到的 +元数据事件,还会创建隐藏 video。修复前两个实际核心的 Chromium 用例都记录 +三个新增订阅和一个隐藏原生 video;Node 两个对应回归也失败。 + +入口在访问订阅或创建 session 之前读取公开 isDestroy,已经销毁时直接返回原有 +名称对象。函数仍为 async,返回 Promise,没有新增拒绝、公开方法或结果字段。 +内部 ThumbnailHost 的标志为可选,保留没有此字段的结构化旧宿主;false/缺省 +宿主仍可完成 JPEG 抽帧、发布和清理。没有修改核心 plugins.add 的已有拒绝策略, +也不把本修复描述成允许该方法在销毁后继续工作。 + +仅修改入口与内部宿主类型,资源生命周期仍由既有 session/extraction 模块负责, +无需引入新抽象或依赖。API-05/06 的失效实例资源边界得到保护,API-09/11 的 +公开声明、default 别名和分发入口保持不变。包 README 与架构说明同步更新。 + +## 测试、产物与工程入口 + +Node 24.21.0、Yarn Classic 1.22.22、TypeScript 5.9.3,Windows。 + +| 范围 | 结果 | +| --- | --- | +| 修复前四项 Node 回归 | 2 pass / 2 fail:残留三个订阅、访问不可用的销毁宿主成员 | +| 修复前真实核心 Chromium | 0 pass / 2 fail:新旧核心均新增订阅和隐藏 video | +| 完整 test:auto-thumbnail 源码/历史/类型集合 | 193 pass | +| 重建 main / legacy 的 auto-thumbnail-* 单元文件 | 各 88 pass | +| main 三引擎浏览器 | 42 pass / 0 skip / 0 retry | +| legacy 三引擎浏览器 | 42 pass / 0 skip / 0 retry | +| 包严格 TS、docs-tools TS、scoped lint | 通过 | +| 浏览器运行入口测试 | 18 pass | +| Yarn 严格工具链 | 通过 | + +每个产物的 42 项包括六项实际新旧核心迟到注册,另 36 项已有原生资源/像素测试 +使用受控插件宿主,验证元数据等待、抽帧/编码取消、成功 JPEG、别名和清理。 +没有把所有 42 项称为完整真实核心组合。浏览器版本为 Chromium153.0.8010.12、 +Firefox155.0、Windows WebKit26.6。新用例只观察订阅,不替换原生视频/抽帧方法。 +故意投递元数据后记录资源,最后补发 destroy 清理修复前复现,不依赖重复 +art.destroy() 自动清掉迟到资源。正常解码/编码仍由原生事件完成。 + +WebKit 无帧回调的既有两项像素用例继续只验后面三格,首两格仍是诊断; +AUTO-THUMB-PIXEL-01 不关闭,没有放宽原断言或新增跳过。 + +`yarn build artplayer-plugin-auto-thumbnail` 重建 main、legacy、ESM 及 docs/compiled +对应副本,未手改产物。test:unit 与 test:auto-thumbnail 加入新 Node 用例; +source 自动收集新 browser 文件,installedTests 显式加入它,入口测试和类型通过。 +本轮实际浏览器使用重建 main/legacy,不声称执行了新的 tarball 安装测试。 +既有安装 map 因生产源码改变需要重新打包生成,不能继续当作当前候选证据。 + +计划初次把未完成子任务追加到 doing 的03时被依赖校验拒绝,改为让 todo 的05 +同时依赖03及本任务,保留全部旧依赖。没有绕开校验或提前将子任务标完成。 +最终计划、风险、风险负例与提交审计必须通过。 + +精确输入、失败/最终报告及文件哈希见 +[auto-thumbnail-registration-validation.json](../baselines/auto-thumbnail-registration-validation.json)。 + +## 状态和回退 + +关闭 AUTO-THUMB-REGISTRATION-01;PKG-AUTO-THUMB-03 的原生首帧问题、05 的设备/ +组合、06 的安装与发布仍未完成。新增本任务后完成数 212,进行中21,待办43, +共276。没有修改包版本、Yarn 锁或依赖,没有推送、部署或发布。 + +回退本独立提交可同时恢复入口、内部类型、测试、构建文件及维护记录。 diff --git a/refactor/plan.md b/refactor/plan.md index b5b5c7c60..4627578bc 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -2,9 +2,9 @@ > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 -基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 275 项,范围 22 个包及工作区/示例。 +基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 276 项,范围 22 个包及工作区/示例。 -状态:todo 43 / doing 21 / blocked 0 / done 211 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 43 / doing 21 / blocked 0 / done 212 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -19,7 +19,7 @@ | 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, PKG-FACTORY-01 | | artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06, PKG-ASR-07, PKG-ASR-08, PKG-ASR-09, PKG-ASR-10 | | 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, SITE-AUDIO-01 | -| 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, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11, PKG-AUTO-THUMB-12 | +| 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, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11, PKG-AUTO-THUMB-12, PKG-AUTO-THUMB-13 | | artplayer-plugin-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06, PKG-CHAPTER-HOVER-01 | | artplayer-plugin-chromecast | 1.1.0 | PKG-CAST-01, PKG-CAST-02, PKG-CAST-03, PKG-CAST-04, PKG-CAST-05, PKG-CAST-06 | | artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, SITE-DANMUKU-01, PKG-DANMUKU-10, PKG-DANMUKU-11, PKG-DANMUKU-12, PKG-DANMUKU-MASK-LOAD-01 | @@ -180,7 +180,7 @@ | PKG-AUTO-THUMB-02 | artplayer-plugin-auto-thumbnail
建立特有行为与错误测试 | PKG-AUTO-THUMB-01, ENG-03, ENG-05 | 抽帧边界、短视频、失败、连续来源和销毁中的抽帧 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-AUTO-THUMB-03 | artplayer-plugin-auto-thumbnail
整理内部职责与资源 | PKG-AUTO-THUMB-02, CORE-09, CORE-19, PKG-AUTO-THUMB-07 | 隐藏 video、seek 队列、canvas 编码与 Blob URL 清理 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | doing | | PKG-AUTO-THUMB-04 | artplayer-plugin-auto-thumbnail
迁移自有源码和公开类型 | PKG-AUTO-THUMB-02, CORE-09, CORE-19, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-12, ENG-04, ENG-06, CORE-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11 | 抽帧结果和真实异步插件返回类型兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done | -| PKG-AUTO-THUMB-05 | artplayer-plugin-auto-thumbnail
验证新旧核心和组合 | PKG-AUTO-THUMB-04, CORE-22, PKG-AUTO-THUMB-12, PKG-AUTO-THUMB-03 | 旧核心/候选核心预览、多次加载无过期缩略图覆盖 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | +| PKG-AUTO-THUMB-05 | artplayer-plugin-auto-thumbnail
验证新旧核心和组合 | PKG-AUTO-THUMB-04, CORE-22, PKG-AUTO-THUMB-12, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-13 | 旧核心/候选核心预览、多次加载无过期缩略图覆盖 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-AUTO-THUMB-06 | artplayer-plugin-auto-thumbnail
验证分发并同步文档 | PKG-AUTO-THUMB-05, ENG-07 | auto.thumbnail.js、三种产物和内存/URL 证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo | | PKG-AUTO-THUMB-07 | artplayer-plugin-auto-thumbnail
终止抽帧时清空私有画布像素尺寸 | PKG-AUTO-THUMB-02, CORE-09, CORE-19 | 将私有 canvas 尺寸归入 job 清理;完成、取消、失败及分配重入均有回归,保留已编码 JPEG | 旧红新绿证明保留回调时画布归零,单项清理失败不阻断其他资源;原生取消和完成后 JPEG 仍可解码,原 API/时间公式/类型不变 | M | done | | PKG-AUTO-THUMB-08 | artplayer-plugin-auto-thumbnail
保留旧声明并提供准确异步类型入口 | PKG-AUTO-THUMB-02, ENG-04, ENG-06, CORE-07 | 保留实际 npm 1.1.0 根声明;新增同实现 /runtime 类型、编辑器声明及隔离安装消费者 | 原 Parameters/ReturnType/替代函数不变;准确 Promise 类型正反例、CJS/ESM 条件入口和声明解析通过;不把像素与设备缺口计为通过 | M | done | @@ -188,6 +188,7 @@ | PKG-AUTO-THUMB-10 | artplayer-plugin-auto-thumbnail
为渐进 JPEG 编码建立取消时限与独立资源归属 | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-07 | 拆分私有 canvas 编码模块,一次一帧的30秒编码时限,完成/失败/切源/销毁取消;迟到和重复回调无副作用 | 受控回调未返回的旧红新绿、正常JPEG和前一预览保持、同步重入/清理失败有回归;真实三浏览器编码/取消通过,公开时间公式与入口不变,首帧风险独立保留 | M | done | | PKG-AUTO-THUMB-11 | artplayer-plugin-auto-thumbnail
保证抽帧事件属性清理异常不截断其他资源释放 | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-10 | 独立清除事件属性、抽帧时限和原生帧回调;首个异常保留,过期回调失效,替换任务可继续 | 八个故障注入用例旧红新绿,实际main/legacy抽帧与三浏览器生命周期回归通过;文档和构建同步,首帧及整体资源验收不冒充完成 | M | done | | PKG-AUTO-THUMB-12 | artplayer-plugin-auto-thumbnail
终止隐藏视频元数据永久等待并释放资源 | PKG-AUTO-THUMB-02, CORE-09, CORE-19 | 元数据独立 30 秒期限、迟到回调失效、取消和错误清理、保留前一张可用预览;源码/重建产物及浏览器证据 | 旧红新绿、正常加载与阶段交接通过;实际挂起 HTTP 下销毁/切源/超时清理通过,不改变公开注册或帧时间公式,不关闭既有首帧风险 | M | done | +| PKG-AUTO-THUMB-13 | artplayer-plugin-auto-thumbnail
阻止已销毁实例的迟到插件注册重新持有资源 | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-04 | 入口检查公共销毁状态、保持异步名称结果;正常及无销毁标志的旧宿主兼容,源码/产物/真实核心回归 | 旧红新绿证明迟到注册零订阅且后续元数据不能创建隐藏视频;正常JPEG生成和清理不变,公开类型/入口不改,首帧问题保留 | M | done | ## 5 包迁移:artplayer-plugin-vtt-thumbnail @@ -726,3 +727,4 @@ - CI-NPM-01: [记录](changes/2026-09-15-CI-NPM-01-bundle.md) [记录](baselines/npm-bundle-validation.json) - SITE-AUDIO-01: [记录](changes/2026-09-15-SITE-AUDIO-01-guides.md) [记录](baselines/audio-docs-validation.json) - SITE-VTT-01: [记录](changes/2026-09-15-SITE-VTT-01-guides.md) [记录](baselines/vtt-docs-validation.json) +- PKG-AUTO-THUMB-13: [记录](changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md) [记录](baselines/auto-thumbnail-registration-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index 4cb2caa52..fe809efd2 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,19 @@ # 进度与证据 +## PKG-AUTO-THUMB-13 阻止销毁后迟到注册 + +修复保留registrar在宿主销毁后仍新增三个监听器、迟到元数据创建隐藏视频的问题。 +入口检查公开isDestroy,保持Promise/name结果;内部标志可选,不影响旧结构宿主。 +旧Node两项失败,真实新旧核心Chromium两项失败;修复后源码193通过,main/legacy +各88单元、各42三引擎浏览器通过。类型/lint/18入口测试通过,产物正常重建。 + +AUTO-THUMB-REGISTRATION-01关闭;原首帧像素问题及03/05/06仍未完成。新用例进入 +Node脚本和installed名单,但未冒充新tarball安装验证,旧安装map需重新生成。 +见[记录](changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md)和[证据](baselines/auto-thumbnail-registration-validation.json)。 + +212 done、21 doing、43 todo,共276;无依赖/版本变更,无推送、部署或发布。 + + ## SITE-VTT-01 缩略图双语指南与像素验证 新增中英文指南与本地侧栏,覆盖索引格式、相对路径、整秒包含边界、异步注册、 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 706098c72..79ecc6ee3 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -286,3 +286,4 @@ | HLS-EXAMPLE-01 | resolved / 已复现 | HLS示例换源累积销毁监听器且原生回退仍安装SDK控制插件 | SITE-HLS-01, EX-03 | | DASH-EXAMPLE-01 | resolved / 已复现 | DASH示例重复销毁SDK、无MSE仍注册插件且空语言回调抛错 | SITE-DASH-01, EX-03 | | CI-BROWSER-MODE-01 | resolved / 已复现 | Installed browser validation can inherit rendering substitutions and direct config loading bypasses launcher diagnostic policy | CI-01 | +| AUTO-THUMB-REGISTRATION-01 | resolved / 已复现 | Delayed direct factory installation on a destroyed host retains subscriptions and can restart hidden decoding | PKG-AUTO-THUMB-13 | diff --git a/refactor/risks.json b/refactor/risks.json index b050e41be..2fe53bd9b 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -6733,6 +6733,27 @@ "refactor/baselines/ci-rendering-policy-validation.json" ], "resolutionRationale": "Eleven new regression failures against the old implementation become passing; final eighteen entry tests and seventy-seven CI tests pass. Fresh twenty-package preparation and four actual installed Chromium lifecycle cases verify the normal path. Scope excludes future unreviewed flags and full CI acceptance." + }, + { + "id": "AUTO-THUMB-REGISTRATION-01", + "title": "Delayed direct factory installation on a destroyed host retains subscriptions and can restart hidden decoding", + "confirmation": "reproduced", + "status": "resolved", + "owners": [ + "PKG-AUTO-THUMB-13" + ], + "evidence": [ + "test/auto-thumbnail-registration.test.js", + "test/browser/auto-thumbnail-registration.spec.js", + "refactor/changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md", + "refactor/baselines/auto-thumbnail-registration-validation.json" + ], + "compatibleResolution": "Return the existing asynchronous name result before touching a destroyed host; retain live and structurally compatible hosts without isDestroy.", + "closureCriteria": "Prechange source and real-core browser failures plus fixed source, rebuilt main/legacy and normal extraction/type regressions; no change to full pixel/device gates.", + "resolutionEvidence": [ + "refactor/baselines/auto-thumbnail-registration-validation.json" + ], + "resolutionRationale": "Two prechange Node failures and both real-core Chromium failures reproduce retained subscriptions and native video creation. Source193, main/legacy88 each and three-engine42 each pass after the entry guard; live/absent-flag hosts retain successful generation. Public types and first-frame/device gates remain unchanged." } ] } diff --git a/refactor/tasks.json b/refactor/tasks.json index f0348e464..de22ce388 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -1888,7 +1888,8 @@ "PKG-AUTO-THUMB-04", "CORE-22", "PKG-AUTO-THUMB-12", - "PKG-AUTO-THUMB-03" + "PKG-AUTO-THUMB-03", + "PKG-AUTO-THUMB-13" ], "status": "todo", "risk": "H", @@ -5926,6 +5927,26 @@ "changes/2026-09-15-SITE-VTT-01-guides.md", "baselines/vtt-docs-validation.json" ] + }, + { + "id": "PKG-AUTO-THUMB-13", + "phase": "5 包迁移:artplayer-plugin-auto-thumbnail", + "title": "阻止已销毁实例的迟到插件注册重新持有资源", + "scope": [ + "artplayer-plugin-auto-thumbnail" + ], + "dependsOn": [ + "PKG-AUTO-THUMB-02", + "PKG-AUTO-THUMB-04" + ], + "status": "done", + "risk": "M", + "deliverable": "入口检查公共销毁状态、保持异步名称结果;正常及无销毁标志的旧宿主兼容,源码/产物/真实核心回归", + "acceptance": "旧红新绿证明迟到注册零订阅且后续元数据不能创建隐藏视频;正常JPEG生成和清理不变,公开类型/入口不改,首帧问题保留", + "evidence": [ + "changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md", + "baselines/auto-thumbnail-registration-validation.json" + ] } ] } diff --git a/scripts/browser-validation/scope.ts b/scripts/browser-validation/scope.ts index 27fac2151..c82926db4 100644 --- a/scripts/browser-validation/scope.ts +++ b/scripts/browser-validation/scope.ts @@ -35,6 +35,7 @@ export const installedTests = [ 'dash-sdk.spec.js', 'auto-thumbnail.spec.js', 'auto-thumbnail-lifecycle.spec.js', + 'auto-thumbnail-registration.spec.js', 'auto-thumbnail-pixels.spec.js', 'asr-audio.spec.js', 'asr-playback.spec.js', diff --git a/test/auto-thumbnail-registration.test.js b/test/auto-thumbnail-registration.test.js new file mode 100644 index 000000000..57edf1c05 --- /dev/null +++ b/test/auto-thumbnail-registration.test.js @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Verify late asynchronous installation ownership. +import test from 'node:test' +import { autoThumbnailCandidate, autoThumbnailEnvironment } from './helpers/auto-thumbnail.js' + +const implementation = await autoThumbnailCandidate() + +for (const mode of ['retained-host', 'unavailable-host-members']) { + test(`Auto-thumbnail registration after destroy is inert: ${mode}`, async () => { + const env = autoThumbnailEnvironment(implementation) + env.art.isDestroy = true + if (mode === 'unavailable-host-members') { + for (const key of ['on', 'off', 'option']) { + Object.defineProperty(env.art, key, { get() { + throw new Error(`Destroyed host ${key} must not be read`) + } }) + } + } + const pending = env.factory({ width: 80, number: 1 })(env.art) + assert.equal(typeof pending.then, 'function', 'Late registration retains the asynchronous public shape') + assert.deepEqual({ ...await pending }, { name: 'artplayerPluginAutoThumbnail' }) + assert.equal([...env.listeners.values()].reduce((count, set) => count + set.size, 0), 0) + env.art.emit('video:loadedmetadata') + assert.equal(env.videos.length, 0) + assert.equal(env.canvases.length, 0) + assert.equal(env.timers.size, 0) + assert.equal(env.updates.length, 0) + assert.equal(env.urls.size, 0) + }) +} + +for (const marker of [false, undefined]) { + test(`Auto-thumbnail live registration remains compatible with isDestroy=${marker}`, async () => { + const env = autoThumbnailEnvironment(implementation) + if (marker !== undefined) + env.art.isDestroy = marker + await env.factory({ width: 80, number: 1 })(env.art) + env.art.emit('video:loadedmetadata') + assert.equal(env.videos.length, 1) + env.metadata() + env.seeked() + env.finish() + assert.equal(env.updates.length, 1) + assert.equal(env.urls.size, 1) + env.art.emit('destroy') + assert.equal(env.urls.size, 0) + assert.equal(env.timers.size, 0) + assert.equal(env.attached.size, 0) + }) +} diff --git a/test/browser/README.md b/test/browser/README.md index 8391d8b7a..043977b1c 100644 --- a/test/browser/README.md +++ b/test/browser/README.md @@ -529,6 +529,14 @@ The first immediate-pixel WebKit failure remains in the refactor evidence. This is native desktop media playback, not physical audibility or full devices. `document-site.spec.js` also checks generated guide links and Run Code parameters. +## Auto-thumbnail delayed registration + +Auto-thumbnail's `auto-thumbnail-registration.spec.js` separately checks direct +registrars delivered after real core destruction. It keeps the Promise/name +contract, records zero subscriptions and verifies a late metadata event cannot +allocate a hidden native video. It uses the source/artifact/installed resolver; +this boundary test does not replace successful decoding or first-frame tests. + ## VTT Thumbnail documentation examples yarn test:browser document-vtt.spec.js document-site.spec.js --workers=1 validates both generated guides and their Run Code destinations after yarn build:docs. The guide test requires the original demo verbatim, preserves the original demo video and substitutes local VTT/SVG resources, and inspects actual preview screenshot pixels with pngjs. It covers red/blue crops from real mouse hover, inclusive endpoint selection via public setBar with a MouseEvent, a gap, destroy and a late event on both cores. It loads local dist explicitly and records its hash; this does not replace the verified installed-artifact suite, physical touch tests or full playback acceptance. diff --git a/test/browser/auto-thumbnail-registration.spec.js b/test/browser/auto-thumbnail-registration.spec.js new file mode 100644 index 000000000..d653c04d6 --- /dev/null +++ b/test/browser/auto-thumbnail-registration.spec.js @@ -0,0 +1,39 @@ +import { hash } from '../../refactor/scripts/releases.mjs' +import { autoThumbnailCandidate } from '../helpers/auto-thumbnail.js' +import { expect, test } from './fixtures.js' + +const implementation = await autoThumbnailCandidate() +for (const core of ['published', 'candidate']) { + test(`Auto-thumbnail delayed factory installation stays inert after ${core} core destruction`, async ({ page }, testInfo) => { + await page.goto(`/test/player.html?core=${core}`) + await page.addScriptTag({ content: implementation.code }) + const evidence = await page.evaluate(async () => { + window.createPlayer('/test/pattern.mp4') + const art = window.art + const install = window.artplayerPluginAutoThumbnail({ width: 80, number: 1 }) + art.destroy() + const subscriptions = [] + const on = art.on + art.on = function (name, callback) { + subscriptions.push(name) + return on.call(this, name, callback) + } + try { + const pending = install(art) + const asynchronous = typeof pending.then === 'function' + const result = await pending + art.emit('video:loadedmetadata') + const videos = document.querySelectorAll('video').length + const canvases = document.querySelectorAll('canvas').length + // Clean up the deliberate regression probe even when the old bundle leaks. + art.emit('destroy') + return { destroyed: art.isDestroy, asynchronous, result, subscriptions, videos, canvases } + } + finally { + art.on = on + } + }) + await testInfo.attach('auto-thumbnail-late-registration', { contentType: 'application/json', body: JSON.stringify({ core, sha256: hash(implementation.code), provenance: implementation.provenance, ...evidence }) }) + expect(evidence).toEqual({ destroyed: true, asynchronous: true, result: { name: 'artplayerPluginAutoThumbnail' }, subscriptions: [], videos: 0, canvases: 0 }) + }) +}