mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
fix(PKG-AUTO-THUMB-11): isolate frame handler cleanup failures
This commit is contained in:
1 parent
a8d19ce5ef
commit
eb6402fdca
16 files changed
+2892
-36
No files matched your search
@@ -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;if(r=void 0,i.onloadeddata=null,i.onseeked=null,!t)return;const n=e([()=>{null!==t.timer&&clearTimeout(t.timer)},()=>{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)Line 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,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.spliLine truncated
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,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.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())retuLine 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,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()Line truncated
|
||||
@@ -165,17 +165,19 @@ function createFrameReader(job, video) {
|
||||
function clear() {
|
||||
const previous = pending;
|
||||
pending = void 0;
|
||||
video.onloadeddata = null;
|
||||
video.onseeked = null;
|
||||
if (!previous)
|
||||
return;
|
||||
const result = cleanupAll([
|
||||
() => {
|
||||
if (previous.timer !== null)
|
||||
video.onloadeddata = null;
|
||||
},
|
||||
() => {
|
||||
video.onseeked = null;
|
||||
},
|
||||
() => {
|
||||
if (previous && previous.timer !== null)
|
||||
clearTimeout(previous.timer);
|
||||
},
|
||||
() => {
|
||||
if (previous.frame !== null)
|
||||
if (previous && previous.frame !== null)
|
||||
video.cancelVideoFrameCallback(previous.frame);
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -52,6 +52,10 @@ for video processing.
|
||||
presentation before drawing. Callback identities are invalidated on retry and
|
||||
replacement; late/duplicate delivery cannot complete a newer sample. One job
|
||||
cleanup handles every frame without accumulating per-frame cleanup closures.
|
||||
It invalidates the pending sample before independently clearing each handler,
|
||||
deadline and frame callback. A throwing event-property setter cannot prevent
|
||||
the remaining releases. The first cleanup error reaches the existing warning
|
||||
path; even a handler that cannot be removed becomes inert after cancellation.
|
||||
- `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. It shares the opt-in `runtime-api.d.ts` option type;
|
||||
@@ -206,14 +210,16 @@ red, black, blue and yellow sections. Its command and fingerprint are in
|
||||
directory with `ARTPLAYER_FFMPEG` and `node scripts/generate-auto-thumbnail-fixture.mjs
|
||||
<new-directory>`; never silently replace the committed fixture.
|
||||
|
||||
Task03's internal-type checkpoint has converted the six runtime modules to strict
|
||||
TypeScript. Numeric annotations describe the nominal options; no runtime casts or
|
||||
The current implementation has eight strict TypeScript modules, including the
|
||||
frame reader and encoder split after task03's initial checkpoint. 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
|
||||
Tasks08/09 supplied the compatible public declarations, implementation assignment
|
||||
fixtures and historical CommonJS alias. Task04 still requires the completed task03
|
||||
implementation and final migration acceptance. Task05 verifies old/final cores and actual
|
||||
devices; task06 covers installed package entries and the real demo/editor. The
|
||||
types and package version are unchanged at this checkpoint. Use
|
||||
package version remains unchanged. Use
|
||||
`refactor/baselines/auto-thumbnail-contract.md` and `auto-thumbnail-failures.md` as
|
||||
the historical evidence map; do not regenerate those baselines from this source.
|
||||
|
||||
|
||||
+1
-1
@@ -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;if(r=void 0,i.onloadeddata=null,i.onseeked=null,!t)return;const n=e([()=>{null!==t.timer&&clearTimeout(t.timer)},()=>{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)Line 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,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.spliLine truncated
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,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.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())retuLine 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,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()Line truncated
|
||||
+8
-6
@@ -165,17 +165,19 @@ function createFrameReader(job, video) {
|
||||
function clear() {
|
||||
const previous = pending;
|
||||
pending = void 0;
|
||||
video.onloadeddata = null;
|
||||
video.onseeked = null;
|
||||
if (!previous)
|
||||
return;
|
||||
const result = cleanupAll([
|
||||
() => {
|
||||
if (previous.timer !== null)
|
||||
video.onloadeddata = null;
|
||||
},
|
||||
() => {
|
||||
video.onseeked = null;
|
||||
},
|
||||
() => {
|
||||
if (previous && previous.timer !== null)
|
||||
clearTimeout(previous.timer);
|
||||
},
|
||||
() => {
|
||||
if (previous.frame !== null)
|
||||
if (previous && previous.frame !== null)
|
||||
video.cancelVideoFrameCallback(previous.frame);
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -19,17 +19,15 @@ export default function createFrameReader(job: ExtractionJob, video: HTMLVideoEl
|
||||
function clear() {
|
||||
const previous = pending
|
||||
pending = undefined
|
||||
video.onloadeddata = null
|
||||
video.onseeked = null
|
||||
if (!previous)
|
||||
return
|
||||
const result = cleanupAll([
|
||||
() => { video.onloadeddata = null },
|
||||
() => { video.onseeked = null },
|
||||
() => {
|
||||
if (previous.timer !== null)
|
||||
if (previous && previous.timer !== null)
|
||||
clearTimeout(previous.timer)
|
||||
},
|
||||
() => {
|
||||
if (previous.frame !== null)
|
||||
if (previous && previous.frame !== null)
|
||||
video.cancelVideoFrameCallback(previous.frame)
|
||||
},
|
||||
])
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,55 @@
|
||||
# PKG-AUTO-THUMB-11: independent frame cleanup
|
||||
|
||||
Baseline: a8d19ce5ef8af8fe4c48424753742e8d78a77962. This task fixes one
|
||||
resource boundary under the unfinished task03 implementation; task04 depends on
|
||||
both. It does not close AUTO-THUMB-LIFE-01 or AUTO-THUMB-PIXEL-01.
|
||||
|
||||
## Defect and implementation
|
||||
|
||||
The frame reader invalidated its pending token, then cleared onloadeddata and
|
||||
onseeked before entering cleanupAll. A throwing property setter skipped deadline
|
||||
and native frame cancellation; outer job cleanup still removed the decoder and
|
||||
canvas, leaving those handles registered. Controlled setters reproduce this in
|
||||
both destroy and restart, while awaiting loaded data and when completing a frame.
|
||||
These are deliberately injected exceptions, not evidence of spontaneous native
|
||||
IDL setter failure in an ordinary browser.
|
||||
|
||||
frames.ts now submits both handler clears and both handle releases to the same
|
||||
cleanupAll operation. It still invalidates identity first, preserves handle zero,
|
||||
and propagates the first error through the existing job warning path. A handler
|
||||
that cannot be removed remains inert after disposal. A failure while finishing
|
||||
prevents drawing/publication, and replacement work can still produce a preview.
|
||||
No new abstraction, dependency, lockfile, script or public declaration is needed.
|
||||
|
||||
API-04/05/06/09/11 retain event names, registration Promise, result, factory alias,
|
||||
options, ten-column layout, sample times, progressive URLs and distribution paths.
|
||||
Only the exceptional cleanup path changes; normal behavior has regression coverage.
|
||||
The package architecture describes current eight-module ownership and remaining
|
||||
acceptance work. This also corrects stale prose predating type/alias tasks08/09.
|
||||
|
||||
## Validation
|
||||
|
||||
See [fingerprints and native results](../baselines/auto-thumbnail-handler-validation.json).
|
||||
|
||||
- Final 27-case frame suite against the exact pre-change main: 19 pass, all eight
|
||||
new fault regressions fail. Current source, actual main and legacy: 27 pass each.
|
||||
- Full package suite: 182 pass, including frozen contracts and type regressions.
|
||||
- Strict package TypeScript passes; targeted read-only lint passes after fixing
|
||||
a missing test brace. Pinned Node24.21.0/Yarn1.22.22 toolchain passes; core public
|
||||
declarations remain 37 files with zero drift.
|
||||
- Normal build regenerates main/legacy/ESM and the corresponding docs copies.
|
||||
- Actual main and legacy each pass 21 native lifecycle cases across Windows
|
||||
Chromium, Firefox and WebKit, with zero retries or skips. Native HTTP decoding,
|
||||
seeking, JPEG, complete/restart/destroy, held frame/Blob delivery, encoding
|
||||
timeout and URL cleanup use a stub ArtPlayer host. The throwing-setter cases
|
||||
are controlled Node tests, not native browser fault-injection tests.
|
||||
- No first-frame pixel rerun, physical-device acceptance, full old/new core
|
||||
combinations, remote CI or npm publication is claimed. Existing pixel evidence
|
||||
and failure remain unchanged; tasks03/04/05/06 remain unfinished.
|
||||
|
||||
## Rollback and next work
|
||||
|
||||
Revert this task's dedicated commit and rebuild the package to recover the
|
||||
previous behavior; the new regression tests document the restored leak. Continue
|
||||
task03 resource-boundary review and obtain a different native backend/device for
|
||||
the outstanding first-frame issue rather than relaxing its assertions.
|
||||
+6
-4
@@ -2,9 +2,9 @@
|
||||
|
||||
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 251 项,范围 22 个包及工作区/示例。
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 252 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 55 / doing 17 / blocked 0 / done 179 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 55 / doing 17 / blocked 0 / done 180 / 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 |
|
||||
| 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-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06 |
|
||||
| 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 |
|
||||
@@ -176,13 +176,14 @@
|
||||
| PKG-AUTO-THUMB-01 | artplayer-plugin-auto-thumbnail<br>核对包契约与历史用法 | BASE-05 | url/width/number/scale、异步工厂和渐进缩略图更新 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
|
||||
| PKG-AUTO-THUMB-02 | artplayer-plugin-auto-thumbnail<br>建立特有行为与错误测试 | PKG-AUTO-THUMB-01, ENG-03, ENG-05 | 抽帧边界、短视频、失败、连续来源和销毁中的抽帧 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-AUTO-THUMB-03 | artplayer-plugin-auto-thumbnail<br>整理内部职责与资源 | 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<br>迁移自有源码和公开类型 | PKG-AUTO-THUMB-03, ENG-04, ENG-06, CORE-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10 | 抽帧结果和真实异步插件返回类型兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-AUTO-THUMB-04 | artplayer-plugin-auto-thumbnail<br>迁移自有源码和公开类型 | 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<br>验证新旧核心和组合 | PKG-AUTO-THUMB-04, CORE-22 | 旧核心/候选核心预览、多次加载无过期缩略图覆盖 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-AUTO-THUMB-06 | artplayer-plugin-auto-thumbnail<br>验证分发并同步文档 | PKG-AUTO-THUMB-05, ENG-07 | auto.thumbnail.js、三种产物和内存/URL 证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
| PKG-AUTO-THUMB-07 | artplayer-plugin-auto-thumbnail<br>终止抽帧时清空私有画布像素尺寸 | PKG-AUTO-THUMB-02, CORE-09, CORE-19 | 将私有 canvas 尺寸归入 job 清理;完成、取消、失败及分配重入均有回归,保留已编码 JPEG | 旧红新绿证明保留回调时画布归零,单项清理失败不阻断其他资源;原生取消和完成后 JPEG 仍可解码,原 API/时间公式/类型不变 | M | done |
|
||||
| PKG-AUTO-THUMB-08 | artplayer-plugin-auto-thumbnail<br>保留旧声明并提供准确异步类型入口 | 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<br>兼容旧 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<br>为渐进 JPEG 编码建立取消时限与独立资源归属 | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-07 | 拆分私有 canvas 编码模块,一次一帧的30秒编码时限,完成/失败/切源/销毁取消;迟到和重复回调无副作用 | 受控回调未返回的旧红新绿、正常JPEG和前一预览保持、同步重入/清理失败有回归;真实三浏览器编码/取消通过,公开时间公式与入口不变,首帧风险独立保留 | M | done |
|
||||
| PKG-AUTO-THUMB-11 | artplayer-plugin-auto-thumbnail<br>保证抽帧事件属性清理异常不截断其他资源释放 | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-10 | 独立清除事件属性、抽帧时限和原生帧回调;首个异常保留,过期回调失效,替换任务可继续 | 八个故障注入用例旧红新绿,实际main/legacy抽帧与三浏览器生命周期回归通过;文档和构建同步,首帧及整体资源验收不冒充完成 | M | done |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-vtt-thumbnail
|
||||
|
||||
@@ -653,3 +654,4 @@
|
||||
- PKG-MULTI-SUB-09: [记录](changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md) [记录](baselines/multiple-subtitles-switch-validation.json)
|
||||
- PKG-MULTI-SUB-10: [记录](changes/2026-09-14-PKG-MULTI-SUB-10-legacy-captions.md) [记录](baselines/multiple-subtitles-legacy-validation.json)
|
||||
- PKG-MULTI-SUB-11: [记录](changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md) [记录](baselines/multiple-subtitles-ass-validation.json)
|
||||
- PKG-AUTO-THUMB-11: [记录](changes/2026-09-14-PKG-AUTO-THUMB-11-handler-cleanup.md) [记录](baselines/auto-thumbnail-handler-validation.json)
|
||||
@@ -1,5 +1,16 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-AUTO-THUMB-11 抽帧清理异常修复完成
|
||||
|
||||
事件属性清除现在与定时器、原生帧回调取消分别执行,单个异常不截断剩余清理。
|
||||
八个受控故障用例在旧main全部失败,新源码/main/legacy各27项抽帧回归通过;
|
||||
完整插件182项通过,实际main/legacy各21项三浏览器生命周期通过。异常属性由测试
|
||||
主动注入,不声称浏览器原生setter自行失败;原生验证使用真实解码和桩宿主。
|
||||
严格TS、定向lint、固定工具链、37声明零漂移通过,三格式和docs副本已重建。
|
||||
见[变更](changes/2026-09-14-PKG-AUTO-THUMB-11-handler-cleanup.md)与[证据](baselines/auto-thumbnail-handler-validation.json)。252项:180 done、17 doing、55 todo。
|
||||
任务03仍doing,04/05/06仍todo,首帧像素与完整资源验收风险保持open。下一步继续
|
||||
Auto Thumbnail资源边界与剩余插件工作;首帧需要另一原生后端/设备证据,不能靠延时放宽。
|
||||
|
||||
## PKG-MULTI-SUB-05 切源时序诊断检查点(仍未完成)
|
||||
|
||||
新增可选原生属性/事件跟踪和独立恢复事件等待对照。WebKit串行3项、重复9项及
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
| AUTO-THUMB-TYPE-01 | open / 已复现 | Published declarations misdescribe async registration and switch height/number option fields across generations | PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-08 |
|
||||
| AUTO-THUMB-EXPORT-01 | open / 已复现 | Published 1.0.1 CommonJS default namespace differs from the direct function exported by 1.1.0 | PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-06, PKG-AUTO-THUMB-09 |
|
||||
| AUTO-THUMB-DIST-01 | open / 已复现 | Published 1.0.0 archive lacks declared main and legacy runtimes | PKG-AUTO-THUMB-06 |
|
||||
| AUTO-THUMB-LIFE-01 | open / 已复现 | Extraction tasks, decoder handlers and final object URLs lack source-switch and destroy ownership | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-05 |
|
||||
| AUTO-THUMB-LIFE-01 | open / 已复现 | Extraction tasks, decoder handlers and final object URLs lack source-switch and destroy ownership | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-11 |
|
||||
| AUTO-THUMB-ENCODE-01 | open / 已复现 | Unserialized encodes include blank output and lack media/canvas/input failure handling | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-05 |
|
||||
| AUTO-THUMB-PIXEL-01 | open / 已复现 | Historical auto-thumbnail yields black sampled JPEG cells on Windows WebKit while played native video and static JPEG controls yield color | PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-05 |
|
||||
| VTT-THUMB-DIST-01 | open / 已复现 | Historical 1.0.0 main/legacy contain invalid regular expressions and cannot load | PKG-VTT-THUMB-06 |
|
||||
|
||||
+7
-3
@@ -4124,7 +4124,8 @@
|
||||
"owners": [
|
||||
"PKG-AUTO-THUMB-02",
|
||||
"PKG-AUTO-THUMB-03",
|
||||
"PKG-AUTO-THUMB-05"
|
||||
"PKG-AUTO-THUMB-05",
|
||||
"PKG-AUTO-THUMB-11"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/baselines/auto-thumbnail-release.json",
|
||||
@@ -4135,11 +4136,14 @@
|
||||
"refactor/baselines/auto-thumbnail-failures-validation.json",
|
||||
"test/auto-thumbnail-lifecycle.test.js",
|
||||
"test/browser/auto-thumbnail-lifecycle.spec.js",
|
||||
"refactor/baselines/auto-thumbnail-lifecycle-checkpoint.json"
|
||||
"refactor/baselines/auto-thumbnail-lifecycle-checkpoint.json",
|
||||
"refactor/changes/2026-09-14-PKG-AUTO-THUMB-11-handler-cleanup.md",
|
||||
"refactor/baselines/auto-thumbnail-handler-validation.json",
|
||||
"test/auto-thumbnail-frames.test.js"
|
||||
],
|
||||
"compatibleResolution": "Cancel obsolete work and release owned resources while retaining live options and progressive usable sheet updates.",
|
||||
"closureCriteria": "Delayed callbacks, repeated metadata, switching, destroy, multiple instances and failure cleanup are reproduced then pass source/artifacts and native browser checks.",
|
||||
"workspaceState": "Task03 ownership checkpoint: 22 candidate lifecycle regressions fail on frozen source and pass on source/main/legacy; two further explicit-URL short-circuit and duration-snapshot cases preserve historical successes. Final candidate runs have 24 passing cases each; 27 native lifecycle cases verify destroy/restart/completion and URL/decoder cleanup. New index/options/session/extraction modules fix serial encoding, stale callbacks, failure cleanup and selected reentry paths. Pixel/frame correctness, resource budgets and additional boundaries remain for task03; old/final core acceptance remains task05. Risk stays open rather than claiming complete extraction repair."
|
||||
"workspaceState": "Task03 ownership checkpoint: 22 candidate lifecycle regressions fail on frozen source and pass on source/main/legacy; two further explicit-URL short-circuit and duration-snapshot cases preserve historical successes. Final candidate runs have 24 passing cases each; 27 native lifecycle cases verify destroy/restart/completion and URL/decoder cleanup. New index/options/session/extraction modules fix serial encoding, stale callbacks, failure cleanup and selected reentry paths. Pixel/frame correctness, resource budgets and additional boundaries remain for task03; old/final core acceptance remains task05. Risk stays open rather than claiming complete extraction repair. Task11 fixes throwing event-property cleanup skipping deadline/frame cancellation: eight controlled regressions fail on pre-change main and pass source/main/legacy; each actual artifact also passes21 native lifecycle checks. This does not prove spontaneous native setter failure or close remaining resource/pixel/combination gates."
|
||||
},
|
||||
{
|
||||
"id": "AUTO-THUMB-ENCODE-01",
|
||||
|
||||
+23
-1
@@ -1820,7 +1820,8 @@
|
||||
"CORE-07",
|
||||
"PKG-AUTO-THUMB-08",
|
||||
"PKG-AUTO-THUMB-09",
|
||||
"PKG-AUTO-THUMB-10"
|
||||
"PKG-AUTO-THUMB-10",
|
||||
"PKG-AUTO-THUMB-11"
|
||||
],
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
@@ -5222,6 +5223,27 @@
|
||||
"changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md",
|
||||
"baselines/multiple-subtitles-ass-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-AUTO-THUMB-11",
|
||||
"phase": "5 包迁移:artplayer-plugin-auto-thumbnail",
|
||||
"title": "保证抽帧事件属性清理异常不截断其他资源释放",
|
||||
"scope": [
|
||||
"artplayer-plugin-auto-thumbnail"
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-AUTO-THUMB-02",
|
||||
"PKG-AUTO-THUMB-07",
|
||||
"PKG-AUTO-THUMB-10"
|
||||
],
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "独立清除事件属性、抽帧时限和原生帧回调;首个异常保留,过期回调失效,替换任务可继续",
|
||||
"acceptance": "八个故障注入用例旧红新绿,实际main/legacy抽帧与三浏览器生命周期回归通过;文档和构建同步,首帧及整体资源验收不冒充完成",
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-AUTO-THUMB-11-handler-cleanup.md",
|
||||
"baselines/auto-thumbnail-handler-validation.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -41,6 +41,60 @@ function closed(env) {
|
||||
assert.equal(env.video.src, undefined)
|
||||
}
|
||||
|
||||
for (const property of ['onloadeddata', 'onseeked']) {
|
||||
for (const scenario of ['destroy', 'restart', 'loadeddata-wait', 'finish']) {
|
||||
test(`Auto-thumbnail frames release independent resources when ${property} cleanup throws during ${scenario}`, async () => {
|
||||
const env = await setup({ readyState: scenario === 'loadeddata-wait' ? 1 : 4 })
|
||||
env.metadata()
|
||||
const handler = env.video[property]
|
||||
const lateSeek = env.video.onseeked
|
||||
const lateLoaded = env.video.onloadeddata
|
||||
const lateFrame = env.frames.get(0)
|
||||
const lateTimer = [...env.timers.values()][0].callback
|
||||
const failure = new Error(`${property} cleanup failed`)
|
||||
Object.defineProperty(env.video, property, {
|
||||
configurable: true,
|
||||
get: () => handler,
|
||||
set(value) {
|
||||
assert.equal(value, null)
|
||||
throw failure
|
||||
},
|
||||
})
|
||||
if (scenario === 'finish') {
|
||||
env.present()
|
||||
env.seeked()
|
||||
}
|
||||
else {
|
||||
env.art.emit(scenario === 'restart' ? 'restart' : 'destroy')
|
||||
}
|
||||
assert.equal(env.frames.size, 0, 'Native frame handles are canceled despite a handler setter failure')
|
||||
assert.equal(env.timers.size, 0, 'The sample deadline is canceled despite a handler setter failure')
|
||||
assert.equal(env.attached.size, 0)
|
||||
assert.equal(env.video.src, undefined)
|
||||
assert.deepEqual(env.canvases.map(canvas => [canvas.width, canvas.height]), [[0, 0]])
|
||||
assert.equal(env.video[property === 'onloadeddata' ? 'onseeked' : 'onloadeddata'], null)
|
||||
assert.ok(env.warnings.some(warning => warning[1] === failure))
|
||||
lateLoaded?.()
|
||||
lateSeek?.()
|
||||
lateFrame?.()
|
||||
lateTimer()
|
||||
assert.equal(env.blobs.length, 0)
|
||||
assert.equal(env.updates.length, 0)
|
||||
assert.equal(env.timers.size, 0)
|
||||
if (scenario === 'restart') {
|
||||
env.art.emit('video:loadedmetadata')
|
||||
env.metadata(env.videos[1])
|
||||
env.seeked(env.videos[1])
|
||||
env.finish()
|
||||
assert.equal(env.updates.length, 1, 'A replacement job still publishes after predecessor cleanup fails')
|
||||
}
|
||||
env.art.emit('destroy')
|
||||
assert.equal(env.urls.size, 0)
|
||||
assert.equal(env.attached.size, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test('Auto-thumbnail frames wait for loadeddata before starting presentation-aware seeking', async () => {
|
||||
const env = await setup({ readyState: 1 })
|
||||
env.metadata()
|
||||
|
||||
Reference in new issue
Block a user