From 42ddead0cd7dbc254837262ae59a2016b2321413 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Tue, 15 Sep 2026 12:31:32 +0800 Subject: [PATCH] fix(auto-thumbnail): [PKG-AUTO-THUMB-12] release stalled metadata loads --- .../artplayer-plugin-auto-thumbnail.js | 2 +- .../artplayer-plugin-auto-thumbnail.legacy.js | 2 +- .../artplayer-plugin-auto-thumbnail.mjs | 22 +- package.json | 4 +- .../ARCHITECTURE.md | 14 +- .../artplayer-plugin-auto-thumbnail/README.md | 6 + .../dist/artplayer-plugin-auto-thumbnail.js | 2 +- .../artplayer-plugin-auto-thumbnail.legacy.js | 2 +- .../dist/artplayer-plugin-auto-thumbnail.mjs | 22 +- .../src/extraction.ts | 22 +- .../auto-thumbnail-metadata-validation.json | 5665 +++++++++++++++++ .../2026-09-15-PKG-AUTO-THUMB-12-metadata.md | 69 + refactor/plan.md | 10 +- refactor/progress.md | 7 + refactor/risk-table.md | 1 + refactor/risks.json | 19 + refactor/tasks.json | 24 +- test/auto-thumbnail-metadata.test.js | 102 + test/browser/auto-thumbnail-lifecycle.spec.js | 67 + test/browser/server.mjs | 6 + 20 files changed, 6048 insertions(+), 20 deletions(-) create mode 100644 refactor/baselines/auto-thumbnail-metadata-validation.json create mode 100644 refactor/changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md create mode 100644 test/auto-thumbnail-metadata.test.js diff --git a/docs/compiled/artplayer-plugin-auto-thumbnail.js b/docs/compiled/artplayer-plugin-auto-thumbnail.js index 838510cf5..5d5c3359c 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,i=!1;for(const r of e)try{r()}catch(n){i||(i=!0,t=n)}return{failed:i,failure:t}}function t(t,i){const n="function"===typeof i.requestVideoFrameCallback&&"function"===typeof i.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const n=e([()=>{i.onloadeddata=null},()=>{i.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&i.cancelVideoFrameCallback(t.frame)}]);if(n.failed)throw n.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,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(!n)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&i.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=i.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:i.cancelVideoFrameCallback(a)})(),d()&&(i.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,i.onloadeddata=null,i.onseeked=t.guard(()=>{if(!d()||i.seeking)return;const t=i.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=!n||i.readyState>=2;d()&&(f?s():i.onloadeddata=s)}}function i(e,i){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 i of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[i]=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);e.active()&&(n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:u,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 i=Math.floor(e.width*t.videoHeight/t.videoWidth),n=Math.trunc(10*e.width),r=Math.trunc(i*Math.ceil(e.number/10));if(![i,n,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:i,canvasWidth:n,canvasHeight:r}}(i,{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 l=document.createElement("canvas");if(e.own(()=>{l.height=0}),e.own(()=>{l.width=0}),!e.active())return;if(l.width=d,!e.active())return;if(l.height=c,!e.active())return;const s=l.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=t(e,n),h=function(e,t){let i;function n(){const e=i;i=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(n),r=>{if(!e.active())return;if(i)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};i=o;const a=()=>e.active()&&i===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(n(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,l);let m=0;const v=e.guard(()=>{m>=i.number?e.dispose():f(r*m/i.number,e.guard(()=>{s.drawImage(n,m%10*i.width,Math.floor(m/10)*u,i.width,u),e.active()&&h(t=>{e.publish(t,{height:u,column:10,number:i.number,width:i.width,scale:i.scale}),e.active()&&(m+=1,v())})}))});v()}),e.active()&&(n.src=i.url))}function n(t){return async n=>{const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,i){let n,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=n;n=void 0,e?.dispose()}return{start:function(){if(o)return;const d=n,c=[];let l=!1;const s={active:()=>!o&&!l&&n===s,own(t){if(l){const n=e([t]);n.failed&&i(n.failure)}else c.push(t)},dispose(){if(l)return;l=!0,n===s&&(n=void 0);const t=e(c.splice(0).reverse());t.failed&&i(t.failure)},fail(e){s.active()&&(s.dispose(),i(e))},guard:e=>(...t)=>{if(s.active())try{return e(...t)}catch(i){s.fail(i)}},publish(n,o){if(!s.active())return;if(!n)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(n);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&&i(t.failure),l}u(c)}};return n=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&&i(t.failure)},get closed(){return o}}}(e=>{n.thumbnails=e},r),a=[],u=()=>{const e=o.start();e&&e.guard(()=>{const r=function(e,t){const i={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(i.width))||i.width<=0||!Number.isFinite(Number(i.number))||i.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return i}(t,()=>n.option.url);e.active()&&i(e,r)})()},d=()=>{const t=a.splice(0),i=e([o.destroy,...t]);i.failed&&r(i.failure)};try{const e=[["destroy",d],["restart",o.cancel],["video:loadedmetadata",u]];for(const[t,i]of e){if(o.closed)break;a.push(()=>n.off(t,i)),n.on(t,i),o.closed&&n.off(t,i)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}}}return n.default=n,n}); +!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}); diff --git a/docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js b/docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js index ca9b20844..a63ec8319 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,i=Object.prototype.propertyIsEnumerable,r=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i;function o(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 a(e,a){let u,d,l=!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(l)return;const f=u,h=[];let m=!1;const v={active:()=>!l&&!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,l){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=d;d=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&r(e,a,o[a]);if(t)for(var a of t(o))i.call(o,a)&&r(e,a,o[a]);return e})({url:f},l))}catch(m){d===f&&(d=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(l)return;l=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);d=void 0,e.failed&&a(e.failure)},get closed(){return l}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let i;function r(){const e=i;i=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(r),(o,a)=>{if(r(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};i=u;const d=()=>e.active()&&i===u,l=e.guard(()=>{d()&&u.sought&&u.presented&&(r(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const i=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&t.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{d()&&i===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&i===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),d()&&(t.currentTime=o)},s=e.guard(()=>{d()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!d()||t.seeking)return;const e=t.currentTime;if(d())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,l()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;d()&&(f?s():t.onloadeddata=s)}}function d(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);e.active()&&(n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{n.onloadedmetadata=null;const i=n.duration,r=n.videoHeight,o=n.videoWidth,{height:a,canvasWidth:d,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}}(t,{duration:i,videoHeight:r,videoWidth:o});if(!e.active())return;n.width=o,n.height=r,n.style.width=`${o}px`,n.style.height=`${r}px`;const c=document.createElement("canvas");if(e.own(()=>{c.height=0}),e.own(()=>{c.width=0}),!e.active())return;if(c.width=d,!e.active())return;if(c.height=l,!e.active())return;const s=c.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=u(e,n),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,c);let m=0;const v=e.guard(()=>{m>=t.number?e.dispose():f(i*m/t.number,e.guard(()=>{s.drawImage(n,m%10*t.width,Math.floor(m/10)*a,t.width,a),e.active()&&h(n=>{e.publish(n,{height:a,column:10,number:t.number,width:t.width,scale:t.scale}),e.active()&&(m+=1,v())})}))});v()}),e.active()&&(n.src=t.url))}function l(e){return t=>{return n=null,i=null,r=function*(){const n=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),i=a(e=>{t.thumbnails=e},n),r=[],u=()=>{const n=i.start();n&&n.guard(()=>{const i=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()&&d(n,i)})()},l=()=>{const e=r.splice(0),t=o([i.destroy,...e]);t.failed&&n(t.failure)};try{const e=[["destroy",l],["restart",i.cancel],["video:loadedmetadata",u]];for(const[n,o]of e){if(i.closed)break;r.push(()=>t.off(n,o)),t.on(n,o),i.closed&&t.off(n,o)}}catch(c){throw l(),c}return{name:"artplayerPluginAutoThumbnail"}},new Promise((e,t)=>{var o=e=>{try{u(r.next(e))}catch(n){t(n)}},a=e=>{try{u(r.throw(e))}catch(n){t(n)}},u=t=>t.done?e(t.value):Promise.resolve(t.value).then(o,a);u((r=r.apply(n,i)).next())});var n,i,r}}return l.default=l,l}); +!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}); diff --git a/docs/compiled/artplayer-plugin-auto-thumbnail.mjs b/docs/compiled/artplayer-plugin-auto-thumbnail.mjs index 820c93f06..77c5afd91 100644 --- a/docs/compiled/artplayer-plugin-auto-thumbnail.mjs +++ b/docs/compiled/artplayer-plugin-auto-thumbnail.mjs @@ -317,10 +317,19 @@ function extract(job, config) { const video = createVideo(job); if (!job.active()) return; + let metadataTimer = null; + const clearMetadataTimer = () => { + const previous = metadataTimer; + metadataTimer = null; + if (previous !== null) + clearTimeout(previous); + }; + job.own(clearMetadataTimer); video.onerror = job.guard(() => { throw video.error || new Error("Auto-thumbnail media failed to load"); }); video.onloadedmetadata = job.guard(() => { + clearMetadataTimer(); video.onloadedmetadata = null; const duration = video.duration; const videoHeight = video.videoHeight; @@ -379,8 +388,17 @@ function extract(job, config) { }); seek(); }); - if (job.active()) - video.src = config.url; + if (!job.active()) + return; + metadataTimer = setTimeout(job.guard(() => { + if (metadataTimer !== null) + throw new Error("Auto-thumbnail metadata timed out"); + }), 3e4); + if (!job.active()) { + clearMetadataTimer(); + return; + } + video.src = config.url; } function artplayerPluginAutoThumbnail(option) { return async (art) => { diff --git a/package.json b/package.json index ea6dd27e6..19537c6b2 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-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: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: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-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-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.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 251acebd1..99a5cc72b 100644 --- a/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md +++ b/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md @@ -24,6 +24,13 @@ for video processing. handlers, and the frame/encode sequence. Each valid draw is encoded before the next seek. The final decoder is paused, cleared and reset; the final sheet URL remains owned by the session until another usable sheet replaces it or destroy. + Metadata loading has a separate 30-second deadline, started before assigning + the URL and canceled when metadata arrives or the job ends. A queued deadline + becomes inert after that phase; it cannot cancel frame reading or encoding. + A stalled load reports through the existing warning callback and disposes its + decoder without replacing the previous usable sheet. Timer-registration reentry + cannot restart loading after destruction. This does not time out registration, + change the public result, retry the URL, or impose a total extraction deadline. The private canvas is also job-owned: cancellation, failure and normal completion reset both dimensions to zero, even if an encoding callback still retains it. Width and height resets are independent cleanup actions, so one throwing setter @@ -104,9 +111,10 @@ separate30-second encoding deadline starting just before toBlob. This is an intentional bound on formerly unbounded pending encoding; it does not shorten the frame wait or change the public frame-time formula. Very slow encodes can now warn and retain the last preview instead of retaining the decoder indefinitely. -Initial metadata/network acquisition remains governed by media errors and session -cancellation, not this encoding deadline. Neither timeout guarantees browser-level -GPU/encoder reclamation or exact background-tab wall-clock scheduling. +Initial metadata/network acquisition has its own 30-second deadline, as described +above, and also ends on media errors or session cancellation. None of these +deadlines guarantees browser-level GPU/encoder reclamation or exact background-tab +wall-clock scheduling. The previous empty first encode is removed. Encoding is serial, duplicate native callbacks are consumed once, and source replacement/destruction invalidates old diff --git a/packages/artplayer-plugin-auto-thumbnail/README.md b/packages/artplayer-plugin-auto-thumbnail/README.md index 9d5ce459f..7666fe1da 100644 --- a/packages/artplayer-plugin-auto-thumbnail/README.md +++ b/packages/artplayer-plugin-auto-thumbnail/README.md @@ -39,6 +39,12 @@ the runtime type for historical callers but remains ignored: the video aspect ratio determines sheet height. Numeric strings retain their old JavaScript coercion behavior but are not advertised as numeric TypeScript options. +The private decoder stops if metadata does not arrive within 30 seconds. Frame +readiness and each JPEG encoding operation also have their own 30-second limit. +Failures use the existing console warning and retain the last usable thumbnail +sheet; registration still resolves before processing. Restarting or destroying +the player cancels the active wait and releases the private decoder. + The runtime entry supports default ESM imports and CommonJS `import autoThumbnail = require('artplayer-plugin-auto-thumbnail/runtime')`. It exports the `Option`, `Result`, `Factory`, and `RuntimeFactory` types. `Factory` 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 838510cf5..5d5c3359c 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,i=!1;for(const r of e)try{r()}catch(n){i||(i=!0,t=n)}return{failed:i,failure:t}}function t(t,i){const n="function"===typeof i.requestVideoFrameCallback&&"function"===typeof i.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const n=e([()=>{i.onloadeddata=null},()=>{i.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&i.cancelVideoFrameCallback(t.frame)}]);if(n.failed)throw n.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,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(!n)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&i.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=i.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:i.cancelVideoFrameCallback(a)})(),d()&&(i.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,i.onloadeddata=null,i.onseeked=t.guard(()=>{if(!d()||i.seeking)return;const t=i.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=!n||i.readyState>=2;d()&&(f?s():i.onloadeddata=s)}}function i(e,i){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 i of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[i]=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);e.active()&&(n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:u,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 i=Math.floor(e.width*t.videoHeight/t.videoWidth),n=Math.trunc(10*e.width),r=Math.trunc(i*Math.ceil(e.number/10));if(![i,n,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:i,canvasWidth:n,canvasHeight:r}}(i,{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 l=document.createElement("canvas");if(e.own(()=>{l.height=0}),e.own(()=>{l.width=0}),!e.active())return;if(l.width=d,!e.active())return;if(l.height=c,!e.active())return;const s=l.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=t(e,n),h=function(e,t){let i;function n(){const e=i;i=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(n),r=>{if(!e.active())return;if(i)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};i=o;const a=()=>e.active()&&i===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(n(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,l);let m=0;const v=e.guard(()=>{m>=i.number?e.dispose():f(r*m/i.number,e.guard(()=>{s.drawImage(n,m%10*i.width,Math.floor(m/10)*u,i.width,u),e.active()&&h(t=>{e.publish(t,{height:u,column:10,number:i.number,width:i.width,scale:i.scale}),e.active()&&(m+=1,v())})}))});v()}),e.active()&&(n.src=i.url))}function n(t){return async n=>{const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,i){let n,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=n;n=void 0,e?.dispose()}return{start:function(){if(o)return;const d=n,c=[];let l=!1;const s={active:()=>!o&&!l&&n===s,own(t){if(l){const n=e([t]);n.failed&&i(n.failure)}else c.push(t)},dispose(){if(l)return;l=!0,n===s&&(n=void 0);const t=e(c.splice(0).reverse());t.failed&&i(t.failure)},fail(e){s.active()&&(s.dispose(),i(e))},guard:e=>(...t)=>{if(s.active())try{return e(...t)}catch(i){s.fail(i)}},publish(n,o){if(!s.active())return;if(!n)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(n);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&&i(t.failure),l}u(c)}};return n=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&&i(t.failure)},get closed(){return o}}}(e=>{n.thumbnails=e},r),a=[],u=()=>{const e=o.start();e&&e.guard(()=>{const r=function(e,t){const i={url:e.url||t(),width:e.width||160,number:e.number||100,scale:e.scale||1};if(!Number.isFinite(Number(i.width))||i.width<=0||!Number.isFinite(Number(i.number))||i.number<=0)throw new TypeError("Auto-thumbnail width and number must be finite positive numbers");return i}(t,()=>n.option.url);e.active()&&i(e,r)})()},d=()=>{const t=a.splice(0),i=e([o.destroy,...t]);i.failed&&r(i.failure)};try{const e=[["destroy",d],["restart",o.cancel],["video:loadedmetadata",u]];for(const[t,i]of e){if(o.closed)break;a.push(()=>n.off(t,i)),n.on(t,i),o.closed&&n.off(t,i)}}catch(c){throw d(),c}return{name:"artplayerPluginAutoThumbnail"}}}return n.default=n,n}); +!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}); 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 ca9b20844..a63ec8319 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,i=Object.prototype.propertyIsEnumerable,r=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i;function o(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 a(e,a){let u,d,l=!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(l)return;const f=u,h=[];let m=!1;const v={active:()=>!l&&!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,l){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=d;d=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&r(e,a,o[a]);if(t)for(var a of t(o))i.call(o,a)&&r(e,a,o[a]);return e})({url:f},l))}catch(m){d===f&&(d=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(l)return;l=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);d=void 0,e.failed&&a(e.failure)},get closed(){return l}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let i;function r(){const e=i;i=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(r),(o,a)=>{if(r(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};i=u;const d=()=>e.active()&&i===u,l=e.guard(()=>{d()&&u.sought&&u.presented&&(r(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const i=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&t.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{d()&&i===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&i===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),d()&&(t.currentTime=o)},s=e.guard(()=>{d()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!d()||t.seeking)return;const e=t.currentTime;if(d())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,l()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;d()&&(f?s():t.onloadeddata=s)}}function d(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);e.active()&&(n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{n.onloadedmetadata=null;const i=n.duration,r=n.videoHeight,o=n.videoWidth,{height:a,canvasWidth:d,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}}(t,{duration:i,videoHeight:r,videoWidth:o});if(!e.active())return;n.width=o,n.height=r,n.style.width=`${o}px`,n.style.height=`${r}px`;const c=document.createElement("canvas");if(e.own(()=>{c.height=0}),e.own(()=>{c.width=0}),!e.active())return;if(c.width=d,!e.active())return;if(c.height=l,!e.active())return;const s=c.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=u(e,n),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,c);let m=0;const v=e.guard(()=>{m>=t.number?e.dispose():f(i*m/t.number,e.guard(()=>{s.drawImage(n,m%10*t.width,Math.floor(m/10)*a,t.width,a),e.active()&&h(n=>{e.publish(n,{height:a,column:10,number:t.number,width:t.width,scale:t.scale}),e.active()&&(m+=1,v())})}))});v()}),e.active()&&(n.src=t.url))}function l(e){return t=>{return n=null,i=null,r=function*(){const n=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),i=a(e=>{t.thumbnails=e},n),r=[],u=()=>{const n=i.start();n&&n.guard(()=>{const i=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()&&d(n,i)})()},l=()=>{const e=r.splice(0),t=o([i.destroy,...e]);t.failed&&n(t.failure)};try{const e=[["destroy",l],["restart",i.cancel],["video:loadedmetadata",u]];for(const[n,o]of e){if(i.closed)break;r.push(()=>t.off(n,o)),t.on(n,o),i.closed&&t.off(n,o)}}catch(c){throw l(),c}return{name:"artplayerPluginAutoThumbnail"}},new Promise((e,t)=>{var o=e=>{try{u(r.next(e))}catch(n){t(n)}},a=e=>{try{u(r.throw(e))}catch(n){t(n)}},u=t=>t.done?e(t.value):Promise.resolve(t.value).then(o,a);u((r=r.apply(n,i)).next())});var n,i,r}}return l.default=l,l}); +!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}); 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 820c93f06..77c5afd91 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 @@ -317,10 +317,19 @@ function extract(job, config) { const video = createVideo(job); if (!job.active()) return; + let metadataTimer = null; + const clearMetadataTimer = () => { + const previous = metadataTimer; + metadataTimer = null; + if (previous !== null) + clearTimeout(previous); + }; + job.own(clearMetadataTimer); video.onerror = job.guard(() => { throw video.error || new Error("Auto-thumbnail media failed to load"); }); video.onloadedmetadata = job.guard(() => { + clearMetadataTimer(); video.onloadedmetadata = null; const duration = video.duration; const videoHeight = video.videoHeight; @@ -379,8 +388,17 @@ function extract(job, config) { }); seek(); }); - if (job.active()) - video.src = config.url; + if (!job.active()) + return; + metadataTimer = setTimeout(job.guard(() => { + if (metadataTimer !== null) + throw new Error("Auto-thumbnail metadata timed out"); + }), 3e4); + if (!job.active()) { + clearMetadataTimer(); + return; + } + video.src = config.url; } function artplayerPluginAutoThumbnail(option) { return async (art) => { diff --git a/packages/artplayer-plugin-auto-thumbnail/src/extraction.ts b/packages/artplayer-plugin-auto-thumbnail/src/extraction.ts index 07f11308f..791b4fa60 100644 --- a/packages/artplayer-plugin-auto-thumbnail/src/extraction.ts +++ b/packages/artplayer-plugin-auto-thumbnail/src/extraction.ts @@ -8,10 +8,19 @@ export default function extract(job: ExtractionJob, config: ExtractionConfig) { const video = createVideo(job) if (!job.active()) return + let metadataTimer: ReturnType | null = null + const clearMetadataTimer = () => { + const previous = metadataTimer + metadataTimer = null + if (previous !== null) + clearTimeout(previous) + } + job.own(clearMetadataTimer) video.onerror = job.guard(() => { throw video.error || new Error('Auto-thumbnail media failed to load') }) video.onloadedmetadata = job.guard(() => { + clearMetadataTimer() video.onloadedmetadata = null const duration = video.duration const videoHeight = video.videoHeight @@ -70,6 +79,15 @@ export default function extract(job: ExtractionJob, config: ExtractionConfig) { }) seek() }) - if (job.active()) - video.src = config.url + if (!job.active()) + return + metadataTimer = setTimeout(job.guard(() => { + if (metadataTimer !== null) + throw new Error('Auto-thumbnail metadata timed out') + }), 30000) + if (!job.active()) { + clearMetadataTimer() + return + } + video.src = config.url } diff --git a/refactor/baselines/auto-thumbnail-metadata-validation.json b/refactor/baselines/auto-thumbnail-metadata-validation.json new file mode 100644 index 000000000..673bf6634 --- /dev/null +++ b/refactor/baselines/auto-thumbnail-metadata-validation.json @@ -0,0 +1,5665 @@ +{ + "schemaVersion": 1, + "task": "PKG-AUTO-THUMB-12", + "recordedAt": "2026-09-15T04:30:49.805Z", + "baseCommit": "d3aa4bbfe4d9931249dce5e6a43afbe54b39800f", + "node": "v24.21.0", + "yarn": "1.22.22", + "fingerprints": { + "package.json": "10d68b834c3fd60447ea418981b418e88be57edfceb73ab0f131fb6f484c3396", + "packages/artplayer-plugin-auto-thumbnail/src/extraction.ts": "61b54fb3322cd4eb374797558db57dc20df4b1bc5b5e49dfff5ddf81cabf285c", + "packages/artplayer-plugin-auto-thumbnail/README.md": "5317758381c6a4b737dd7b59d1065f48274ae0fd9bd62e88666fc3168abd2131", + "packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md": "c5b766bdceed975145f9f5850cf1f30e772bd38c907d58807a4d6f32b4b5a919", + "test/auto-thumbnail-metadata.test.js": "107b84c999e794586fbc899c1a860dcee54ccdcda6b8ff96fc702f95657a1c6d", + "test/browser/auto-thumbnail-lifecycle.spec.js": "7011b1df14dc73456ffaec56f5775d5d6be0a5b5c5dd63f3156c98375151a8ab", + "test/browser/server.mjs": "d00afdce6917b7636a735a3f01518a803fc43a89378ab35872bda0d0f315362b" + }, + "artifacts": [ + { + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "docsCopy": "docs/compiled/artplayer-plugin-auto-thumbnail.js", + "previousSha256": "4c8bdd75f49ad0514167e1b67c6941236c27837d09dd1d5d69d0345d69fa0357" + }, + { + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "docsCopy": "docs/compiled/artplayer-plugin-auto-thumbnail.legacy.js", + "previousSha256": "389239c482d89639c70c796f2220a552ed8b1a87062fc5950cb03dc14c2b525c" + }, + { + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs", + "sha256": "27c66d600e9b282063941de762906529e118bc47545d0fdd1b9c249ef9c648b9", + "docsCopy": "docs/compiled/artplayer-plugin-auto-thumbnail.mjs", + "previousSha256": "7c4ea237f03eaae0cbc06b27782ba03ee8e3639ec78f0de03c11841467f22a17" + } + ], + "logs": { + "old-main": { + "file": "refactor/.cache/auto-thumbnail-metadata-old-main.log", + "sha256": "17836588f859a80c664f080c6067d4aa9ea7dd5fc69790966462e29e6b874645" + }, + "all-unit": { + "file": "refactor/.cache/auto-thumbnail-metadata-all-unit.log", + "sha256": "c290f8a2596a0c46e5e4d4fdcc68bba2ff5b9d25aedaf69f3cbe5abe97c90862" + }, + "js-unit": { + "file": "refactor/.cache/auto-thumbnail-metadata-js-unit.log", + "sha256": "eed7f7c5e79d898fcb91dac61cabfb46778b6ccf78ec826750aeed95e02e0733" + }, + "legacy.js-unit": { + "file": "refactor/.cache/auto-thumbnail-metadata-legacy.js-unit.log", + "sha256": "e23c6e9a3cec308fff1064764b9d31cc744aaa8cc1adcb5dc61f737feadc8731" + }, + "build": { + "file": "refactor/.cache/auto-thumbnail-metadata-build.log", + "sha256": "e912e529c316bd37cd7c5552df7ea907dfcb8a4d780e5cd94687ab21ad4c55e8" + }, + "toolchain": { + "file": "refactor/.cache/auto-thumbnail-metadata-toolchain.log", + "sha256": "46a0f2b5ccc6deb712d1d57bf61a72ba5fd1fae5c1bac0937ed5c32094949470" + } + }, + "unit": { + "oldMain": { + "passed": 0, + "failed": 7, + "exitCode": 1 + }, + "source": { + "passed": 189, + "failed": 0, + "exitCode": 0 + }, + "main": { + "passed": 82, + "failed": 0, + "exitCode": 0 + }, + "legacy": { + "passed": 82, + "failed": 0, + "exitCode": 0 + } + }, + "checks": { + "packageTypes": 0, + "scopedLint": 0, + "strictToolchain": 0, + "packageBuild": 0 + }, + "browsers": { + "failedInterceptionHarness": { + "file": "refactor/.cache/auto-thumbnail-metadata-interception-browser/report.json", + "sha256": "497fdaad8d069f73775de6f80388945637fbd7e354d26aa78d9b76939a64a2c1", + "stats": { + "startTime": "2026-09-15T04:24:57.185Z", + "duration": 95976.31199999999, + "expected": 33, + "skipped": 0, + "unexpected": 3, + "flaky": 0 + }, + "errors": [], + "tests": [ + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "timeout", + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "destroy", + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "restart", + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "timeout", + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "destroy", + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "restart", + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "webkit", + "status": "timedOut", + "retry": 0, + "errors": [ + { + "message": "\u001b[31mTest timeout of 20000ms exceeded.\u001b[39m" + }, + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-lifecycle.spec.js", + "column": 26, + "line": 15 + }, + "message": "Error: page.waitForRequest: Test timeout of 20000ms exceeded.\n=========================== logs ===========================\nwaiting for request \"**/pending-thumbnail-metadata.mp4\"\n============================================================\n\n 13 | })\n 14 | await page.addScriptTag({ content: implementation.code })\n> 15 | const request = page.waitForRequest('**/pending-thumbnail-metadata.mp4')\n | ^\n 16 | await page.evaluate(async () => {\n 17 | const schedule = window.setTimeout\n 18 | const clear = window.clearTimeout\n at D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-lifecycle.spec.js:15:26" + } + ], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "webkit", + "status": "timedOut", + "retry": 0, + "errors": [ + { + "message": "\u001b[31mTest timeout of 20000ms exceeded.\u001b[39m" + }, + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-lifecycle.spec.js", + "column": 26, + "line": 15 + }, + "message": "Error: page.waitForRequest: Test timeout of 20000ms exceeded.\n=========================== logs ===========================\nwaiting for request \"**/pending-thumbnail-metadata.mp4\"\n============================================================\n\n 13 | })\n 14 | await page.addScriptTag({ content: implementation.code })\n> 15 | const request = page.waitForRequest('**/pending-thumbnail-metadata.mp4')\n | ^\n 16 | await page.evaluate(async () => {\n 17 | const schedule = window.setTimeout\n 18 | const clear = window.clearTimeout\n at D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-lifecycle.spec.js:15:26" + } + ], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "webkit", + "status": "timedOut", + "retry": 0, + "errors": [ + { + "message": "\u001b[31mTest timeout of 20000ms exceeded.\u001b[39m" + }, + { + "location": { + "file": "D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-lifecycle.spec.js", + "column": 26, + "line": 15 + }, + "message": "Error: page.waitForRequest: Test timeout of 20000ms exceeded.\n=========================== logs ===========================\nwaiting for request \"**/pending-thumbnail-metadata.mp4\"\n============================================================\n\n 13 | })\n 14 | await page.addScriptTag({ content: implementation.code })\n> 15 | const request = page.waitForRequest('**/pending-thumbnail-metadata.mp4')\n | ^\n 16 | await page.evaluate(async () => {\n 17 | const schedule = window.setTimeout\n 18 | const clear = window.clearTimeout\n at D:\\github\\ArtPlayer\\test\\browser\\auto-thumbnail-lifecycle.spec.js:15:26" + } + ], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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.6011924, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 238, + 14, + 14, + 255 + ] + }, + { + "time": 3.2030186, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 16, + 16, + 255 + ] + }, + { + "time": 4.8025276, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4017353, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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.6017615, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 3.2016138, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 4.8009896, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4024974, + "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": [] + } + } + ] + }, + "main": { + "file": "refactor/.cache/auto-thumbnail-metadata-main-browser/report.json", + "sha256": "d2827f4b098940e99c2ab03edfb89e2fac0a47668e73dd6ee209b93cf1d6dc59", + "stats": { + "startTime": "2026-09-15T04:27:46.556Z", + "duration": 34550.257000000005, + "expected": 36, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "errors": [], + "tests": [ + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fd6199113f507b4a5687-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "timeout", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fd6199113f507b4a5687-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-db1643714b42d7109817-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "destroy", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-db1643714b42d7109817-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fc9f1151d1a7f7955d37-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "restart", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fc9f1151d1a7f7955d37-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-dbb5fa535b67bd4f5ae4-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "timeout", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-dbb5fa535b67bd4f5ae4-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-c064c2f74bff1d14fb6a-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "destroy", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-c064c2f74bff1d14fb6a-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1118a10c61b3609f4370-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "restart", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1118a10c61b3609f4370-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "timeout", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-43dc5b61f85ae7299365-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "destroy", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1c71b2365730194022fb-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "scenario": "restart", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-a6308186be8e0eb6306a-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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.6013046, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 238, + 14, + 14, + 255 + ] + }, + { + "time": 3.2018706, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 16, + 16, + 255 + ] + }, + { + "time": 4.8013337, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4007299, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "615c7efd3c7177a5e451ef2fe148b8e8ec91562ef8cc4864b6135b3046105f46" + }, + "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.0012229, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 0 + ] + }, + { + "time": 1.6017072, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 3.2019802, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 4.8009845, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.408194, + "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": [] + } + } + ] + }, + "legacy": { + "file": "refactor/.cache/browser/report.json", + "sha256": "29c2c8bca066bf7efca5c98e55ce8ffc382e8fa465b321b62808644ff759ec02", + "stats": { + "startTime": "2026-09-15T04:28:52.776Z", + "duration": 34832.753000000004, + "expected": 36, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "errors": [], + "tests": [ + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fd6199113f507b4a5687-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "timeout", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fd6199113f507b4a5687-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-db1643714b42d7109817-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "destroy", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-db1643714b42d7109817-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fc9f1151d1a7f7955d37-0", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "restart", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-fc9f1151d1a7f7955d37-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "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": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-dbb5fa535b67bd4f5ae4-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "timeout", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-dbb5fa535b67bd4f5ae4-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-c064c2f74bff1d14fb6a-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "destroy", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-c064c2f74bff1d14fb6a-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1118a10c61b3609f4370-0", + "resourceType": "media", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "restart", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1118a10c61b3609f4370-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "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 complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on timeout", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "timeout", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-43dc5b61f85ae7299365-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [ + [ + "ArtPlayer auto-thumbnail failed:", + "Error: Auto-thumbnail metadata timed out" + ] + ], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on destroy", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "destroy", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-1c71b2365730194022fb-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native pending metadata releases on restart", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "deadline": { + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "scenario": "restart", + "mediaRequests": [ + { + "path": "/test/pending-thumbnail-metadata.mp4", + "range": "bytes=0-", + "method": "GET" + } + ], + "virtualDeadline": true, + "before": { + "connected": true, + "readyState": 0, + "source": "/test/pending-thumbnail-metadata.mp4?case=df35481b0fd839119f1d-a6308186be8e0eb6306a-0", + "timers": 1 + }, + "after": { + "connected": false, + "readyState": 0, + "source": null, + "metadataHandler": null, + "errorHandler": null, + "timers": 0, + "canvases": 0 + }, + "result": { + "name": "artplayerPluginAutoThumbnail" + }, + "warnings": [], + "updates": 0 + } + }, + { + "title": "Auto-thumbnail candidate native destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-destroy owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native frame-restart owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native alias-complete owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate native encoding-timeout owns decoder and encoded URLs", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [] + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "153.0.8010.12", + "pageErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "pixels": { + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "155.0", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented auto-thumbnail-timeline pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "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.6020004, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 238, + 14, + 14, + 255 + ] + }, + { + "time": 3.2010176, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 16, + 16, + 255 + ] + }, + { + "time": 4.8013539, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4013167, + "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": [] + } + }, + { + "title": "Auto-thumbnail candidate verifies presented pattern pixels and records fallback limits", + "project": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": "26.6", + "pageErrors": [], + "failedRequests": [], + "pixels": { + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219", + "provenance": { + "kind": "explicit-artifact", + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "c7c893f0e9f21b0f539d19e3606f0326cace67314b35c80fd6e6d1f061494219" + }, + "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.0011706, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 0, + 0, + 0, + 0 + ] + }, + { + "time": 1.6013216, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 3.2018444, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 4.8013614, + "duration": 8, + "connected": true, + "visibility": "hidden", + "display": "block", + "width": 320, + "height": 180, + "raw": [ + 16, + 15, + 239, + 255 + ] + }, + { + "time": 6.4016004, + "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": [] + } + } + ] + } + }, + "limitations": [ + "Metadata deadline delivery is controlled in tests; native media/HTTP and decoder teardown are real, not 30 seconds of wall time.", + "First WebKit harness failure was waiting for browser-visible requests; final test observes the real HTTP server instead.", + "Windows WebKit initial-frame pixel gap remains AUTO-THUMB-PIXEL-01; successful lifecycle checks do not close task03.", + "No remote CI, physical-device, installed-consumer or release completion claimed." + ] +} diff --git a/refactor/changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md b/refactor/changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md new file mode 100644 index 000000000..444a29468 --- /dev/null +++ b/refactor/changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md @@ -0,0 +1,69 @@ +# PKG-AUTO-THUMB-12: bound the private decoder metadata wait + +Implementation baseline: d3aa4bbfe4d9931249dce5e6a43afbe54b39800f. + +## Defect and implementation + +The private video could remain attached indefinitely if its request produced +neither loadedmetadata nor error. Frame and JPEG deadlines start later, so they +did not cover this phase. The seven final regression cases fail against the +prechange committed main artifact and pass against the changed implementation. +An earlier test draft omitted the required options object in one case; that draft +is not used as the final seven-case reproduction evidence. + +`src/extraction.ts` now owns a 30-second metadata timer from immediately before +URL assignment until loadedmetadata or job disposal. Its callback becomes inert +when the phase ends, including if already queued before cancellation. The job's +existing disposal releases the handlers, resets and removes the decoder. Timer +registration that synchronously destroys the session cannot resume URL loading. +No extra module or dependency is needed: extraction already owns this phase. + +This intentionally replaces an unbounded failed-load wait with the same warning +and cleanup policy used for frames and encoding. A slow load may now stop after +30 seconds. The last usable sheet remains available until replacement or destroy. +The public factory, Promise timing/result, options, ten-column sheet layout, frame +time formula, declarations, package entrypoints and dependency versions are +unchanged. Timers remain subject to browser/background scheduling; this is not a +guarantee of immediate network/GPU reclamation or an aggregate extraction timeout. + +## Verification + +The new unit cases cover no metadata, late metadata/timer callbacks, success, +native errors, destroy/restart, retained previous sheets and synchronous timer +registration reentry. They are part of `test:auto-thumbnail` and `test:unit`. +The existing native lifecycle file adds pending-metadata cases for timeout, +destroy and restart and remains in both source and installed CI collections. + +The first browser harness used Playwright interception; Chromium/Firefox passed, +but three WebKit cases timed out waiting for `page.waitForRequest` before testing +cleanup. This is retained as failed harness evidence, not a plugin pass. The final +fixture uses a real server response that sends no metadata and confirms requests +through the server log. Its 10-second socket inactivity bound prevents failed +tests from leaving permanent connections. The test explicitly invokes the captured +30-second callback; it verifies native video cleanup, not 30 seconds of wall time. +Existing successful decoding/encoding tests continue to use real media events. + +Commands and exact counts/artifact hashes are in the +[validation record](../baselines/auto-thumbnail-metadata-validation.json). +Source tests pass 189/189; rebuilt main and legacy each pass 82 targeted unit +cases and 36 browser cases across Chromium, Firefox and Windows WebKit. The latter +includes nine pending-metadata cases per artifact. Neither final browser run has +unhandled page errors. Failed-request observations (14 main, 16 legacy) remain in +the record, including deliberately canceled pending loads; these are not reports +of zero network failures. Package strict types, scoped lint and strict toolchain +checks pass. Existing WebKit pixel assertions still exclude the unresolved first +two cells when presentation callbacks are unavailable, as before this fix. +Normal `yarn build artplayer-plugin-auto-thumbnail` regenerates main, legacy and +ESM plus matching docs copies. There are no hand-edited bundles. + +## Maintenance and remaining gates + +The package README and ARCHITECTURE explain the independent metadata/frame/encode +deadlines and failure policy. The Windows WebKit first-frame pixel issue remains +open: existing pixel tests retain their documented fallback limitations. This +fix does not complete task03, the package combination matrix, remote CI, device +validation, or release reviews. No publishing, pushing or deployment is included. + +Revert this task's dedicated commit to restore source/tests/docs and generated +artifacts together. Future changes to metadata loading should rerun the unit +suite and native lifecycle/pixel tests against rebuilt main and legacy outputs. diff --git a/refactor/plan.md b/refactor/plan.md index 0033b470f..f231d12b6 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -2,9 +2,9 @@ > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 -基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 265 项,范围 22 个包及工作区/示例。 +基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 266 项,范围 22 个包及工作区/示例。 -状态:todo 44 / doing 22 / blocked 0 / done 199 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 44 / doing 22 / blocked 0 / done 200 / 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 | | artplayer-plugin-audio-track | 1.1.0 | PKG-AUDIO-01, PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-04, PKG-AUDIO-05, PKG-AUDIO-06 | -| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11 | +| 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-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, PKG-DANMUKU-10, PKG-DANMUKU-11, PKG-DANMUKU-12, PKG-DANMUKU-MASK-LOAD-01 | @@ -179,13 +179,14 @@ | 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-03, ENG-04, ENG-06, CORE-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11 | 抽帧结果和真实异步插件返回类型兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | -| PKG-AUTO-THUMB-05 | artplayer-plugin-auto-thumbnail
验证新旧核心和组合 | PKG-AUTO-THUMB-04, CORE-22 | 旧核心/候选核心预览、多次加载无过期缩略图覆盖 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | +| PKG-AUTO-THUMB-05 | artplayer-plugin-auto-thumbnail
验证新旧核心和组合 | PKG-AUTO-THUMB-04, CORE-22, PKG-AUTO-THUMB-12 | 旧核心/候选核心预览、多次加载无过期缩略图覆盖 | 最终核心与原支持范围核心分别通过;设备/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 | | PKG-AUTO-THUMB-09 | artplayer-plugin-auto-thumbnail
兼容旧 CommonJS default 与直接工厂调用 | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-08 | 同一 callable 上的 default 自别名,保留最新根声明并补准确 runtime 别名类型;旧/新实际包入口证据 | 旧红新绿、直接/default/ESM/legacy/global 调用一致;旧根类型提取不变,公开准确类型与实际别名匹配;不关闭抽帧像素和设备缺口 | M | done | | 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 | ## 5 包迁移:artplayer-plugin-vtt-thumbnail @@ -695,3 +696,4 @@ - PKG-HLS-PACK-01: [记录](changes/2026-09-15-PKG-HLS-PACK-01-config.md) [记录](baselines/hls-pack-config-validation.json) - ENG-LINT-02: [记录](changes/2026-09-15-ENG-LINT-02-clean-checks.md) [记录](baselines/eng-lint02-validation.json) - SITE-CONSOLE-01: [记录](console-modernization.md) [记录](changes/2026-09-15-SITE-CONSOLE-01-typescript.md) [记录](baselines/site-console01-validation.json) +- PKG-AUTO-THUMB-12: [记录](changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md) [记录](baselines/auto-thumbnail-metadata-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index 522441ae8..66a255a7f 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,12 @@ # 进度与证据 +## PKG-AUTO-THUMB-12 元数据加载超时与资源回收 + +修复隐藏视频在未收到 metadata/error 时永久等待:独立 30 秒期限归 extraction/job 所有,成功/取消后旧期限失效,超时保留上一张可用图。旧 main 的 7 项回归全红;源码 189/189、main/legacy 各 82/82,重建后三引擎各 36/36。首版 WebKit 请求拦截等待失败已保留,最终用真实 HTTP 挂起端点及服务端到达证据验证。期限回调由测试显式触发,不声称等待了真实 30 秒。见[变更](changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md)与[证据](baselines/auto-thumbnail-metadata-validation.json)。 + +本任务完成后 200/266;AUTO-THUMB-PIXEL-01、03/04/05 及设备/远端 CI/发布复盘仍未完成。SITE-07 的 Node 路径检查点已提交 d3aa4bbfe,后续来源审查仍开放。下一步继续关闭实际插件兼容/生命周期问题,不重复已定位的本机原生首帧对照。 + + ## SITE-07 Monaco Node 路径来源与完整模块 核实 Node v14.16.0 固定源码及 VS Code 移植。原 notice 已含完整相同条款,新增 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index d89c9ee61..b40f7601c 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -279,3 +279,4 @@ | CONSOLE-SCROLL-01 | resolved / 已复现 | 控制台卸载后仍执行滚动回调 | SITE-CONSOLE-01, SITE-07 | | CONSOLE-OWNERSHIP-01 | resolved / 已复现 | 多个控制台的全局 hook 所有权冲突 | SITE-CONSOLE-01, SITE-07 | | CONSOLE-ERROR-01 | resolved / 已复现 | Firefox/WebKit 控制台丢失原生 Error 消息 | SITE-CONSOLE-01, SITE-07 | +| AUTO-THUMB-METADATA-01 | resolved / 已复现 | Private metadata loading can retain its decoder indefinitely without metadata or media errors | PKG-AUTO-THUMB-12 | diff --git a/refactor/risks.json b/refactor/risks.json index 61335f97d..678b26c73 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -6552,6 +6552,25 @@ "scripts/site-vendor/console/README.md" ], "resolutionRationale": "Candidate behavior failed on the original bundle (11 failed/1 passed); final browser/editor matrix 63 passed plus shared parser 3 passed across Chromium/Firefox/WebKit. Units, strict types and generation checks pass. Preserves React17, old component return, raw objects, exact CSS and 100 vendor module bodies. Physical BFCache and complete third-party provenance remain separate." + }, + { + "id": "AUTO-THUMB-METADATA-01", + "title": "Private metadata loading can retain its decoder indefinitely without metadata or media errors", + "confirmation": "reproduced", + "status": "resolved", + "owners": [ + "PKG-AUTO-THUMB-12" + ], + "evidence": [ + "refactor/changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md", + "refactor/baselines/auto-thumbnail-metadata-validation.json" + ], + "compatibleResolution": "Bound only the private metadata phase, dispose through existing ownership, preserve usable previews and public registration timing.", + "closureCriteria": "Prechange failure and candidate unit/artifact/browser evidence cover stale deadlines, no metadata, cancellation, success and decoder cleanup.", + "resolutionEvidence": [ + "refactor/baselines/auto-thumbnail-metadata-validation.json" + ], + "resolutionRationale": "Seven regressions fail on prechange main; source 189/189, main and legacy 82/82, native main and legacy 36/36 each. Final pending-response tests observe the HTTP server directly. Existing first-frame risk stays open." } ] } diff --git a/refactor/tasks.json b/refactor/tasks.json index 0e6fe6569..ebdd27f37 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -1872,7 +1872,8 @@ ], "dependsOn": [ "PKG-AUTO-THUMB-04", - "CORE-22" + "CORE-22", + "PKG-AUTO-THUMB-12" ], "status": "todo", "risk": "H", @@ -5676,6 +5677,27 @@ "changes/2026-09-15-SITE-CONSOLE-01-typescript.md", "baselines/site-console01-validation.json" ] + }, + { + "id": "PKG-AUTO-THUMB-12", + "phase": "5 包迁移:artplayer-plugin-auto-thumbnail", + "title": "终止隐藏视频元数据永久等待并释放资源", + "scope": [ + "artplayer-plugin-auto-thumbnail" + ], + "dependsOn": [ + "PKG-AUTO-THUMB-02", + "CORE-09", + "CORE-19" + ], + "status": "done", + "risk": "M", + "deliverable": "元数据独立 30 秒期限、迟到回调失效、取消和错误清理、保留前一张可用预览;源码/重建产物及浏览器证据", + "acceptance": "旧红新绿、正常加载与阶段交接通过;实际挂起 HTTP 下销毁/切源/超时清理通过,不改变公开注册或帧时间公式,不关闭既有首帧风险", + "evidence": [ + "changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md", + "baselines/auto-thumbnail-metadata-validation.json" + ] } ] } diff --git a/test/auto-thumbnail-metadata.test.js b/test/auto-thumbnail-metadata.test.js new file mode 100644 index 000000000..f71632404 --- /dev/null +++ b/test/auto-thumbnail-metadata.test.js @@ -0,0 +1,102 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Verify metadata wait ownership and cancellation. +import test from 'node:test' +import { autoThumbnailCandidate, autoThumbnailEnvironment } from './helpers/auto-thumbnail.js' + +const implementation = await autoThumbnailCandidate() +async function setup() { + const env = autoThumbnailEnvironment(implementation) + const result = await env.factory({ width: 80, number: 1 })(env.art) + assert.equal(result.name, 'artplayerPluginAutoThumbnail') + env.art.emit('video:loadedmetadata') + return env +} + +function released(env, video = env.videos[0]) { + assert.equal(env.attached.has(video), false) + assert.equal(video.src, undefined) + assert.equal(video.onloadedmetadata, null) + assert.equal(video.onerror, null) +} + +test('Auto-thumbnail metadata timeout releases a stalled decoder and ignores late metadata', async () => { + const env = await setup() + const metadata = env.videos[0].onloadedmetadata + const deadline = [...env.timers.values()][0] + assert(deadline, 'Metadata loading must have an owned deadline') + assert.equal(deadline.delay, 30000) + deadline.callback() + released(env) + assert.equal(env.timers.size, 0) + assert.equal(env.warnings.length, 1) + assert.match(env.warnings[0][1].message, /metadata timed out/) + metadata() + deadline.callback() + assert.equal(env.canvases.length, 0) + assert.equal(env.updates.length, 0) + assert.equal(env.warnings.length, 1) + env.art.emit('destroy') +}) + +for (const scenario of ['destroy', 'restart', 'error', 'metadata']) { + test(`Auto-thumbnail metadata deadline becomes inert after ${scenario}`, async () => { + const env = await setup() + const deadline = [...env.timers.values()][0] + assert(deadline, 'Metadata loading must have an owned deadline') + if (scenario === 'metadata') { + env.metadata() + const frameDeadline = [...env.timers.values()][0] + assert.notEqual(frameDeadline, deadline) + deadline.callback() + assert.equal(env.timers.size, 1, 'A queued metadata timeout cannot cancel the new frame wait') + env.seeked() + env.finish() + assert.equal(env.updates.length, 1) + } + else if (scenario === 'error') { + env.videos[0].error = new Error('Media failed') + env.videos[0].onerror() + } + else { + env.art.emit(scenario) + } + deadline.callback() + released(env) + assert.equal(env.timers.size, 0) + assert.equal(env.warnings.length, scenario === 'error' ? 1 : 0) + env.art.emit('destroy') + }) +} + +test('Auto-thumbnail stalled replacement retains the previous usable sheet', async () => { + const env = await setup() + env.metadata() + env.seeked() + env.finish() + const sheet = env.art.thumbnails + env.art.emit('restart') + env.art.emit('video:loadedmetadata') + const deadline = [...env.timers.values()][0] + assert(deadline, 'Replacement metadata loading must have a deadline') + deadline.callback() + released(env, env.videos[1]) + assert.equal(env.art.thumbnails, sheet) + assert.equal(env.urls.has(sheet.url), true) + assert.equal(env.updates.length, 1) + env.art.emit('destroy') + assert.equal(env.urls.size, 0) +}) + +test('Auto-thumbnail metadata timer registration cannot start loading after destruction', async () => { + const env = autoThumbnailEnvironment(implementation) + await env.factory({ width: 80, number: 1 })(env.art) + const schedule = env.box.setTimeout + env.box.setTimeout = (...args) => { + env.art.emit('destroy') + return schedule(...args) + } + env.art.emit('video:loadedmetadata') + released(env) + assert.equal(env.timers.size, 0) + assert.equal(env.canvases.length, 0) +}) diff --git a/test/browser/auto-thumbnail-lifecycle.spec.js b/test/browser/auto-thumbnail-lifecycle.spec.js index 3a68e7ee0..8e2d25fbe 100644 --- a/test/browser/auto-thumbnail-lifecycle.spec.js +++ b/test/browser/auto-thumbnail-lifecycle.spec.js @@ -3,6 +3,73 @@ import { autoThumbnailCandidate } from '../helpers/auto-thumbnail.js' import { expect, test } from './fixtures.js' const implementation = await autoThumbnailCandidate() +for (const scenario of ['timeout', 'destroy', 'restart']) { + test(`Auto-thumbnail candidate native pending metadata releases on ${scenario}`, async ({ page, request }, testInfo) => { + await page.goto('/test/player.html?core=published') + await page.setContent('
') + const caseId = `${testInfo.testId}-${testInfo.retry}` + await page.addScriptTag({ content: implementation.code }) + await page.evaluate(async (caseId) => { + const schedule = window.setTimeout + const clear = window.clearTimeout + const warn = console.warn + const probe = window.metadataProbe = { timers: new Map(), warnings: [], updates: [], listeners: new Map() } + window.setTimeout = (callback, delay, ...args) => { + const id = schedule(callback, delay, ...args) + if (delay === 30000) + probe.timers.set(id, callback) + return id + } + window.clearTimeout = (id) => { + probe.timers.delete(id) + clear(id) + } + console.warn = (...args) => probe.warnings.push(args.map(String)) + probe.restore = () => { + window.setTimeout = schedule + window.clearTimeout = clear + console.warn = warn + } + probe.art = { + option: { url: `/test/pending-thumbnail-metadata.mp4?case=${encodeURIComponent(caseId)}` }, + on(name, callback) { probe.listeners.set(name, callback) }, + off(name) { probe.listeners.delete(name) }, + get thumbnails() { return probe.updates.at(-1) }, + set thumbnails(value) { probe.updates.push(value) }, + } + probe.result = await window.artplayerPluginAutoThumbnail({ width: 80, number: 1 })(probe.art) + probe.listeners.get('video:loadedmetadata')() + probe.video = document.querySelector('video') + }, caseId) + const requestLog = `/test/requests.json?case=${encodeURIComponent(caseId)}` + await expect.poll(async () => (await (await request.get(requestLog)).json()).length).toBeGreaterThan(0) + const mediaRequests = await (await request.get(requestLog)).json() + const evidence = await page.evaluate((scenario) => { + const probe = window.metadataProbe + const video = probe.video + const before = { connected: video.isConnected, readyState: video.readyState, source: video.getAttribute('src'), timers: probe.timers.size } + const deadline = [...probe.timers.values()][0] + if (scenario === 'timeout') + deadline?.() + else probe.listeners.get(scenario)() + const after = { connected: video.isConnected, readyState: video.readyState, source: video.getAttribute('src'), metadataHandler: video.onloadedmetadata, errorHandler: video.onerror, timers: probe.timers.size, canvases: document.querySelectorAll('canvas').length } + deadline?.() + probe.listeners.get('destroy')?.() + probe.restore() + return { before, after, result: probe.result, warnings: probe.warnings, updates: probe.updates.length } + }, scenario) + await testInfo.attach('auto-thumbnail-metadata-deadline', { contentType: 'application/json', body: JSON.stringify({ provenance: implementation.provenance, sha256: hash(implementation.code), scenario, mediaRequests, virtualDeadline: true, ...evidence }) }) + expect(evidence.result).toEqual({ name: 'artplayerPluginAutoThumbnail' }) + expect(evidence.before).toMatchObject({ connected: true, readyState: 0, timers: 1 }) + expect(evidence.before.source).toContain('pending-thumbnail-metadata.mp4') + expect(evidence.after).toEqual({ connected: false, readyState: 0, source: null, metadataHandler: null, errorHandler: null, timers: 0, canvases: 0 }) + expect(evidence.updates).toBe(0) + expect(evidence.warnings).toHaveLength(scenario === 'timeout' ? 1 : 0) + if (scenario === 'timeout') + expect(evidence.warnings[0].join(' ')).toContain('metadata timed out') + }) +} + for (const variant of ['destroy', 'restart', 'complete', 'frame-destroy', 'frame-restart', 'alias-complete', 'encoding-timeout']) { const useDefault = variant === 'alias-complete' const scenario = useDefault ? 'complete' : variant diff --git a/test/browser/server.mjs b/test/browser/server.mjs index 72bc006e0..cef2b4cee 100644 --- a/test/browser/server.mjs +++ b/test/browser/server.mjs @@ -181,6 +181,12 @@ const server = http.createServer((req, res) => { res.writeHead(503, { 'Content-Type': 'text/plain', 'Cache-Control': 'no-store' }).end('Intentional media failure') return } + if (url.pathname === '/test/pending-thumbnail-metadata.mp4') { + // Native media backends may bypass browser request interception. Keep this + // real HTTP response pending, with a server-side bound if a test fails. + res.setTimeout(10000, () => res.destroy()) + return + } if (files.has(url.pathname)) { if (url.pathname === '/test/vast-skippable.xml' && ['http://imasdk.googleapis.com', 'https://imasdk.googleapis.com'].includes(req.headers.origin)) { res.setHeader('Access-Control-Allow-Origin', req.headers.origin)