mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
fix(auto-thumbnail): [PKG-AUTO-THUMB-12] release stalled metadata loads
This commit is contained in:
1 parent
d3aa4bbfe4
commit
42ddead0cd
20 files changed
+6048
-20
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;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
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const i=e([()=>{n.onloadeddata=null},()=>{n.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&n.cancelVideoFrameCallback(t.frame)}]);if(i.failed)throw i.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!i,retries:0,sequence:0,started:!1};r=u;const d=()=>t.active()&&r===u,c=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),l=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,c())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");l()}else u.sought=!0,c()}),l())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?s():n.onloadeddata=s)}}function n(e,n){const i=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const o=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(o),i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{o(),i.onloadedmetadata=null;const r=i.duration,a=i.videoHeight,u=i.videoWidth,{height:d,canvasWidth:c,canvasHeight:l}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),i=Math.trunc(10*e.width),r=Math.trunc(n*Math.ceil(e.number/10));if(![n,i,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:i,canvasHeight:r}}(n,{duration:r,videoHeight:a,videoWidth:u});if(!e.active())return;i.width=u,i.height=a,i.style.width=`${u}px`,i.style.height=`${a}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=c,!e.active())return;if(s.height=l,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const h=t(e,i),m=function(e,t){let n;function i(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(i),r=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(i(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=n.number?e.dispose():h(r*v/n.number,e.guard(()=>{f.drawImage(i,v%10*n.width,Math.floor(v/10)*d,n.width,d),e.active()&&m(t=>{e.publish(t,{height:d,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?i.src=n.url:o())}function i(t){return async i=>{const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,n){let i,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=i;i=void 0,e?.dispose()}return{start:functioLine 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,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
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,l,d=!1;const c=new Set;function s(e){void 0!==e&&c.delete(e)&&URL.revokeObjectURL(e)}function f(){const e=u;u=void 0,null==e||e.dispose()}return{start:function(){if(d)return;const f=u,h=[];let m=!1;const v={active:()=>!d&&!m&&u===v,own(e){if(m){const t=o([e]);t.failed&&a(t.failure)}else h.push(e)},dispose(){if(m)return;m=!0,u===v&&(u=void 0);const e=o(h.splice(0).reverse());e.failed&&a(e.failure)},fail(e){v.active()&&(v.dispose(),a(e))},guard:e=>(...t)=>{if(v.active())try{return e(...t)}catch(n){v.fail(n)}},publish(u,d){if(!v.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!v.active())return void s(f);const h=l;l=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&i(e,a,o[a]);if(t)for(var a of t(o))r.call(o,a)&&i(e,a,o[a]);return e})({url:f},d))}catch(m){l===f&&(l=void 0!==h&&c.has(h)?h:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),m}s(h)}};return u=v,null==f||f.dispose(),v},cancel:f,destroy:function(){if(d)return;d=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);l=void 0,e.failed&&a(e.failure)},get closed(){return d}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let r;function i(){const e=r;r=void 0;const n=o([()=>{t.onloadeddata=null},()=>{t.onseeked=null},()=>{e&&null!==e.timer&&clearTimeout(e.timer)},()=>{e&&null!==e.frame&&t.cancelVideoFrameCallback(e.frame)}]);if(n.failed)throw n.failure}return e.own(i),(o,a)=>{if(i(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};r=u;const l=()=>e.active()&&r===u,d=e.guard(()=>{l()&&u.sought&&u.presented&&(i(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const r=++u.sequence;u.presented=!1;const i=u.frame;if(u.frame=null,null!==i&&t.cancelVideoFrameCallback(i),!l())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{l()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,d())}));l()&&r===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),l()&&(t.currentTime=o)},s=e.guard(()=>{l()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!l()||t.seeking)return;const e=t.currentTime;if(l())if(!Number.isFinite(e)||Math.abs(e-o)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,d()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(l())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!l())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;l()&&(f?s():t.onloadeddata=s)}}function l(e,t){const n=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const i=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(i),n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{i(),n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:l,canvasWidth:d,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),r=Math.trunc(10*e.width),i=Math.trunc(n*Math.ceil(e.number/10));if(![n,r,i].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:r,canvasHeight:i}}(t,{duration:r,videoHeight:o,videoWidth:a});if(!e.active())return;n.width=a,n.height=o,n.style.width=`${a}px`,n.style.height=`${o}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=Line truncated
|
||||
@@ -317,10 +317,19 @@ function extract(job, config) {
|
||||
const video = createVideo(job);
|
||||
if (!job.active())
|
||||
return;
|
||||
let metadataTimer = null;
|
||||
const clearMetadataTimer = () => {
|
||||
const previous = metadataTimer;
|
||||
metadataTimer = null;
|
||||
if (previous !== null)
|
||||
clearTimeout(previous);
|
||||
};
|
||||
job.own(clearMetadataTimer);
|
||||
video.onerror = job.guard(() => {
|
||||
throw video.error || new Error("Auto-thumbnail media failed to load");
|
||||
});
|
||||
video.onloadedmetadata = job.guard(() => {
|
||||
clearMetadataTimer();
|
||||
video.onloadedmetadata = null;
|
||||
const duration = video.duration;
|
||||
const videoHeight = video.videoHeight;
|
||||
@@ -379,8 +388,17 @@ function extract(job, config) {
|
||||
});
|
||||
seek();
|
||||
});
|
||||
if (job.active())
|
||||
video.src = config.url;
|
||||
if (!job.active())
|
||||
return;
|
||||
metadataTimer = setTimeout(job.guard(() => {
|
||||
if (metadataTimer !== null)
|
||||
throw new Error("Auto-thumbnail metadata timed out");
|
||||
}), 3e4);
|
||||
if (!job.active()) {
|
||||
clearMetadataTimer();
|
||||
return;
|
||||
}
|
||||
video.src = config.url;
|
||||
}
|
||||
function artplayerPluginAutoThumbnail(option) {
|
||||
return async (art) => {
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:vconsole && yarn build:console && yarn build:site-notices && yarn build:test && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
|
||||
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
|
||||
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
@@ -87,7 +87,7 @@
|
||||
"test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs",
|
||||
"test:thumbnail": "node --test test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs refactor/scripts/thumbnail-types.test.mjs",
|
||||
"test:thumbnail-types-package": "node refactor/scripts/thumbnail-package-types.mjs",
|
||||
"test:auto-thumbnail": "node --test test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs refactor/scripts/auto-thumbnail-types.test.mjs",
|
||||
"test:auto-thumbnail": "node --test test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs refactor/scripts/auto-thumbnail-types.test.mjs",
|
||||
"test:vtt-thumbnail": "node --test test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js refactor/scripts/vtt-thumbnail-contract.test.mjs refactor/scripts/vtt-thumbnail-types.test.mjs",
|
||||
"test:vtt-thumbnail-types-package": "node refactor/scripts/vtt-thumbnail-package-types.mjs",
|
||||
"test:multiple-subtitles": "node --test test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs",
|
||||
|
||||
@@ -24,6 +24,13 @@ for video processing.
|
||||
handlers, and the frame/encode sequence. Each valid draw is encoded before the
|
||||
next seek. The final decoder is paused, cleared and reset; the final sheet URL
|
||||
remains owned by the session until another usable sheet replaces it or destroy.
|
||||
Metadata loading has a separate 30-second deadline, started before assigning
|
||||
the URL and canceled when metadata arrives or the job ends. A queued deadline
|
||||
becomes inert after that phase; it cannot cancel frame reading or encoding.
|
||||
A stalled load reports through the existing warning callback and disposes its
|
||||
decoder without replacing the previous usable sheet. Timer-registration reentry
|
||||
cannot restart loading after destruction. This does not time out registration,
|
||||
change the public result, retry the URL, or impose a total extraction deadline.
|
||||
The private canvas is also job-owned: cancellation, failure and normal completion
|
||||
reset both dimensions to zero, even if an encoding callback still retains it.
|
||||
Width and height resets are independent cleanup actions, so one throwing setter
|
||||
@@ -104,9 +111,10 @@ separate30-second encoding deadline starting just before toBlob. This is an
|
||||
intentional bound on formerly unbounded pending encoding; it does not shorten the
|
||||
frame wait or change the public frame-time formula. Very slow encodes can now
|
||||
warn and retain the last preview instead of retaining the decoder indefinitely.
|
||||
Initial metadata/network acquisition remains governed by media errors and session
|
||||
cancellation, not this encoding deadline. Neither timeout guarantees browser-level
|
||||
GPU/encoder reclamation or exact background-tab wall-clock scheduling.
|
||||
Initial metadata/network acquisition has its own 30-second deadline, as described
|
||||
above, and also ends on media errors or session cancellation. None of these
|
||||
deadlines guarantees browser-level GPU/encoder reclamation or exact background-tab
|
||||
wall-clock scheduling.
|
||||
|
||||
The previous empty first encode is removed. Encoding is serial, duplicate native
|
||||
callbacks are consumed once, and source replacement/destruction invalidates old
|
||||
|
||||
@@ -39,6 +39,12 @@ the runtime type for historical callers but remains ignored: the video aspect
|
||||
ratio determines sheet height. Numeric strings retain their old JavaScript
|
||||
coercion behavior but are not advertised as numeric TypeScript options.
|
||||
|
||||
The private decoder stops if metadata does not arrive within 30 seconds. Frame
|
||||
readiness and each JPEG encoding operation also have their own 30-second limit.
|
||||
Failures use the existing console warning and retain the last usable thumbnail
|
||||
sheet; registration still resolves before processing. Restarting or destroying
|
||||
the player cancels the active wait and releases the private decoder.
|
||||
|
||||
The runtime entry supports default ESM imports and CommonJS
|
||||
`import autoThumbnail = require('artplayer-plugin-auto-thumbnail/runtime')`.
|
||||
It exports the `Option`, `Result`, `Factory`, and `RuntimeFactory` types. `Factory`
|
||||
|
||||
+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;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
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";function e(e){let t,n=!1;for(const r of e)try{r()}catch(i){n||(n=!0,t=i)}return{failed:n,failure:t}}function t(t,n){const i="function"===typeof n.requestVideoFrameCallback&&"function"===typeof n.cancelVideoFrameCallback;let r;function o(){const t=r;r=void 0;const i=e([()=>{n.onloadeddata=null},()=>{n.onseeked=null},()=>{t&&null!==t.timer&&clearTimeout(t.timer)},()=>{t&&null!==t.frame&&n.cancelVideoFrameCallback(t.frame)}]);if(i.failed)throw i.failure}return t.own(o),(e,a)=>{if(o(),!t.active())return;const u={frame:null,timer:null,sought:!1,presented:!i,retries:0,sequence:0,started:!1};r=u;const d=()=>t.active()&&r===u,c=t.guard(()=>{d()&&u.sought&&u.presented&&(o(),t.active()&&a())}),l=()=>{u.sought=!1,(()=>{if(!i)return;const e=++u.sequence;u.presented=!1;const r=u.frame;if(u.frame=null,null!==r&&n.cancelVideoFrameCallback(r),!d())return;let o=!1;const a=n.requestVideoFrameCallback(t.guard(()=>{d()&&e===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,c())}));d()&&e===u.sequence&&!o?u.frame=a:n.cancelVideoFrameCallback(a)})(),d()&&(n.currentTime=e)},s=t.guard(()=>{d()&&!u.started&&(u.started=!0,n.onloadeddata=null,n.onseeked=t.guard(()=>{if(!d()||n.seeking)return;const t=n.currentTime;if(d())if(!Number.isFinite(t)||Math.abs(t-e)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");l()}else u.sought=!0,c()}),l())});if(u.timer=setTimeout(t.guard(()=>{if(d())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!d())return void clearTimeout(u.timer);const f=!i||n.readyState>=2;d()&&(f?s():n.onloadeddata=s)}}function n(e,n){const i=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const o=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(o),i.onerror=e.guard(()=>{throw i.error||new Error("Auto-thumbnail media failed to load")}),i.onloadedmetadata=e.guard(()=>{o(),i.onloadedmetadata=null;const r=i.duration,a=i.videoHeight,u=i.videoWidth,{height:d,canvasWidth:c,canvasHeight:l}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),i=Math.trunc(10*e.width),r=Math.trunc(n*Math.ceil(e.number/10));if(![n,i,r].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:i,canvasHeight:r}}(n,{duration:r,videoHeight:a,videoWidth:u});if(!e.active())return;i.width=u,i.height=a,i.style.width=`${u}px`,i.style.height=`${a}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=>{s.width=0}),!e.active())return;if(s.width=c,!e.active())return;if(s.height=l,!e.active())return;const f=s.getContext("2d");if(!f)throw new Error("Auto-thumbnail canvas context is unavailable");if(!e.active())return;const h=t(e,i),m=function(e,t){let n;function i(){const e=n;n=void 0,e&&null!==e.timer&&clearTimeout(e.timer)}return e.own(i),r=>{if(!e.active())return;if(n)throw new Error("Auto-thumbnail encoding is already pending");const o={timer:null};n=o;const a=()=>e.active()&&n===o;o.timer=setTimeout(e.guard(()=>{if(a())throw new Error("Auto-thumbnail encoding timed out")}),3e4),a()?t.toBlob(e.guard(t=>{a()&&(i(),e.active()&&r(t))}),"image/jpeg"):clearTimeout(o.timer)}}(e,s);let v=0;const b=e.guard(()=>{v>=n.number?e.dispose():h(r*v/n.number,e.guard(()=>{f.drawImage(i,v%10*n.width,Math.floor(v/10)*d,n.width,d),e.active()&&m(t=>{e.publish(t,{height:d,column:10,number:n.number,width:n.width,scale:n.scale}),e.active()&&(v+=1,b())})}))});b()}),e.active()&&(r=setTimeout(e.guard(()=>{if(null!==r)throw new Error("Auto-thumbnail metadata timed out")}),3e4),e.active()?i.src=n.url:o())}function i(t){return async i=>{const r=e=>console.warn("ArtPlayer auto-thumbnail failed:",e),o=function(t,n){let i,r,o=!1;const a=new Set;function u(e){void 0!==e&&a.delete(e)&&URL.revokeObjectURL(e)}function d(){const e=i;i=void 0,e?.dispose()}return{start:functioLine 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,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
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginAutoThumbnail=t(),define(function(){return e.artplayerPluginAutoThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAutoThumbnail=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r;function o(e){let t,n=!1;for(const i of e)try{i()}catch(r){n||(n=!0,t=r)}return{failed:n,failure:t}}function a(e,a){let u,l,d=!1;const c=new Set;function s(e){void 0!==e&&c.delete(e)&&URL.revokeObjectURL(e)}function f(){const e=u;u=void 0,null==e||e.dispose()}return{start:function(){if(d)return;const f=u,h=[];let m=!1;const v={active:()=>!d&&!m&&u===v,own(e){if(m){const t=o([e]);t.failed&&a(t.failure)}else h.push(e)},dispose(){if(m)return;m=!0,u===v&&(u=void 0);const e=o(h.splice(0).reverse());e.failed&&a(e.failure)},fail(e){v.active()&&(v.dispose(),a(e))},guard:e=>(...t)=>{if(v.active())try{return e(...t)}catch(n){v.fail(n)}},publish(u,d){if(!v.active())return;if(!u)throw new Error("Auto-thumbnail encoding returned no Blob");const f=URL.createObjectURL(u);if(c.add(f),!v.active())return void s(f);const h=l;l=f;try{e(((e,o)=>{for(var a in o||(o={}))n.call(o,a)&&i(e,a,o[a]);if(t)for(var a of t(o))r.call(o,a)&&i(e,a,o[a]);return e})({url:f},d))}catch(m){l===f&&(l=void 0!==h&&c.has(h)?h:void 0);const e=o([()=>s(f)]);throw e.failed&&a(e.failure),m}s(h)}};return u=v,null==f||f.dispose(),v},cancel:f,destroy:function(){if(d)return;d=!0;const e=o([f,...[...c].map(e=>()=>s(e))]);l=void 0,e.failed&&a(e.failure)},get closed(){return d}}}function u(e,t){const n="function"===typeof t.requestVideoFrameCallback&&"function"===typeof t.cancelVideoFrameCallback;let r;function i(){const e=r;r=void 0;const n=o([()=>{t.onloadeddata=null},()=>{t.onseeked=null},()=>{e&&null!==e.timer&&clearTimeout(e.timer)},()=>{e&&null!==e.frame&&t.cancelVideoFrameCallback(e.frame)}]);if(n.failed)throw n.failure}return e.own(i),(o,a)=>{if(i(),!e.active())return;const u={frame:null,timer:null,sought:!1,presented:!n,retries:0,sequence:0,started:!1};r=u;const l=()=>e.active()&&r===u,d=e.guard(()=>{l()&&u.sought&&u.presented&&(i(),e.active()&&a())}),c=()=>{u.sought=!1,(()=>{if(!n)return;const r=++u.sequence;u.presented=!1;const i=u.frame;if(u.frame=null,null!==i&&t.cancelVideoFrameCallback(i),!l())return;let o=!1;const a=t.requestVideoFrameCallback(e.guard(()=>{l()&&r===u.sequence&&!o&&(o=!0,u.frame=null,u.presented=!0,d())}));l()&&r===u.sequence&&!o?u.frame=a:t.cancelVideoFrameCallback(a)})(),l()&&(t.currentTime=o)},s=e.guard(()=>{l()&&!u.started&&(u.started=!0,t.onloadeddata=null,t.onseeked=e.guard(()=>{if(!l()||t.seeking)return;const e=t.currentTime;if(l())if(!Number.isFinite(e)||Math.abs(e-o)>.05){if(++u.retries>3)throw new Error("Auto-thumbnail seek did not reach the requested time");c()}else u.sought=!0,d()}),c())});if(u.timer=setTimeout(e.guard(()=>{if(l())throw new Error("Auto-thumbnail frame readiness timed out")}),3e4),!l())return void clearTimeout(u.timer);const f=!n||t.readyState>=2;l()&&(f?s():t.onloadeddata=s)}}function l(e,t){const n=function(e){const t=document.createElement("video");e.own(()=>t.remove()),e.own(()=>t.load()),e.own(()=>t.removeAttribute("src")),e.own(()=>t.pause());for(const n of["onloadedmetadata","onloadeddata","onseeked","onerror"])e.own(()=>{t[n]=null});return e.active()?(t.crossOrigin="anonymous",t.muted=!0,t.playsInline=!0,t.tabIndex=-1,t.setAttribute("aria-hidden","true"),t.style.cssText="position:fixed;left:0;top:0;visibility:hidden;pointer-events:none;display:block;width:auto;height:auto;max-width:none;max-height:none",document.documentElement.appendChild(t),e.active()||t.remove(),t):t}(e);if(!e.active())return;let r=null;const i=()=>{const e=r;r=null,null!==e&&clearTimeout(e)};e.own(i),n.onerror=e.guard(()=>{throw n.error||new Error("Auto-thumbnail media failed to load")}),n.onloadedmetadata=e.guard(()=>{i(),n.onloadedmetadata=null;const r=n.duration,o=n.videoHeight,a=n.videoWidth,{height:l,canvasWidth:d,canvasHeight:c}=function(e,t){if(!Number.isFinite(t.duration)||t.duration<=0||!Number.isFinite(t.videoWidth)||t.videoWidth<=0||!Number.isFinite(t.videoHeight)||t.videoHeight<=0)throw new TypeError("Auto-thumbnail requires finite media duration and dimensions");const n=Math.floor(e.width*t.videoHeight/t.videoWidth),r=Math.trunc(10*e.width),i=Math.trunc(n*Math.ceil(e.number/10));if(![n,r,i].every(e=>Number.isSafeInteger(e)&&e>0&&e<=4294967295))throw new RangeError("Auto-thumbnail canvas dimensions are invalid");return{height:n,canvasWidth:r,canvasHeight:i}}(t,{duration:r,videoHeight:o,videoWidth:a});if(!e.active())return;n.width=a,n.height=o,n.style.width=`${a}px`,n.style.height=`${o}px`;const s=document.createElement("canvas");if(e.own(()=>{s.height=0}),e.own(()=Line truncated
|
||||
+20
-2
@@ -317,10 +317,19 @@ function extract(job, config) {
|
||||
const video = createVideo(job);
|
||||
if (!job.active())
|
||||
return;
|
||||
let metadataTimer = null;
|
||||
const clearMetadataTimer = () => {
|
||||
const previous = metadataTimer;
|
||||
metadataTimer = null;
|
||||
if (previous !== null)
|
||||
clearTimeout(previous);
|
||||
};
|
||||
job.own(clearMetadataTimer);
|
||||
video.onerror = job.guard(() => {
|
||||
throw video.error || new Error("Auto-thumbnail media failed to load");
|
||||
});
|
||||
video.onloadedmetadata = job.guard(() => {
|
||||
clearMetadataTimer();
|
||||
video.onloadedmetadata = null;
|
||||
const duration = video.duration;
|
||||
const videoHeight = video.videoHeight;
|
||||
@@ -379,8 +388,17 @@ function extract(job, config) {
|
||||
});
|
||||
seek();
|
||||
});
|
||||
if (job.active())
|
||||
video.src = config.url;
|
||||
if (!job.active())
|
||||
return;
|
||||
metadataTimer = setTimeout(job.guard(() => {
|
||||
if (metadataTimer !== null)
|
||||
throw new Error("Auto-thumbnail metadata timed out");
|
||||
}), 3e4);
|
||||
if (!job.active()) {
|
||||
clearMetadataTimer();
|
||||
return;
|
||||
}
|
||||
video.src = config.url;
|
||||
}
|
||||
function artplayerPluginAutoThumbnail(option) {
|
||||
return async (art) => {
|
||||
|
||||
@@ -8,10 +8,19 @@ export default function extract(job: ExtractionJob, config: ExtractionConfig) {
|
||||
const video = createVideo(job)
|
||||
if (!job.active())
|
||||
return
|
||||
let metadataTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const clearMetadataTimer = () => {
|
||||
const previous = metadataTimer
|
||||
metadataTimer = null
|
||||
if (previous !== null)
|
||||
clearTimeout(previous)
|
||||
}
|
||||
job.own(clearMetadataTimer)
|
||||
video.onerror = job.guard(() => {
|
||||
throw video.error || new Error('Auto-thumbnail media failed to load')
|
||||
})
|
||||
video.onloadedmetadata = job.guard(() => {
|
||||
clearMetadataTimer()
|
||||
video.onloadedmetadata = null
|
||||
const duration = video.duration
|
||||
const videoHeight = video.videoHeight
|
||||
@@ -70,6 +79,15 @@ export default function extract(job: ExtractionJob, config: ExtractionConfig) {
|
||||
})
|
||||
seek()
|
||||
})
|
||||
if (job.active())
|
||||
video.src = config.url
|
||||
if (!job.active())
|
||||
return
|
||||
metadataTimer = setTimeout(job.guard(() => {
|
||||
if (metadataTimer !== null)
|
||||
throw new Error('Auto-thumbnail metadata timed out')
|
||||
}), 30000)
|
||||
if (!job.active()) {
|
||||
clearMetadataTimer()
|
||||
return
|
||||
}
|
||||
video.src = config.url
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,69 @@
|
||||
# PKG-AUTO-THUMB-12: bound the private decoder metadata wait
|
||||
|
||||
Implementation baseline: d3aa4bbfe4d9931249dce5e6a43afbe54b39800f.
|
||||
|
||||
## Defect and implementation
|
||||
|
||||
The private video could remain attached indefinitely if its request produced
|
||||
neither loadedmetadata nor error. Frame and JPEG deadlines start later, so they
|
||||
did not cover this phase. The seven final regression cases fail against the
|
||||
prechange committed main artifact and pass against the changed implementation.
|
||||
An earlier test draft omitted the required options object in one case; that draft
|
||||
is not used as the final seven-case reproduction evidence.
|
||||
|
||||
`src/extraction.ts` now owns a 30-second metadata timer from immediately before
|
||||
URL assignment until loadedmetadata or job disposal. Its callback becomes inert
|
||||
when the phase ends, including if already queued before cancellation. The job's
|
||||
existing disposal releases the handlers, resets and removes the decoder. Timer
|
||||
registration that synchronously destroys the session cannot resume URL loading.
|
||||
No extra module or dependency is needed: extraction already owns this phase.
|
||||
|
||||
This intentionally replaces an unbounded failed-load wait with the same warning
|
||||
and cleanup policy used for frames and encoding. A slow load may now stop after
|
||||
30 seconds. The last usable sheet remains available until replacement or destroy.
|
||||
The public factory, Promise timing/result, options, ten-column sheet layout, frame
|
||||
time formula, declarations, package entrypoints and dependency versions are
|
||||
unchanged. Timers remain subject to browser/background scheduling; this is not a
|
||||
guarantee of immediate network/GPU reclamation or an aggregate extraction timeout.
|
||||
|
||||
## Verification
|
||||
|
||||
The new unit cases cover no metadata, late metadata/timer callbacks, success,
|
||||
native errors, destroy/restart, retained previous sheets and synchronous timer
|
||||
registration reentry. They are part of `test:auto-thumbnail` and `test:unit`.
|
||||
The existing native lifecycle file adds pending-metadata cases for timeout,
|
||||
destroy and restart and remains in both source and installed CI collections.
|
||||
|
||||
The first browser harness used Playwright interception; Chromium/Firefox passed,
|
||||
but three WebKit cases timed out waiting for `page.waitForRequest` before testing
|
||||
cleanup. This is retained as failed harness evidence, not a plugin pass. The final
|
||||
fixture uses a real server response that sends no metadata and confirms requests
|
||||
through the server log. Its 10-second socket inactivity bound prevents failed
|
||||
tests from leaving permanent connections. The test explicitly invokes the captured
|
||||
30-second callback; it verifies native video cleanup, not 30 seconds of wall time.
|
||||
Existing successful decoding/encoding tests continue to use real media events.
|
||||
|
||||
Commands and exact counts/artifact hashes are in the
|
||||
[validation record](../baselines/auto-thumbnail-metadata-validation.json).
|
||||
Source tests pass 189/189; rebuilt main and legacy each pass 82 targeted unit
|
||||
cases and 36 browser cases across Chromium, Firefox and Windows WebKit. The latter
|
||||
includes nine pending-metadata cases per artifact. Neither final browser run has
|
||||
unhandled page errors. Failed-request observations (14 main, 16 legacy) remain in
|
||||
the record, including deliberately canceled pending loads; these are not reports
|
||||
of zero network failures. Package strict types, scoped lint and strict toolchain
|
||||
checks pass. Existing WebKit pixel assertions still exclude the unresolved first
|
||||
two cells when presentation callbacks are unavailable, as before this fix.
|
||||
Normal `yarn build artplayer-plugin-auto-thumbnail` regenerates main, legacy and
|
||||
ESM plus matching docs copies. There are no hand-edited bundles.
|
||||
|
||||
## Maintenance and remaining gates
|
||||
|
||||
The package README and ARCHITECTURE explain the independent metadata/frame/encode
|
||||
deadlines and failure policy. The Windows WebKit first-frame pixel issue remains
|
||||
open: existing pixel tests retain their documented fallback limitations. This
|
||||
fix does not complete task03, the package combination matrix, remote CI, device
|
||||
validation, or release reviews. No publishing, pushing or deployment is included.
|
||||
|
||||
Revert this task's dedicated commit to restore source/tests/docs and generated
|
||||
artifacts together. Future changes to metadata loading should rerun the unit
|
||||
suite and native lifecycle/pixel tests against rebuilt main and legacy outputs.
|
||||
+6
-4
@@ -2,9 +2,9 @@
|
||||
|
||||
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 265 项,范围 22 个包及工作区/示例。
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 266 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 44 / doing 22 / blocked 0 / done 199 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 44 / doing 22 / blocked 0 / done 200 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
| artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06, PKG-FACTORY-01 |
|
||||
| artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06, PKG-ASR-07, PKG-ASR-08, PKG-ASR-09 |
|
||||
| artplayer-plugin-audio-track | 1.1.0 | PKG-AUDIO-01, PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-04, PKG-AUDIO-05, PKG-AUDIO-06 |
|
||||
| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11 |
|
||||
| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11, PKG-AUTO-THUMB-12 |
|
||||
| artplayer-plugin-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06, PKG-CHAPTER-HOVER-01 |
|
||||
| artplayer-plugin-chromecast | 1.1.0 | PKG-CAST-01, PKG-CAST-02, PKG-CAST-03, PKG-CAST-04, PKG-CAST-05, PKG-CAST-06 |
|
||||
| artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, PKG-DANMUKU-10, PKG-DANMUKU-11, PKG-DANMUKU-12, PKG-DANMUKU-MASK-LOAD-01 |
|
||||
@@ -179,13 +179,14 @@
|
||||
| PKG-AUTO-THUMB-02 | artplayer-plugin-auto-thumbnail<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, 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-05 | artplayer-plugin-auto-thumbnail<br>验证新旧核心和组合 | PKG-AUTO-THUMB-04, CORE-22, PKG-AUTO-THUMB-12 | 旧核心/候选核心预览、多次加载无过期缩略图覆盖 | 最终核心与原支持范围核心分别通过;设备/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 |
|
||||
| PKG-AUTO-THUMB-12 | artplayer-plugin-auto-thumbnail<br>终止隐藏视频元数据永久等待并释放资源 | PKG-AUTO-THUMB-02, CORE-09, CORE-19 | 元数据独立 30 秒期限、迟到回调失效、取消和错误清理、保留前一张可用预览;源码/重建产物及浏览器证据 | 旧红新绿、正常加载与阶段交接通过;实际挂起 HTTP 下销毁/切源/超时清理通过,不改变公开注册或帧时间公式,不关闭既有首帧风险 | M | done |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-vtt-thumbnail
|
||||
|
||||
@@ -695,3 +696,4 @@
|
||||
- PKG-HLS-PACK-01: [记录](changes/2026-09-15-PKG-HLS-PACK-01-config.md) [记录](baselines/hls-pack-config-validation.json)
|
||||
- ENG-LINT-02: [记录](changes/2026-09-15-ENG-LINT-02-clean-checks.md) [记录](baselines/eng-lint02-validation.json)
|
||||
- SITE-CONSOLE-01: [记录](console-modernization.md) [记录](changes/2026-09-15-SITE-CONSOLE-01-typescript.md) [记录](baselines/site-console01-validation.json)
|
||||
- PKG-AUTO-THUMB-12: [记录](changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md) [记录](baselines/auto-thumbnail-metadata-validation.json)
|
||||
@@ -1,5 +1,12 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-AUTO-THUMB-12 元数据加载超时与资源回收
|
||||
|
||||
修复隐藏视频在未收到 metadata/error 时永久等待:独立 30 秒期限归 extraction/job 所有,成功/取消后旧期限失效,超时保留上一张可用图。旧 main 的 7 项回归全红;源码 189/189、main/legacy 各 82/82,重建后三引擎各 36/36。首版 WebKit 请求拦截等待失败已保留,最终用真实 HTTP 挂起端点及服务端到达证据验证。期限回调由测试显式触发,不声称等待了真实 30 秒。见[变更](changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md)与[证据](baselines/auto-thumbnail-metadata-validation.json)。
|
||||
|
||||
本任务完成后 200/266;AUTO-THUMB-PIXEL-01、03/04/05 及设备/远端 CI/发布复盘仍未完成。SITE-07 的 Node 路径检查点已提交 d3aa4bbfe,后续来源审查仍开放。下一步继续关闭实际插件兼容/生命周期问题,不重复已定位的本机原生首帧对照。
|
||||
|
||||
|
||||
## SITE-07 Monaco Node 路径来源与完整模块
|
||||
|
||||
核实 Node v14.16.0 固定源码及 VS Code 移植。原 notice 已含完整相同条款,新增
|
||||
|
||||
@@ -279,3 +279,4 @@
|
||||
| CONSOLE-SCROLL-01 | resolved / 已复现 | 控制台卸载后仍执行滚动回调 | SITE-CONSOLE-01, SITE-07 |
|
||||
| CONSOLE-OWNERSHIP-01 | resolved / 已复现 | 多个控制台的全局 hook 所有权冲突 | SITE-CONSOLE-01, SITE-07 |
|
||||
| CONSOLE-ERROR-01 | resolved / 已复现 | Firefox/WebKit 控制台丢失原生 Error 消息 | SITE-CONSOLE-01, SITE-07 |
|
||||
| AUTO-THUMB-METADATA-01 | resolved / 已复现 | Private metadata loading can retain its decoder indefinitely without metadata or media errors | PKG-AUTO-THUMB-12 |
|
||||
@@ -6552,6 +6552,25 @@
|
||||
"scripts/site-vendor/console/README.md"
|
||||
],
|
||||
"resolutionRationale": "Candidate behavior failed on the original bundle (11 failed/1 passed); final browser/editor matrix 63 passed plus shared parser 3 passed across Chromium/Firefox/WebKit. Units, strict types and generation checks pass. Preserves React17, old component return, raw objects, exact CSS and 100 vendor module bodies. Physical BFCache and complete third-party provenance remain separate."
|
||||
},
|
||||
{
|
||||
"id": "AUTO-THUMB-METADATA-01",
|
||||
"title": "Private metadata loading can retain its decoder indefinitely without metadata or media errors",
|
||||
"confirmation": "reproduced",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-AUTO-THUMB-12"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md",
|
||||
"refactor/baselines/auto-thumbnail-metadata-validation.json"
|
||||
],
|
||||
"compatibleResolution": "Bound only the private metadata phase, dispose through existing ownership, preserve usable previews and public registration timing.",
|
||||
"closureCriteria": "Prechange failure and candidate unit/artifact/browser evidence cover stale deadlines, no metadata, cancellation, success and decoder cleanup.",
|
||||
"resolutionEvidence": [
|
||||
"refactor/baselines/auto-thumbnail-metadata-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Seven regressions fail on prechange main; source 189/189, main and legacy 82/82, native main and legacy 36/36 each. Final pending-response tests observe the HTTP server directly. Existing first-frame risk stays open."
|
||||
}
|
||||
]
|
||||
}
|
||||
+23
-1
@@ -1872,7 +1872,8 @@
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-AUTO-THUMB-04",
|
||||
"CORE-22"
|
||||
"CORE-22",
|
||||
"PKG-AUTO-THUMB-12"
|
||||
],
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
@@ -5676,6 +5677,27 @@
|
||||
"changes/2026-09-15-SITE-CONSOLE-01-typescript.md",
|
||||
"baselines/site-console01-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-AUTO-THUMB-12",
|
||||
"phase": "5 包迁移:artplayer-plugin-auto-thumbnail",
|
||||
"title": "终止隐藏视频元数据永久等待并释放资源",
|
||||
"scope": [
|
||||
"artplayer-plugin-auto-thumbnail"
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-AUTO-THUMB-02",
|
||||
"CORE-09",
|
||||
"CORE-19"
|
||||
],
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "元数据独立 30 秒期限、迟到回调失效、取消和错误清理、保留前一张可用预览;源码/重建产物及浏览器证据",
|
||||
"acceptance": "旧红新绿、正常加载与阶段交接通过;实际挂起 HTTP 下销毁/切源/超时清理通过,不改变公开注册或帧时间公式,不关闭既有首帧风险",
|
||||
"evidence": [
|
||||
"changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md",
|
||||
"baselines/auto-thumbnail-metadata-validation.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import assert from 'node:assert/strict'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Verify metadata wait ownership and cancellation.
|
||||
import test from 'node:test'
|
||||
import { autoThumbnailCandidate, autoThumbnailEnvironment } from './helpers/auto-thumbnail.js'
|
||||
|
||||
const implementation = await autoThumbnailCandidate()
|
||||
async function setup() {
|
||||
const env = autoThumbnailEnvironment(implementation)
|
||||
const result = await env.factory({ width: 80, number: 1 })(env.art)
|
||||
assert.equal(result.name, 'artplayerPluginAutoThumbnail')
|
||||
env.art.emit('video:loadedmetadata')
|
||||
return env
|
||||
}
|
||||
|
||||
function released(env, video = env.videos[0]) {
|
||||
assert.equal(env.attached.has(video), false)
|
||||
assert.equal(video.src, undefined)
|
||||
assert.equal(video.onloadedmetadata, null)
|
||||
assert.equal(video.onerror, null)
|
||||
}
|
||||
|
||||
test('Auto-thumbnail metadata timeout releases a stalled decoder and ignores late metadata', async () => {
|
||||
const env = await setup()
|
||||
const metadata = env.videos[0].onloadedmetadata
|
||||
const deadline = [...env.timers.values()][0]
|
||||
assert(deadline, 'Metadata loading must have an owned deadline')
|
||||
assert.equal(deadline.delay, 30000)
|
||||
deadline.callback()
|
||||
released(env)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert.equal(env.warnings.length, 1)
|
||||
assert.match(env.warnings[0][1].message, /metadata timed out/)
|
||||
metadata()
|
||||
deadline.callback()
|
||||
assert.equal(env.canvases.length, 0)
|
||||
assert.equal(env.updates.length, 0)
|
||||
assert.equal(env.warnings.length, 1)
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
|
||||
for (const scenario of ['destroy', 'restart', 'error', 'metadata']) {
|
||||
test(`Auto-thumbnail metadata deadline becomes inert after ${scenario}`, async () => {
|
||||
const env = await setup()
|
||||
const deadline = [...env.timers.values()][0]
|
||||
assert(deadline, 'Metadata loading must have an owned deadline')
|
||||
if (scenario === 'metadata') {
|
||||
env.metadata()
|
||||
const frameDeadline = [...env.timers.values()][0]
|
||||
assert.notEqual(frameDeadline, deadline)
|
||||
deadline.callback()
|
||||
assert.equal(env.timers.size, 1, 'A queued metadata timeout cannot cancel the new frame wait')
|
||||
env.seeked()
|
||||
env.finish()
|
||||
assert.equal(env.updates.length, 1)
|
||||
}
|
||||
else if (scenario === 'error') {
|
||||
env.videos[0].error = new Error('Media failed')
|
||||
env.videos[0].onerror()
|
||||
}
|
||||
else {
|
||||
env.art.emit(scenario)
|
||||
}
|
||||
deadline.callback()
|
||||
released(env)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert.equal(env.warnings.length, scenario === 'error' ? 1 : 0)
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
}
|
||||
|
||||
test('Auto-thumbnail stalled replacement retains the previous usable sheet', async () => {
|
||||
const env = await setup()
|
||||
env.metadata()
|
||||
env.seeked()
|
||||
env.finish()
|
||||
const sheet = env.art.thumbnails
|
||||
env.art.emit('restart')
|
||||
env.art.emit('video:loadedmetadata')
|
||||
const deadline = [...env.timers.values()][0]
|
||||
assert(deadline, 'Replacement metadata loading must have a deadline')
|
||||
deadline.callback()
|
||||
released(env, env.videos[1])
|
||||
assert.equal(env.art.thumbnails, sheet)
|
||||
assert.equal(env.urls.has(sheet.url), true)
|
||||
assert.equal(env.updates.length, 1)
|
||||
env.art.emit('destroy')
|
||||
assert.equal(env.urls.size, 0)
|
||||
})
|
||||
|
||||
test('Auto-thumbnail metadata timer registration cannot start loading after destruction', async () => {
|
||||
const env = autoThumbnailEnvironment(implementation)
|
||||
await env.factory({ width: 80, number: 1 })(env.art)
|
||||
const schedule = env.box.setTimeout
|
||||
env.box.setTimeout = (...args) => {
|
||||
env.art.emit('destroy')
|
||||
return schedule(...args)
|
||||
}
|
||||
env.art.emit('video:loadedmetadata')
|
||||
released(env)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert.equal(env.canvases.length, 0)
|
||||
})
|
||||
@@ -3,6 +3,73 @@ import { autoThumbnailCandidate } from '../helpers/auto-thumbnail.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementation = await autoThumbnailCandidate()
|
||||
for (const scenario of ['timeout', 'destroy', 'restart']) {
|
||||
test(`Auto-thumbnail candidate native pending metadata releases on ${scenario}`, async ({ page, request }, testInfo) => {
|
||||
await page.goto('/test/player.html?core=published')
|
||||
await page.setContent('<!doctype html><div></div>')
|
||||
const caseId = `${testInfo.testId}-${testInfo.retry}`
|
||||
await page.addScriptTag({ content: implementation.code })
|
||||
await page.evaluate(async (caseId) => {
|
||||
const schedule = window.setTimeout
|
||||
const clear = window.clearTimeout
|
||||
const warn = console.warn
|
||||
const probe = window.metadataProbe = { timers: new Map(), warnings: [], updates: [], listeners: new Map() }
|
||||
window.setTimeout = (callback, delay, ...args) => {
|
||||
const id = schedule(callback, delay, ...args)
|
||||
if (delay === 30000)
|
||||
probe.timers.set(id, callback)
|
||||
return id
|
||||
}
|
||||
window.clearTimeout = (id) => {
|
||||
probe.timers.delete(id)
|
||||
clear(id)
|
||||
}
|
||||
console.warn = (...args) => probe.warnings.push(args.map(String))
|
||||
probe.restore = () => {
|
||||
window.setTimeout = schedule
|
||||
window.clearTimeout = clear
|
||||
console.warn = warn
|
||||
}
|
||||
probe.art = {
|
||||
option: { url: `/test/pending-thumbnail-metadata.mp4?case=${encodeURIComponent(caseId)}` },
|
||||
on(name, callback) { probe.listeners.set(name, callback) },
|
||||
off(name) { probe.listeners.delete(name) },
|
||||
get thumbnails() { return probe.updates.at(-1) },
|
||||
set thumbnails(value) { probe.updates.push(value) },
|
||||
}
|
||||
probe.result = await window.artplayerPluginAutoThumbnail({ width: 80, number: 1 })(probe.art)
|
||||
probe.listeners.get('video:loadedmetadata')()
|
||||
probe.video = document.querySelector('video')
|
||||
}, caseId)
|
||||
const requestLog = `/test/requests.json?case=${encodeURIComponent(caseId)}`
|
||||
await expect.poll(async () => (await (await request.get(requestLog)).json()).length).toBeGreaterThan(0)
|
||||
const mediaRequests = await (await request.get(requestLog)).json()
|
||||
const evidence = await page.evaluate((scenario) => {
|
||||
const probe = window.metadataProbe
|
||||
const video = probe.video
|
||||
const before = { connected: video.isConnected, readyState: video.readyState, source: video.getAttribute('src'), timers: probe.timers.size }
|
||||
const deadline = [...probe.timers.values()][0]
|
||||
if (scenario === 'timeout')
|
||||
deadline?.()
|
||||
else probe.listeners.get(scenario)()
|
||||
const after = { connected: video.isConnected, readyState: video.readyState, source: video.getAttribute('src'), metadataHandler: video.onloadedmetadata, errorHandler: video.onerror, timers: probe.timers.size, canvases: document.querySelectorAll('canvas').length }
|
||||
deadline?.()
|
||||
probe.listeners.get('destroy')?.()
|
||||
probe.restore()
|
||||
return { before, after, result: probe.result, warnings: probe.warnings, updates: probe.updates.length }
|
||||
}, scenario)
|
||||
await testInfo.attach('auto-thumbnail-metadata-deadline', { contentType: 'application/json', body: JSON.stringify({ provenance: implementation.provenance, sha256: hash(implementation.code), scenario, mediaRequests, virtualDeadline: true, ...evidence }) })
|
||||
expect(evidence.result).toEqual({ name: 'artplayerPluginAutoThumbnail' })
|
||||
expect(evidence.before).toMatchObject({ connected: true, readyState: 0, timers: 1 })
|
||||
expect(evidence.before.source).toContain('pending-thumbnail-metadata.mp4')
|
||||
expect(evidence.after).toEqual({ connected: false, readyState: 0, source: null, metadataHandler: null, errorHandler: null, timers: 0, canvases: 0 })
|
||||
expect(evidence.updates).toBe(0)
|
||||
expect(evidence.warnings).toHaveLength(scenario === 'timeout' ? 1 : 0)
|
||||
if (scenario === 'timeout')
|
||||
expect(evidence.warnings[0].join(' ')).toContain('metadata timed out')
|
||||
})
|
||||
}
|
||||
|
||||
for (const variant of ['destroy', 'restart', 'complete', 'frame-destroy', 'frame-restart', 'alias-complete', 'encoding-timeout']) {
|
||||
const useDefault = variant === 'alias-complete'
|
||||
const scenario = useDefault ? 'complete' : variant
|
||||
|
||||
@@ -181,6 +181,12 @@ const server = http.createServer((req, res) => {
|
||||
res.writeHead(503, { 'Content-Type': 'text/plain', 'Cache-Control': 'no-store' }).end('Intentional media failure')
|
||||
return
|
||||
}
|
||||
if (url.pathname === '/test/pending-thumbnail-metadata.mp4') {
|
||||
// Native media backends may bypass browser request interception. Keep this
|
||||
// real HTTP response pending, with a server-side bound if a test fails.
|
||||
res.setTimeout(10000, () => res.destroy())
|
||||
return
|
||||
}
|
||||
if (files.has(url.pathname)) {
|
||||
if (url.pathname === '/test/vast-skippable.xml' && ['http://imasdk.googleapis.com', 'https://imasdk.googleapis.com'].includes(req.headers.origin)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
|
||||
|
||||
Reference in new issue
Block a user