refactor(auto-thumbnail): [PKG-AUTO-THUMB-03] type internal extraction contracts

This commit is contained in:
Harvey Zhao committed 2026-09-13 23:39:06 +08:00
1 parent 00e81309ca
commit 6c989deccd
20 files changed
+3968 -48

No files matched your search

@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * Released under the MIT License.
*/ */
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;if(r=void 0,n.onloadeddata=null,n.onseeked=null,!t)return;const i=e([()=>clearTimeout(t.timer),()=>{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,s=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,s())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},l=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,s()}),c())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?l():n.onloadeddata=l)}}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);e.active()&&(i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{i.onloadedmetadata=null;const r=i.duration,o=i.videoHeight,a=i.videoWidth,{height:u,canvasWidth:d,canvasHeight:s}=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:o,videoWidth:a});if(!e.active())return;i.width=a,i.height=o,i.style.width=`${a}px`,i.style.height=`${o}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=s;const l=c.getContext("2d");if(!l)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=t(e,i);let h=0;const m=e.guard(()=>{h>=n.number?e.dispose():f(r*h/n.number,e.guard(()=>{if(l.drawImage(i,h%10*n.width,Math.floor(h/10)*u,n.width,u),!e.active())return;let t=!1;c.toBlob(e.guard(i=>{t||(t=!0,e.publish(i,{height:u,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(h+=1,m()))}),"image/jpeg")}))});m()}),e.active()&&(i.src=n.url))}return function(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){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,s=[];let c=!1;const l={active:()=>!o&&!c&&i===l,own(t){if(c){const i=e([t]);i.failed&&n(i.failure)}else s.push(t)},dispose(){if(c)return;c=!0,i===l&&(i=void 0);const t=e(s.splice(0).reverse());t.failed&&n(t.failure)},fail(e){l.active()&&(l.dispose(),n(e))},guard:e=>(...t)=>{if(l.active())try{return e(...t)}catch(n){l.fail(n)}},publish(i,o){if(!l.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!l.active())return void u(d);const s=r;r=d;try{t({url:d,...o})}catch(c){r===d&&(r=a.has(s)?s:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),c}u(s)}};return i=l,d?.dispose(),l},cancel:d,destroy:function(){if(o)rLine truncated !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;if(r=void 0,n.onloadeddata=null,n.onseeked=null,!t)return;const i=e([()=>{null!==t.timer&&clearTimeout(t.timer)},()=>{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,s=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,s())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},l=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,s()}),c())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?l():n.onloadeddata=l)}}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);e.active()&&(i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{i.onloadedmetadata=null;const r=i.duration,o=i.videoHeight,a=i.videoWidth,{height:u,canvasWidth:d,canvasHeight:s}=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:o,videoWidth:a});if(!e.active())return;i.width=a,i.height=o,i.style.width=`${a}px`,i.style.height=`${o}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=s;const l=c.getContext("2d");if(!l)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=t(e,i);let h=0;const m=e.guard(()=>{h>=n.number?e.dispose():f(r*h/n.number,e.guard(()=>{if(l.drawImage(i,h%10*n.width,Math.floor(h/10)*u,n.width,u),!e.active())return;let t=!1;c.toBlob(e.guard(i=>{t||(t=!0,e.publish(i,{height:u,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(h+=1,m()))}),"image/jpeg")}))});m()}),e.active()&&(i.src=n.url))}return function(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,s=[];let c=!1;const l={active:()=>!o&&!c&&i===l,own(t){if(c){const i=e([t]);i.failed&&n(i.failure)}else s.push(t)},dispose(){if(c)return;c=!0,i===l&&(i=void 0);const t=e(s.splice(0).reverse());t.failed&&n(t.failure)},fail(e){l.active()&&(l.dispose(),n(e))},guard:e=>(...t)=>{if(l.active())try{return e(...t)}catch(n){l.fail(n)}},publish(i,o){if(!l.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!l.active())return void u(d);const s=r;r=d;try{t({url:d,...o})}catch(c){r===d&&(r=void 0!==s&&a.has(s)?s:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),c}u(s)}};return i=l,d?.dispoLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * Released under the MIT License.
*/ */
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,d,l=!1;const c=new Set;function s(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 b={active:()=>!l&&!m&&u===b,own(e){if(m){const t=o([e]);t.failed&&a(t.failure)}else h.push(e)},dispose(){if(m)return;m=!0,u===b&&(u=void 0);const e=o(h.splice(0).reverse());e.failed&&a(e.failure)},fail(e){b.active()&&(b.dispose(),a(e))},guard:e=>(...t)=>{if(b.active())try{return e(...t)}catch(n){b.fail(n)}},publish(u,l){if(!b.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!b.active())return void s(f);const h=d;d=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},l))}catch(m){d===f&&(d=c.has(h)?h:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),m}s(h)}};return u=b,null==f||f.dispose(),b},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 r;function i(){const e=r;if(r=void 0,t.onloadeddata=null,t.onseeked=null,!e)return;const n=o([()=>clearTimeout(e.timer),()=>{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 d=()=>e.active()&&r===u,l=e.guard(()=>{d()&&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),!d())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{d()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&r===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 r=n.duration,i=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),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:i,videoWidth:o});if(!e.active())return;n.width=o,n.height=i,n.style.width=`${o}px`,n.style.height=`${i}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=l;const s=c.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=u(eLine truncated !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,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)&&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},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 r;function i(){const e=r;if(r=void 0,t.onloadeddata=null,t.onseeked=null,!e)return;const n=o([()=>{null!==e.timer&&clearTimeout(e.timer)},()=>{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 d=()=>e.active()&&r===u,l=e.guard(()=>{d()&&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),!d())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{d()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&r===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 r=n.duration,i=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),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:i,videoWidth:o});if(!e.active())return;n.width=o,n.height=i,n.style.width=`${o}px`,n.style.height=`${i}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=l;const s=c.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavaLine truncated
@@ -25,7 +25,7 @@ function createSession(publish, report) {
let lastUrl; let lastUrl;
const urls = /* @__PURE__ */ new Set(); const urls = /* @__PURE__ */ new Set();
function release(url) { function release(url) {
if (urls.delete(url)) if (url !== void 0 && urls.delete(url))
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
function cancel() { function cancel() {
@@ -103,7 +103,7 @@ function createSession(publish, report) {
publish({ url, ...config }); publish({ url, ...config });
} catch (error) { } catch (error) {
if (lastUrl === url) if (lastUrl === url)
lastUrl = urls.has(previousUrl) ? previousUrl : void 0; lastUrl = previousUrl !== void 0 && urls.has(previousUrl) ? previousUrl : void 0;
const result = cleanupAll([() => release(url)]); const result = cleanupAll([() => release(url)]);
if (result.failed) if (result.failed)
report(result.failure); report(result.failure);
@@ -136,7 +136,10 @@ function createFrameReader(job, video) {
if (!previous) if (!previous)
return; return;
const result = cleanupAll([ const result = cleanupAll([
() => clearTimeout(previous.timer), () => {
if (previous.timer !== null)
clearTimeout(previous.timer);
},
() => { () => {
if (previous.frame !== null) if (previous.frame !== null)
video.cancelVideoFrameCallback(previous.frame); video.cancelVideoFrameCallback(previous.frame);
@@ -360,7 +363,8 @@ function artplayerPluginAutoThumbnail(option) {
report(result.failure); report(result.failure);
}; };
try { try {
for (const [name, callback] of [["destroy", onDestroy], ["restart", session.cancel], ["video:loadedmetadata", onMetadata]]) { const events = [["destroy", onDestroy], ["restart", session.cancel], ["video:loadedmetadata", onMetadata]];
for (const [name, callback] of events) {
if (session.closed) if (session.closed)
break; break;
subscriptions.push(() => art.off(name, callback)); subscriptions.push(() => art.off(name, callback));
@@ -2,4 +2,5 @@ node_modules
.DS_Store .DS_Store
yarn.lock yarn.lock
package-lock.json package-lock.json
src src
tsconfig.json
@@ -7,39 +7,42 @@ for video processing.
## Modules and ownership ## Modules and ownership
- `src/index.js` owns ArtPlayer subscriptions and the public factory. Options are - `src/index.ts` owns ArtPlayer subscriptions and the public factory. Options are
read at each `video:loadedmetadata` event, including subsequent changes to the read at each `video:loadedmetadata` event, including subsequent changes to the
original options object. `restart` cancels the old decoder immediately. `destroy` original options object. `restart` cancels the old decoder immediately. `destroy`
closes the session and removes subscriptions. Partial registration is rolled back. closes the session and removes subscriptions. Partial registration is rolled back.
- `src/options.js` preserves truthy defaults and computes the ten-column layout. - `src/options.ts` preserves truthy defaults and computes the ten-column layout.
Coercible numeric JS inputs and fractional frame counts retain their previous Coercible numeric JS inputs and fractional frame counts retain their previous
behavior; raw option values are forwarded to the thumbnail configuration. behavior; raw option values are forwarded to the thumbnail configuration.
Invalid/nonfinite dimensions fail before canvas allocation. Canvas dimensions Invalid/nonfinite dimensions fail before canvas allocation. Canvas dimensions
are checked against integer/IDL bounds, not a universal memory budget. are checked against integer/IDL bounds, not a universal memory budget.
- `src/session.js` owns one active extraction job and the generated object URLs. - `src/session.ts` owns one active extraction job and the generated object URLs.
Guards prevent obsolete callbacks from publishing or scheduling another frame. Guards prevent obsolete callbacks from publishing or scheduling another frame.
Cleanup runs all registered actions even if one fails. Installing a replacement Cleanup runs all registered actions even if one fails. Installing a replacement
before cleaning up its predecessor makes synchronous host reentry observable. before cleaning up its predecessor makes synchronous host reentry observable.
- `src/extraction.js` owns the private video and canvas, metadata/seek/error - `src/extraction.ts` owns the private video and canvas, metadata/seek/error
handlers, and the frame/encode sequence. Each valid draw is encoded before the 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 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. remains owned by the session until another usable sheet replaces it or destroy.
- `src/video.js` creates and owns the hidden media element. It is attached to the - `src/video.ts` creates and owns the hidden media element. It is attached to the
document root because detached media loses drawable pixels on Windows WebKit. document root because detached media loses drawable pixels on Windows WebKit.
`visibility:hidden` preserves its rendered box; `display:none` and a 1px box do `visibility:hidden` preserves its rendered box; `display:none` and a 1px box do
not. Metadata locks the box to intrinsic dimensions, overriding ordinary global not. Metadata locks the box to intrinsic dimensions, overriding ordinary global
video sizing rules. It is silent, excluded from focus/accessibility, and never video sizing rules. It is silent, excluded from focus/accessibility, and never
played by this implementation. Cleanup also removes the element if insertion, played by this implementation. Cleanup also removes the element if insertion,
cancellation, pause or decoder reset fails. cancellation, pause or decoder reset fails.
- `src/frames.js` owns one pending sample, its seek/data handlers, deadline and - `src/frames.ts` owns one pending sample, its seek/data handlers, deadline and
native presentation callback. When request/cancel frame callbacks are both native presentation callback. When request/cancel frame callbacks are both
available it waits for loaded data, then requires both seek completion and frame available it waits for loaded data, then requires both seek completion and frame
presentation before drawing. Callback identities are invalidated on retry and presentation before drawing. Callback identities are invalidated on retry and
replacement; late/duplicate delivery cannot complete a newer sample. One job replacement; late/duplicate delivery cannot complete a newer sample. One job
cleanup handles every frame without accumulating per-frame cleanup closures. cleanup handles every frame without accumulating per-frame cleanup closures.
- `src/types.ts` defines internal configuration, sheet data, the minimal host and
guarded extraction-job callbacks. `guard` retains argument/result types and the
inactive undefined result. No public declarations are generated from this file.
Only the entry module receives ArtPlayer. Extraction receives a guarded job and a Only the entry module receives ArtPlayer. Extraction receives a guarded job and a
configuration snapshot. These internal modules do not add public player fields, configuration snapshot. These internal modules do not add public player fields,
plugin methods, or a dependency on a particular core implementation. plugin methods, or a dependency on a particular core implementation.
## Compatibility and intentional fixes ## Compatibility and intentional fixes
@@ -80,6 +83,7 @@ Use the root Yarn toolchain:
```sh ```sh
yarn test:auto-thumbnail yarn test:auto-thumbnail
yarn exec tsc -p packages/artplayer-plugin-auto-thumbnail/tsconfig.json --noEmit
yarn test:browser test/browser/auto-thumbnail-lifecycle.spec.js yarn test:browser test/browser/auto-thumbnail-lifecycle.spec.js
yarn test:browser test/browser/auto-thumbnail-pixels.spec.js yarn test:browser test/browser/auto-thumbnail-pixels.spec.js
yarn build artplayer-plugin-auto-thumbnail yarn build artplayer-plugin-auto-thumbnail
@@ -109,9 +113,18 @@ red, black, blue and yellow sections. Its command and fingerprint are in
directory with `ARTPLAYER_FFMPEG` and `node scripts/generate-auto-thumbnail-fixture.mjs directory with `ARTPLAYER_FFMPEG` and `node scripts/generate-auto-thumbnail-fixture.mjs
<new-directory>`; never silently replace the committed fixture. <new-directory>`; never silently replace the committed fixture.
Task04 converts these modules to strict TypeScript and resolves historical public Task03's internal-type checkpoint has converted the six runtime modules to strict
declaration/CommonJS differences. Task05 verifies old/final cores and actual TypeScript. Numeric annotations describe the nominal options; no runtime casts or
normalization were added, so historical coercible JS inputs retain the same
validation/arithmetic and raw values. Missing options retain the existing behavior.
Task04 still resolves public declarations, implementation-to-consumer contracts and
historical CommonJS differences. Task05 verifies old/final cores and actual
devices; task06 covers installed package entries and the real demo/editor. The devices; task06 covers installed package entries and the real demo/editor. The
types and package version are unchanged at this checkpoint. Use types and package version are unchanged at this checkpoint. Use
`refactor/baselines/auto-thumbnail-contract.md` and `auto-thumbnail-failures.md` as `refactor/baselines/auto-thumbnail-contract.md` and `auto-thumbnail-failures.md` as
the historical evidence map; do not regenerate those baselines from this source. the historical evidence map; do not regenerate those baselines from this source.
The internal type checkpoint and its core-host assignment fixture are recorded in
`refactor/changes/2026-09-13-PKG-AUTO-THUMB-03-internal-types.md`. A core host can
hold partial user thumbnail options; extraction publishes a complete sheet. Do not
require the host's existing getter to have every generated-sheet field.
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * Released under the MIT License.
*/ */
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;if(r=void 0,n.onloadeddata=null,n.onseeked=null,!t)return;const i=e([()=>clearTimeout(t.timer),()=>{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,s=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,s())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},l=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,s()}),c())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?l():n.onloadeddata=l)}}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);e.active()&&(i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{i.onloadedmetadata=null;const r=i.duration,o=i.videoHeight,a=i.videoWidth,{height:u,canvasWidth:d,canvasHeight:s}=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:o,videoWidth:a});if(!e.active())return;i.width=a,i.height=o,i.style.width=`${a}px`,i.style.height=`${o}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=s;const l=c.getContext("2d");if(!l)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=t(e,i);let h=0;const m=e.guard(()=>{h>=n.number?e.dispose():f(r*h/n.number,e.guard(()=>{if(l.drawImage(i,h%10*n.width,Math.floor(h/10)*u,n.width,u),!e.active())return;let t=!1;c.toBlob(e.guard(i=>{t||(t=!0,e.publish(i,{height:u,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(h+=1,m()))}),"image/jpeg")}))});m()}),e.active()&&(i.src=n.url))}return function(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){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,s=[];let c=!1;const l={active:()=>!o&&!c&&i===l,own(t){if(c){const i=e([t]);i.failed&&n(i.failure)}else s.push(t)},dispose(){if(c)return;c=!0,i===l&&(i=void 0);const t=e(s.splice(0).reverse());t.failed&&n(t.failure)},fail(e){l.active()&&(l.dispose(),n(e))},guard:e=>(...t)=>{if(l.active())try{return e(...t)}catch(n){l.fail(n)}},publish(i,o){if(!l.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!l.active())return void u(d);const s=r;r=d;try{t({url:d,...o})}catch(c){r===d&&(r=a.has(s)?s:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),c}u(s)}};return i=l,d?.dispose(),l},cancel:d,destroy:function(){if(o)rLine truncated !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;if(r=void 0,n.onloadeddata=null,n.onseeked=null,!t)return;const i=e([()=>{null!==t.timer&&clearTimeout(t.timer)},()=>{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,s=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,s())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},l=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,s()}),c())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?l():n.onloadeddata=l)}}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);e.active()&&(i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{i.onloadedmetadata=null;const r=i.duration,o=i.videoHeight,a=i.videoWidth,{height:u,canvasWidth:d,canvasHeight:s}=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:o,videoWidth:a});if(!e.active())return;i.width=a,i.height=o,i.style.width=`${a}px`,i.style.height=`${o}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=s;const l=c.getContext("2d");if(!l)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=t(e,i);let h=0;const m=e.guard(()=>{h>=n.number?e.dispose():f(r*h/n.number,e.guard(()=>{if(l.drawImage(i,h%10*n.width,Math.floor(h/10)*u,n.width,u),!e.active())return;let t=!1;c.toBlob(e.guard(i=>{t||(t=!0,e.publish(i,{height:u,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(h+=1,m()))}),"image/jpeg")}))});m()}),e.active()&&(i.src=n.url))}return function(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,s=[];let c=!1;const l={active:()=>!o&&!c&&i===l,own(t){if(c){const i=e([t]);i.failed&&n(i.failure)}else s.push(t)},dispose(){if(c)return;c=!0,i===l&&(i=void 0);const t=e(s.splice(0).reverse());t.failed&&n(t.failure)},fail(e){l.active()&&(l.dispose(),n(e))},guard:e=>(...t)=>{if(l.active())try{return e(...t)}catch(n){l.fail(n)}},publish(i,o){if(!l.active())return;if(!i)throw new Error("Auto-thumbnail encoding returned no Blob");const d=URL.createObjectURL(i);if(a.add(d),!l.active())return void u(d);const s=r;r=d;try{t({url:d,...o})}catch(c){r===d&&(r=void 0!==s&&a.has(s)?s:void 0);const t=e([()=>u(d)]);throw t.failed&&n(t.failure),c}u(s)}};return i=l,d?.dispoLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * Released under the MIT License.
*/ */
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,d,l=!1;const c=new Set;function s(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 b={active:()=>!l&&!m&&u===b,own(e){if(m){const t=o([e]);t.failed&&a(t.failure)}else h.push(e)},dispose(){if(m)return;m=!0,u===b&&(u=void 0);const e=o(h.splice(0).reverse());e.failed&&a(e.failure)},fail(e){b.active()&&(b.dispose(),a(e))},guard:e=>(...t)=>{if(b.active())try{return e(...t)}catch(n){b.fail(n)}},publish(u,l){if(!b.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!b.active())return void s(f);const h=d;d=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},l))}catch(m){d===f&&(d=c.has(h)?h:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),m}s(h)}};return u=b,null==f||f.dispose(),b},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 r;function i(){const e=r;if(r=void 0,t.onloadeddata=null,t.onseeked=null,!e)return;const n=o([()=>clearTimeout(e.timer),()=>{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 d=()=>e.active()&&r===u,l=e.guard(()=>{d()&&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),!d())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{d()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&r===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 r=n.duration,i=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),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:i,videoWidth:o});if(!e.active())return;n.width=o,n.height=i,n.style.width=`${o}px`,n.style.height=`${i}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=l;const s=c.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const f=u(eLine truncated !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,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)&&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},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 r;function i(){const e=r;if(r=void 0,t.onloadeddata=null,t.onseeked=null,!e)return;const n=o([()=>{null!==e.timer&&clearTimeout(e.timer)},()=>{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 d=()=>e.active()&&r===u,l=e.guard(()=>{d()&&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),!d())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{d()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,l())}));d()&&r===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 r=n.duration,i=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),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:i,videoWidth:o});if(!e.active())return;n.width=o,n.height=i,n.style.width=`${o}px`,n.style.height=`${i}px`;const c=document.createElement("canvas");if(!e.active())return;c.width=d,c.height=l;const s=c.getContext("2d");if(!s)throw new Error("Auto-thumbnail canvas context is unavaLine truncated
@@ -25,7 +25,7 @@ function createSession(publish, report) {
let lastUrl; let lastUrl;
const urls = /* @__PURE__ */ new Set(); const urls = /* @__PURE__ */ new Set();
function release(url) { function release(url) {
if (urls.delete(url)) if (url !== void 0 && urls.delete(url))
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
function cancel() { function cancel() {
@@ -103,7 +103,7 @@ function createSession(publish, report) {
publish({ url, ...config }); publish({ url, ...config });
} catch (error) { } catch (error) {
if (lastUrl === url) if (lastUrl === url)
lastUrl = urls.has(previousUrl) ? previousUrl : void 0; lastUrl = previousUrl !== void 0 && urls.has(previousUrl) ? previousUrl : void 0;
const result = cleanupAll([() => release(url)]); const result = cleanupAll([() => release(url)]);
if (result.failed) if (result.failed)
report(result.failure); report(result.failure);
@@ -136,7 +136,10 @@ function createFrameReader(job, video) {
if (!previous) if (!previous)
return; return;
const result = cleanupAll([ const result = cleanupAll([
() => clearTimeout(previous.timer), () => {
if (previous.timer !== null)
clearTimeout(previous.timer);
},
() => { () => {
if (previous.frame !== null) if (previous.frame !== null)
video.cancelVideoFrameCallback(previous.frame); video.cancelVideoFrameCallback(previous.frame);
@@ -360,7 +363,8 @@ function artplayerPluginAutoThumbnail(option) {
report(result.failure); report(result.failure);
}; };
try { try {
for (const [name, callback] of [["destroy", onDestroy], ["restart", session.cancel], ["video:loadedmetadata", onMetadata]]) { const events = [["destroy", onDestroy], ["restart", session.cancel], ["video:loadedmetadata", onMetadata]];
for (const [name, callback] of events) {
if (session.closed) if (session.closed)
break; break;
subscriptions.push(() => art.off(name, callback)); subscriptions.push(() => art.off(name, callback));
@@ -1,8 +1,9 @@
import type { ExtractionConfig, ExtractionJob } from './types'
import createFrameReader from './frames' import createFrameReader from './frames'
import { sheetSize } from './options' import { sheetSize } from './options'
import createVideo from './video' import createVideo from './video'
export default function extract(job, config) { export default function extract(job: ExtractionJob, config: ExtractionConfig) {
const video = createVideo(job) const video = createVideo(job)
if (!job.active()) if (!job.active())
return return
@@ -1,9 +1,20 @@
import type { ExtractionJob } from './types'
import { cleanupAll } from './session' import { cleanupAll } from './session'
export default function createFrameReader(job, video) { interface PendingFrame {
frame: number | null
timer: ReturnType<typeof setTimeout> | null
sought: boolean
presented: boolean
retries: number
sequence: number
started: boolean
}
export default function createFrameReader(job: ExtractionJob, video: HTMLVideoElement) {
const presentedFrames = typeof video.requestVideoFrameCallback === 'function' const presentedFrames = typeof video.requestVideoFrameCallback === 'function'
&& typeof video.cancelVideoFrameCallback === 'function' && typeof video.cancelVideoFrameCallback === 'function'
let pending let pending: PendingFrame | undefined
function clear() { function clear() {
const previous = pending const previous = pending
@@ -13,7 +24,10 @@ export default function createFrameReader(job, video) {
if (!previous) if (!previous)
return return
const result = cleanupAll([ const result = cleanupAll([
() => clearTimeout(previous.timer), () => {
if (previous.timer !== null)
clearTimeout(previous.timer)
},
() => { () => {
if (previous.frame !== null) if (previous.frame !== null)
video.cancelVideoFrameCallback(previous.frame) video.cancelVideoFrameCallback(previous.frame)
@@ -24,11 +38,11 @@ export default function createFrameReader(job, video) {
} }
job.own(clear) job.own(clear)
return (target, draw) => { return (target: number, draw: () => void) => {
clear() clear()
if (!job.active()) if (!job.active())
return return
const current = { frame: null, timer: null, sought: false, presented: !presentedFrames, retries: 0, sequence: 0, started: false } const current: PendingFrame = { frame: null, timer: null, sought: false, presented: !presentedFrames, retries: 0, sequence: 0, started: false }
pending = current pending = current
const active = () => job.active() && pending === current const active = () => job.active() && pending === current
const finish = job.guard(() => { const finish = job.guard(() => {
@@ -1,14 +1,15 @@
import type { Cleanup, ThumbnailHost, ThumbnailOptions } from './types'
import extract from './extraction' import extract from './extraction'
import { readOptions } from './options' import { readOptions } from './options'
import createSession, { cleanupAll } from './session' import createSession, { cleanupAll } from './session'
export default function artplayerPluginAutoThumbnail(option) { export default function artplayerPluginAutoThumbnail(option: ThumbnailOptions) {
return async (art) => { return async (art: ThumbnailHost) => {
const report = error => console.warn('ArtPlayer auto-thumbnail failed:', error) const report = (error: unknown) => console.warn('ArtPlayer auto-thumbnail failed:', error)
const session = createSession((config) => { const session = createSession((config) => {
art.thumbnails = config art.thumbnails = config
}, report) }, report)
const subscriptions = [] const subscriptions: Cleanup[] = []
const onMetadata = () => { const onMetadata = () => {
const job = session.start() const job = session.start()
if (job) { if (job) {
@@ -26,7 +27,8 @@ export default function artplayerPluginAutoThumbnail(option) {
report(result.failure) report(result.failure)
} }
try { try {
for (const [name, callback] of [['destroy', onDestroy], ['restart', session.cancel], ['video:loadedmetadata', onMetadata]]) { const events: [string, Cleanup][] = [['destroy', onDestroy], ['restart', session.cancel], ['video:loadedmetadata', onMetadata]]
for (const [name, callback] of events) {
if (session.closed) if (session.closed)
break break
subscriptions.push(() => art.off(name, callback)) subscriptions.push(() => art.off(name, callback))
@@ -1,4 +1,6 @@
export function readOptions(option, getFallbackUrl) { import type { ExtractionConfig, ThumbnailOptions } from './types'
export function readOptions(option: ThumbnailOptions, getFallbackUrl: () => string): ExtractionConfig {
const config = { const config = {
url: option.url || getFallbackUrl(), url: option.url || getFallbackUrl(),
width: option.width || 160, width: option.width || 160,
@@ -10,7 +12,7 @@ export function readOptions(option, getFallbackUrl) {
return config return config
} }
export function sheetSize(config, video) { export function sheetSize(config: ExtractionConfig, video: Pick<HTMLVideoElement, 'duration' | 'videoWidth' | 'videoHeight'>) {
if (!Number.isFinite(video.duration) || video.duration <= 0 || !Number.isFinite(video.videoWidth) || video.videoWidth <= 0 || !Number.isFinite(video.videoHeight) || video.videoHeight <= 0) if (!Number.isFinite(video.duration) || video.duration <= 0 || !Number.isFinite(video.videoWidth) || video.videoWidth <= 0 || !Number.isFinite(video.videoHeight) || video.videoHeight <= 0)
throw new TypeError('Auto-thumbnail requires finite media duration and dimensions') throw new TypeError('Auto-thumbnail requires finite media duration and dimensions')
const height = Math.floor(config.width * video.videoHeight / video.videoWidth) const height = Math.floor(config.width * video.videoHeight / video.videoWidth)
@@ -1,5 +1,7 @@
export function cleanupAll(actions) { import type { Cleanup, ExtractionJob, ThumbnailSheet } from './types'
let failure
export function cleanupAll(actions: Cleanup[]) {
let failure: unknown
let failed = false let failed = false
for (const action of actions) { for (const action of actions) {
try { try {
@@ -15,14 +17,14 @@ export function cleanupAll(actions) {
return { failed, failure } return { failed, failure }
} }
export default function createSession(publish, report) { export default function createSession(publish: (sheet: ThumbnailSheet) => void, report: (error: unknown) => void) {
let closed = false let closed = false
let current let current: ExtractionJob | undefined
let lastUrl let lastUrl: string | undefined
const urls = new Set() const urls = new Set<string>()
function release(url) { function release(url: string | undefined) {
if (urls.delete(url)) if (url !== undefined && urls.delete(url))
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
} }
@@ -47,9 +49,9 @@ export default function createSession(publish, report) {
return return
// Install the replacement before cleanup, which can reenter through host APIs. // Install the replacement before cleanup, which can reenter through host APIs.
const previous = current const previous = current
const actions = [] const actions: Cleanup[] = []
let disposed = false let disposed = false
const job = { const job: ExtractionJob = {
active: () => !closed && !disposed && current === job, active: () => !closed && !disposed && current === job,
own(action) { own(action) {
if (disposed) { if (disposed) {
@@ -107,7 +109,7 @@ export default function createSession(publish, report) {
} }
catch (error) { catch (error) {
if (lastUrl === url) if (lastUrl === url)
lastUrl = urls.has(previousUrl) ? previousUrl : undefined lastUrl = previousUrl !== undefined && urls.has(previousUrl) ? previousUrl : undefined
const result = cleanupAll([() => release(url)]) const result = cleanupAll([() => release(url)])
if (result.failed) if (result.failed)
report(result.failure) report(result.failure)
@@ -0,0 +1,43 @@
// Internal contracts only. Public declarations retain their historical entrypoint.
export interface ThumbnailOptions {
url?: string
width?: number
number?: number
scale?: number
height?: number
}
export interface ExtractionConfig {
url: string
width: number
number: number
scale: number
}
export interface SheetConfig {
height: number
column: number
number: number
width: number
scale: number
}
export interface ThumbnailSheet extends SheetConfig { url: string }
export type Cleanup = () => void
export interface ExtractionJob {
active: () => boolean
own: (action: Cleanup) => void
dispose: Cleanup
fail: (error: unknown) => void
guard: <Args extends unknown[], Result>(callback: (...args: Args) => Result) => (...args: Args) => Result | undefined
publish: (blob: Blob | null, config: SheetConfig) => void
}
export interface ThumbnailHost {
option: { url: string }
// A host may already hold a partial user configuration; generated sheets are complete.
thumbnails: { url: string } & Partial<SheetConfig>
on: (event: string, callback: Cleanup) => unknown
off: (event: string, callback: Cleanup) => unknown
}
@@ -1,10 +1,12 @@
export default function createVideo(job) { import type { ExtractionJob } from './types'
export default function createVideo(job: ExtractionJob) {
const video = document.createElement('video') const video = document.createElement('video')
job.own(() => video.remove()) job.own(() => video.remove())
job.own(() => video.load()) job.own(() => video.load())
job.own(() => video.removeAttribute('src')) job.own(() => video.removeAttribute('src'))
job.own(() => video.pause()) job.own(() => video.pause())
for (const property of ['onloadedmetadata', 'onloadeddata', 'onseeked', 'onerror']) for (const property of ['onloadedmetadata', 'onloadeddata', 'onseeked', 'onerror'] as const)
job.own(() => { video[property] = null }) job.own(() => { video[property] = null })
if (!job.active()) if (!job.active())
return video return video
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "allowJs": false },
"include": ["src/**/*.ts", "../../refactor/fixtures/implementation/auto-thumbnail.ts"]
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,42 @@
# PKG-AUTO-THUMB-03 内部严格TS检查点
本检查点推进内部代码维护,03仍doing;公开声明和类型分发仍属04。没有降低
首帧像素要求,也没有将WebKit的早期帧诊断计入完整像素验收。
## 改动
原有六个职责模块index/options/session/extraction/video/frames迁为TS,新增types.ts
集中描述任务所有权、guard参数/返回、完整图集配置和最小宿主。新增分包严格配置,
不启用allowJs/skipLibCheck,不引入any兜底;沿用现有构建的JS/TS共存能力。
公开types/、入口/版本/依赖不变,注册仍为异步Promise,抽帧在后续metadata进行。
分包tsconfig仅用于仓库实施检查,加入.npmignore,避免发布引用仓库外fixture的配置。
数字字段只作名义类型标注,没有加入运行时归一化、转换或额外getter读取;旧JS
可转换字符串/小数输入、原始width/number/scale向宿主转交及读取时序继续由既有
用例保护。URL集合不接受undefined,timer清理排除尚未取得的null句柄;有效0
句柄和取消中重入保持原有行为。
新增真正使用核心公开Artplayer类型的源码接入fixture。最初最小宿主错误要求
已存在的thumbnails配置所有字段必填,编译报TS2345(height可为undefined)。
修正为宿主允许既有部分配置,而job.publish产出的ThumbnailSheet仍完整必填;
最终分包严格检查包含该fixture并通过。没有修改核心或插件公开声明来迁就内部类型。
## 验证与限制
源码联合154项通过,其中52项当前生命周期/帧读取、102项历史契约与缺陷记录;
main/legacy各52项候选单测通过。源码/main/legacy各21共63项浏览器通过,0重试/
跳过;有原生帧回调时五格像素严格断言,无回调路径的前两格仍是诊断,不关闭
AUTO-THUMB-PIXEL-01。三格式正常构建和docs副本一致。新源码lint及分包严格TS通过。
Yarn实际pack的七个文件通过分发入口检查,无src或tsconfig泄漏;此检查不替代安装消费验收。
全仓类型流程通过398个生产TS及旧/新编译器消费者;其后新增的最终宿主接入fixture
又由分包严格检查单独验证。完整插件公开声明/安装包兼容仍待04及后续分发任务。
重跑:`yarn test:auto-thumbnail`、
`yarn exec tsc -p packages/artplayer-plugin-auto-thumbnail/tsconfig.json --noEmit`、
`yarn build artplayer-plugin-auto-thumbnail`;浏览器运行
`yarn test:browser test/browser/auto-thumbnail-lifecycle.spec.js test/browser/auto-thumbnail-pixels.spec.js --workers=2`。
以ARTPLAYER_AUTO_THUMBNAIL_ARTIFACT逐个选择main/legacy,必须等待终态并归档报告。
机器结果见[内部TS证据](../baselines/auto-thumbnail-internal-types.json)。
03继续处理缺少原生帧回调时的首帧和其余资源边界;Mask03同时进行但文件和提交
独立。本检查点可回退六个模块和新增类型/config/fixture,再正常构建;不推送或发布。
@@ -0,0 +1,6 @@
import type Artplayer from 'artplayer'
import factory from '../../../packages/artplayer-plugin-auto-thumbnail/src'
declare const art: Artplayer
const registration: Promise<{ name: string }> = factory({ width: 80, number: 5 })(art)
export { registration }
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据 # 进度与证据
## PKG-AUTO-THUMB-03 内部TS检查点(仍doing)
六个抽帧/会话/资源运行模块迁到严格TS,内部类型和实际核心宿主接入fixture齐备。
源码154项(52候选、102历史)、产物各52项、三格式浏览器各21共63项通过;
严格分包及全仓398生产TS流程通过,最终宿主兼容修正另经分包fixture验证。
仅当前实现内部契约;公开声明04、WebKit无帧回调的前两格像素与资源边界仍待完成。
详见[检查点](changes/2026-09-13-PKG-AUTO-THUMB-03-internal-types.md);不增加done数,
不关闭像素风险。该检查点单独本地提交,Mask源码与状态不纳入同一提交。
## PKG-DANMUKU-07 弹幕稳定性阶段验收完成 ## PKG-DANMUKU-07 弹幕稳定性阶段验收完成
12后的源码在三引擎完成新旧插件两档负载对照,每例三轮42秒原生播放;候选 12后的源码在三引擎完成新旧插件两档负载对照,每例三轮42秒原生播放;候选